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
7USE_DF(NEWTOY(df, "HPkhit*a[-HPkh]", TOYFLAG_SBIN))
8
9config 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 output (K=1024)
23    -H	Human readable output (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
34GLOBALS(
35  struct arg_list *fstype;
36
37  long units;
38  int column_widths[5];
39  int header_shown;
40)
41
42static 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
49static 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
55static 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
98static 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.fstype) {
108    struct arg_list *al;
109
110    for (al = TT.fstype; 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
171void 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