Skip to content

Commit 1a7b592

Browse files
author
Andrei Makeev
committed
Implement df command using os.statvfs
1 parent c8a2cd7 commit 1a7b592

File tree

3 files changed

+337
-1
lines changed

3 files changed

+337
-1
lines changed

README.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,25 @@ and destination, it will only be copied if the source is newer than the
378378
destination.
379379

380380

381+
df
382+
--
383+
384+
::
385+
386+
usage: df [-b|-h|-H]
387+
388+
Report file system space usage
389+
390+
optional arguments:
391+
--help show this help message and exit
392+
-b, --bytes Prints sizes in bytes
393+
-h, --human-readable Prints sizes in a human-readable format using power of 1024
394+
-H, --si Prints sizes in a human-readable format using power of 1000
395+
396+
Gets filesystem available space based on statvfs. Granularity is limited
397+
to filesystem block size.
398+
399+
381400
echo
382401
----
383402

rshell/dfutils.py

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#!/usr/bin/env python3
2+
3+
"""Implements tooling for formatting df command columns"""
4+
5+
from enum import Enum
6+
7+
8+
def convert_bytes(size, unit = '', always_append_unit = False):
9+
"""Converts size in bytes to closest power of 1024: Ki, Mi, Gi, etc.
10+
"""
11+
single_unit = '' if always_append_unit else unit
12+
appendix = unit if always_append_unit else ''
13+
for x in [single_unit, 'Ki', 'Mi', 'Gi', 'Ti']:
14+
if size < 1024.0:
15+
return "%3.1f%s%s" % (size, x, appendix)
16+
size /= 1024.0
17+
18+
return size
19+
20+
def convert_bytes_si(size, unit = '', always_append_unit = False):
21+
"""Converts size in bytes to closest power of 1000: K, M, G, etc.
22+
"""
23+
single_unit = '' if always_append_unit else unit
24+
appendix = unit if always_append_unit else ''
25+
for x in [single_unit, 'K', 'M', 'G', 'T']:
26+
if size < 1000.0:
27+
return "%3.1f%s%s" % (size, x, appendix)
28+
size /= 1000.0
29+
30+
return size
31+
32+
33+
class DByteFormat(Enum):
34+
"""Enum for selecting the formatting for size in bytes
35+
"""
36+
37+
BYTES = 1
38+
"""Output in bytes
39+
"""
40+
41+
HUMAN = 2
42+
"""Output in human readable format: powers of 1024
43+
"""
44+
45+
HUMAN_SI = 3
46+
"""Output in human readable format: powers of 1000
47+
"""
48+
49+
50+
class DfColumn:
51+
52+
def title(self):
53+
pass
54+
55+
def formatted(statvfs, dev_name, dir):
56+
pass
57+
58+
59+
class DfNumColumn(DfColumn):
60+
61+
def __init__(self, num_format = '{:d}'):
62+
self.num_format = num_format
63+
64+
def formatted(self, statvfs, dev_name, dir):
65+
value = self.get_num_value(statvfs)
66+
return self.num_format.format(value)
67+
68+
def get_num_value(self, statvfs):
69+
pass
70+
71+
72+
class DfByteColumn(DfNumColumn):
73+
74+
def __init__(self, byte_format):
75+
self.byte_format = byte_format
76+
super().__init__('{:d}B')
77+
78+
def formatted(self, statvfs, dev_name, dir):
79+
value = self.get_num_value(statvfs)
80+
if self.byte_format == DByteFormat.HUMAN:
81+
return convert_bytes(value, 'B')
82+
elif self.byte_format == DByteFormat.HUMAN_SI:
83+
return convert_bytes_si(value, 'B')
84+
else: # fallback to bytes as default
85+
return super().formatted(statvfs, dev_name, dir)
86+
87+
88+
class DfFilesystem(DfColumn):
89+
90+
def title(self):
91+
return 'Filesystem'
92+
93+
def formatted(self, statvfs, dev_name, dir):
94+
return '{:s}@{:s}'.format(dir[:-1], dev_name)
95+
# format: /${dir_name}/@${device_name}
96+
# e.g. /flash@pyboard
97+
98+
99+
class DfMountedOn(DfColumn):
100+
101+
def title(self):
102+
return 'Mounted on'
103+
104+
def formatted(self, statvfs, dev_name, dir):
105+
return '/{}{}'.format(dev_name, dir)[:-1]
106+
# format: /${device_name}/${dir_name}
107+
# e.g. /pyboard/flash
108+
109+
110+
class DfNumBlocks(DfNumColumn):
111+
112+
def title(self):
113+
return 'Blocks'
114+
115+
def get_num_value(self, statvfs):
116+
return statvfs[2]
117+
# f_blocks
118+
119+
120+
class DfBlockSize(DfNumColumn):
121+
122+
def title(self):
123+
return 'Block size'
124+
125+
def get_num_value(self, statvfs):
126+
return statvfs[1]
127+
# f_frsize
128+
129+
130+
class DfUsedBlocks(DfNumColumn):
131+
132+
def title(self):
133+
return 'Used'
134+
135+
def get_num_value(self, statvfs):
136+
return statvfs[2] - statvfs[3]
137+
# f_blocks - f_used
138+
139+
140+
class DfAvailBlocks(DfNumColumn):
141+
142+
def title(self):
143+
return 'Available'
144+
145+
def get_num_value(self, statvfs):
146+
return statvfs[4]
147+
# f_bavail
148+
149+
150+
class DfCapacityBlocks(DfNumColumn):
151+
152+
def __init__(self):
153+
super().__init__('{:.0f}%')
154+
155+
def title(self):
156+
return 'Capacity'
157+
158+
def get_num_value(self, statvfs):
159+
return 100 * (statvfs[2] - statvfs[3]) / statvfs[2] if statvfs[2] > 0 else 0
160+
# 100 * (f_blocks - f_used) / f_blocks
161+
# or 0 if 0 blocks
162+
163+
164+
class DfSizeBytes(DfByteColumn):
165+
166+
def __init__(self, byte_format):
167+
super().__init__(byte_format)
168+
169+
def title(self):
170+
return 'Size'
171+
172+
def get_num_value(self, statvfs):
173+
return statvfs[1] * statvfs[2]
174+
# f_frsize * f_blocks
175+
176+
177+
class DfUsedBytes(DfByteColumn):
178+
179+
def __init__(self, byte_format):
180+
super().__init__(byte_format)
181+
182+
def title(self):
183+
return 'Used'
184+
185+
def get_num_value(self, statvfs):
186+
return statvfs[1] * (statvfs[2] - statvfs[3])
187+
# f_frsize * (f_blocks - f_used)
188+
189+
190+
class DfAvailBytes(DfByteColumn):
191+
192+
def __init__(self, byte_format):
193+
super().__init__(byte_format)
194+
195+
def title(self):
196+
return 'Available'
197+
198+
def get_num_value(self, statvfs):
199+
return statvfs[1] * statvfs[4]
200+
# f_frsize * f_bavail
201+
202+
class DfCapacityBytes(DfNumColumn):
203+
204+
def __init__(self):
205+
super().__init__('{:.0f}%')
206+
207+
def title(self):
208+
return 'Capacity'
209+
210+
def get_num_value(self, statvfs):
211+
return 100 * (statvfs[2] - statvfs[3]) / statvfs[2] if statvfs[2] > 0 else 0
212+
# 100 * (f_blocks - f_used) / f_blocks
213+
# or 0 if 0 blocks
214+
215+
216+
def create_byte_sizes_columns(byte_format):
217+
"""Returns standard set of columns for df command output
218+
in bytes in different formats
219+
"""
220+
return [
221+
DfFilesystem(),
222+
DfSizeBytes(byte_format),
223+
DfUsedBytes(byte_format),
224+
DfAvailBytes(byte_format),
225+
DfCapacityBytes(),
226+
DfMountedOn(),
227+
]
228+
229+
230+
def create_block_sizes_columns():
231+
"""Returns standard set of columns for df command output
232+
in blocks
233+
"""
234+
return [
235+
DfFilesystem(),
236+
DfBlockSize(),
237+
DfNumBlocks(),
238+
DfUsedBlocks(),
239+
DfAvailBlocks(),
240+
DfCapacityBlocks(),
241+
DfMountedOn(),
242+
]

