1 /* df.c - report free disk space.
2 *
3 * Copyright 2006 Rob Landley <rob@landley.net>
4 *
5 * See http://opengroup.org/onlinepubs/9699919799/utilities/df.html
6
7 USE_DF(NEWTOY(df, "HPkhit*a[-HPkh]", TOYFLAG_SBIN))
8
9 config DF
10 bool "df"
11 default y
12 help
13 usage: df [-HPkhi] [-t type] [FILESYSTEM ...]
14
15 The "disk free" command shows total/used/available disk space for
16 each filesystem listed on the command line, or all currently mounted
17 filesystems.
18
19 -a Show all (including /proc and friends)
20 -P The SUSv3 "Pedantic" option
21 -k Sets units back to 1024 bytes (the default without -P)
22 -h Human readable (K=1024)
23 -H Human readable (k=1000)
24 -i Show inodes instead of blocks
25 -t type Display only filesystems of this type
26
27 Pedantic provides a slightly less useful output format dictated by Posix,
28 and sets the units to 512 bytes instead of the default 1024 bytes.
29 */
30
31 #define FOR_df
32 #include "toys.h"
33
GLOBALS(struct arg_list * t;long units;int column_widths[5];int header_shown;)34 GLOBALS(
35 struct arg_list *t;
36
37 long units;
38 int column_widths[5];
39 int header_shown;
40 )
41
42 static void measure_column(int col, const char *s)
43 {
44 size_t len = strlen(s);
45
46 if (TT.column_widths[col] < len) TT.column_widths[col] = len;
47 }
48
measure_numeric_column(int col,long long n)49 static void measure_numeric_column(int col, long long n)
50 {
51 snprintf(toybuf, sizeof(toybuf), "%llu", n);
52 return measure_column(col, toybuf);
53 }
54
show_header()55 static void show_header()
56 {
57 TT.header_shown = 1;
58
59 // The filesystem column is always at least this wide.
60 if (TT.column_widths[0] < 14) TT.column_widths[0] = 14;
61
62 if ((toys.optflags & (FLAG_H|FLAG_h))) {
63 xprintf((toys.optflags&FLAG_i) ?
64 "%-*sInodes IUsed IFree IUse%% Mounted on\n" :
65 "%-*s Size Used Avail Use%% Mounted on\n",
66 TT.column_widths[0], "Filesystem");
67 } else {
68 const char *item_label, *used_label, *free_label, *use_label;
69
70 if (toys.optflags & FLAG_i) {
71 item_label = "Inodes";
72 used_label = "IUsed";
73 free_label = "IFree";
74 use_label = "IUse%";
75 } else {
76 item_label = TT.units == 512 ? "512-blocks" : "1K-blocks";
77 used_label = "Used";
78 free_label = "Available";
79 use_label = toys.optflags & FLAG_P ? "Capacity" : "Use%";
80 }
81
82 measure_column(1, item_label);
83 measure_column(2, used_label);
84 measure_column(3, free_label);
85 measure_column(4, use_label);
86 xprintf("%-*s %*s %*s %*s %*s Mounted on\n",
87 TT.column_widths[0], "Filesystem",
88 TT.column_widths[1], item_label,
89 TT.column_widths[2], used_label,
90 TT.column_widths[3], free_label,
91 TT.column_widths[4], use_label);
92
93 // For the "Use%" column, the trailing % should be inside the column.
94 TT.column_widths[4]--;
95 }
96 }
97
show_mt(struct mtab_list * mt,int measuring)98 static void show_mt(struct mtab_list *mt, int measuring)
99 {
100 unsigned long long size, used, avail, percent, block;
101 char *device;
102
103 // Return if it wasn't found (should never happen, but with /etc/mtab...)
104 if (!mt) return;
105
106 // If we have -t, skip other filesystem types
107 if (TT.t) {
108 struct arg_list *al;
109
110 for (al = TT.t; al; al = al->next)
111 if (!strcmp(mt->type, al->arg)) break;
112
113 if (!al) return;
114 }
115
116 // If we don't have -a, skip synthetic filesystems
117 if (!(toys.optflags & FLAG_a) && !mt->statvfs.f_blocks) return;
118
119 // Figure out how much total/used/free space this filesystem has,
120 // forcing 64-bit math because filesystems are big now.
121 if (toys.optflags & FLAG_i) {
122 size = mt->statvfs.f_files;
123 used = mt->statvfs.f_files - mt->statvfs.f_ffree;
124 avail = getuid() ? mt->statvfs.f_favail : mt->statvfs.f_ffree;
125 } else {
126 block = mt->statvfs.f_bsize ? mt->statvfs.f_bsize : 1;
127 size = (block * mt->statvfs.f_blocks) / TT.units;
128 used = (block * (mt->statvfs.f_blocks-mt->statvfs.f_bfree)) / TT.units;
129 avail= (block*(getuid()?mt->statvfs.f_bavail:mt->statvfs.f_bfree))/TT.units;
130 }
131 if (!(used+avail)) percent = 0;
132 else {
133 percent = (used*100)/(used+avail);
134 if (used*100 != percent*(used+avail)) percent++;
135 }
136
137 device = *mt->device == '/' ? realpath(mt->device, NULL) : NULL;
138 if (!device) device = mt->device;
139
140 if (measuring) {
141 measure_column(0, device);
142 measure_numeric_column(1, size);
143 measure_numeric_column(2, used);
144 measure_numeric_column(3, avail);
145 } else {
146 if (!TT.header_shown) show_header();
147
148 if (toys.optflags & (FLAG_H|FLAG_h)) {
149 char *size_str = toybuf, *used_str = toybuf+64, *avail_str = toybuf+128;
150 int hr_flags = (toys.optflags & FLAG_H) ? HR_1000 : 0;
151 int w = 4 + !!(toys.optflags & FLAG_i);
152
153 human_readable(size_str, size, hr_flags);
154 human_readable(used_str, used, hr_flags);
155 human_readable(avail_str, avail, hr_flags);
156 xprintf("%-*s %*s %*s %*s %*llu%% %s\n",
157 TT.column_widths[0], device,
158 w, size_str, w, used_str, w, avail_str, w-1, percent, mt->dir);
159 } else xprintf("%-*s %*llu %*llu %*llu %*llu%% %s\n",
160 TT.column_widths[0], device,
161 TT.column_widths[1], size,
162 TT.column_widths[2], used,
163 TT.column_widths[3], avail,
164 TT.column_widths[4], percent,
165 mt->dir);
166 }
167
168 if (device != mt->device) free(device);
169 }
170
df_main(void)171 void df_main(void)
172 {
173 struct mtab_list *mt, *mtstart, *mtend;
174 int measuring;
175
176 if (toys.optflags & (FLAG_H|FLAG_h)) {
177 TT.units = 1;
178 } else {
179 // Units are 512 bytes if you select "pedantic" without "kilobytes".
180 TT.units = toys.optflags & FLAG_P ? 512 : 1024;
181 }
182
183 if (!(mtstart = xgetmountlist(0))) return;
184 mtend = dlist_terminate(mtstart);
185
186 // If we have a list of filesystems on the command line, loop through them.
187 if (*toys.optargs) {
188 // Measure the names then output the table.
189 for (measuring = 1; measuring >= 0; --measuring) {
190 char **next;
191
192 for (next = toys.optargs; *next; next++) {
193 struct stat st;
194
195 // Stat it (complain if we can't).
196 if (stat(*next, &st)) {
197 perror_msg("'%s'", *next);
198 continue;
199 }
200
201 // Find and display this filesystem. Use _last_ hit in case of
202 // overmounts (which is first hit in the reversed list).
203 for (mt = mtend; mt; mt = mt->prev) {
204 if (st.st_dev == mt->stat.st_dev
205 || (st.st_rdev && (st.st_rdev == mt->stat.st_dev)))
206 {
207 show_mt(mt, measuring);
208 break;
209 }
210 }
211 }
212 }
213 } else {
214 // Loop through mount list to filter out overmounts.
215 for (mt = mtend; mt; mt = mt->prev) {
216 struct mtab_list *mt2, *mt3;
217
218 // 0:0 is LANANA null device
219 if (!mt->stat.st_dev) continue;
220
221 // Filter out overmounts.
222 mt3 = mt;
223 for (mt2 = mt->prev; mt2; mt2 = mt2->prev) {
224 if (mt->stat.st_dev == mt2->stat.st_dev) {
225 // For --bind mounts, show earliest mount
226 if (!strcmp(mt->device, mt2->device)) {
227 if (!(toys.optflags & FLAG_a)) mt3->stat.st_dev = 0;
228 mt3 = mt2;
229 } else mt2->stat.st_dev = 0;
230 }
231 }
232 }
233
234 // Measure the names then output the table.
235 for (measuring = 1; measuring >= 0; --measuring) {
236 // Cosmetic: show filesystems in creation order.
237 for (mt = mtstart; mt; mt = mt->next) {
238 if (mt->stat.st_dev) show_mt(mt, measuring);
239 }
240 }
241 }
242
243 if (CFG_TOYBOX_FREE) llist_traverse(mtstart, free);
244 }
245