rshell/main.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import sys
2222
try:
23+
import rshell.dfutils as dfutils
2324
from rshell.getch import getch
2425
from rshell.pyboard import Pyboard, PyboardError
2526
from rshell.version import __version__
@@ -713,6 +714,15 @@ def eval_str(string):
713714
return output
714715

715716

717+
def get_vfs_stats(filename):
718+
"""Returns filesystem statistics."""
719+
import os
720+
try:
721+
return os.statvfs(filename)
722+
except OSError:
723+
return -1
724+
725+
716726
def get_filesize(filename):
717727
"""Returns the size of a file, in bytes."""
718728
import os
@@ -2006,7 +2016,8 @@ def create_argparser(self, command):
20062016
parser = argparse.ArgumentParser(
20072017
prog=command,
20082018
usage='\n'.join(usage),
2009-
description='\n'.join(description)
2019+
description='\n'.join(description),
2020+
conflict_handler='resolve'
20102021
)
20112022
for args, kwargs in argparse_args:
20122023
parser.add_argument(*args, **kwargs)
@@ -2581,6 +2592,70 @@ def do_ls(self, line):
25812592
if len(files) > 0:
25822593
print_cols(sorted(files), self.print, self.columns)
25832594

2595+
argparse_df = (
2596+
add_arg(
2597+
'-b', '--bytes',
2598+
dest='byte_sizes',
2599+
action='store_true',
2600+
help='Prints sizes in bytes',
2601+
default=False
2602+
),
2603+
add_arg(
2604+
'-h', '--human-readable',
2605+
dest='human_readable',
2606+
action='store_true',
2607+
help='Prints sizes in a human-readable format using power of 1024',
2608+
default=False
2609+
),
2610+
add_arg(
2611+
'-H', '--si',
2612+
dest='human_readable_si',
2613+
action='store_true',
2614+
help='Prints sizes in a human-readable format using power of 1000',
2615+
default=False
2616+
),
2617+
)
2618+
2619+
def complete_df(self, text, line, begidx, endidx):
2620+
return self.filename_complete(text, line, begidx, endidx)
2621+
2622+
2623+
def do_df(self, line):
2624+
"""df [-b|-h|-H]
2625+
2626+
Report file system space usage
2627+
"""
2628+
args = self.line_to_args(line)
2629+
2630+
if args.byte_sizes:
2631+
columns = dfutils.create_byte_sizes_columns(dfutils.DByteFormat.BYTES)
2632+
elif args.human_readable:
2633+
columns = dfutils.create_byte_sizes_columns(dfutils.DByteFormat.HUMAN)
2634+
elif args.human_readable_si:
2635+
columns = dfutils.create_byte_sizes_columns(dfutils.DByteFormat.HUMAN_SI)
2636+
else:
2637+
columns = dfutils.create_block_sizes_columns()
2638+
2639+
table = []
2640+
widths = [len(col.title()) for col in columns]
2641+
with DEV_LOCK:
2642+
for dev in DEVS:
2643+
for dir in dev.root_dirs:
2644+
stats = dev.remote_eval(get_vfs_stats, dir)
2645+
row = [col.formatted(stats, dev.name, dir) for col in columns]
2646+
table.append(row)
2647+
widths = [max(len(val), widths[i]) for i, val in enumerate(row)]
2648+
col_formatters = []
2649+
for i, width in enumerate(widths):
2650+
# first and last row should be aligned to the left, others to the right
2651+
alignment = '<' if i == 0 or i == len(widths) - 1 else '>'
2652+
col_formatters.append('{:' + alignment + str(width) + 's}')
2653+
row_f = " ".join(col_formatters)
2654+
print(row_f.format(*[col.title() for col in columns]))
2655+
for row in table:
2656+
print(row_f.format(*row))
2657+
2658+
25842659
def complete_mkdir(self, text, line, begidx, endidx):
25852660
return self.filename_complete(text, line, begidx, endidx)
25862661

0 commit comments

Comments
 (0)