1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <dirent.h>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <stdalign.h>
9 #include <stdarg.h>
10 #include <stdbool.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <unistd.h>
16 
17 #include <fbl/unique_fd.h>
18 #include <fuchsia/io/c/fidl.h>
19 #include <lib/fzl/fdio.h>
20 #include <zircon/device/vfs.h>
21 
22 #include <utility>
23 
usage(void)24 int usage(void) {
25     fprintf(stderr, "usage: df [ <option>* ] [paths]\n");
26     fprintf(stderr, "df displays the mounted filesystems for a list of paths\n");
27     fprintf(stderr, " -i : List inode information instead of block usage\n");
28     fprintf(stderr, " -h : Show sizes in human readable format (e.g., 1K 2M 3G)\n");
29     fprintf(stderr, " --help : Show this help message\n");
30     return -1;
31 }
32 
33 typedef struct {
34     bool node_usage;
35     bool human_readable;
36 } df_options_t;
37 
38 const char* root = "/";
39 
parse_args(int argc,const char ** argv,df_options_t * options,const char *** dirs,size_t * count)40 int parse_args(int argc, const char** argv, df_options_t* options, const char*** dirs, size_t* count) {
41     while (argc > 1) {
42         if (!strcmp(argv[1], "-i")) {
43             options->node_usage = true;
44         } else if (!strcmp(argv[1], "-h")) {
45             options->human_readable = true;
46         } else if (!strcmp(argv[1], "--help")) {
47             return usage();
48         } else {
49             break;
50         }
51         argc--;
52         argv++;
53     }
54     if (argc >= 2) {
55         *dirs = &argv[1];
56         *count = argc - 1;
57     } else {
58         *dirs = &root;
59         *count = 1;
60     }
61     return 0;
62 }
63 
64 // Format for the header
65 const char* hfmt = "%-10s %10s %10s %10s %3s%%  %-10s  %-10s\n";
66 // Format for the human-readable header
67 const char* hrfmt = "%-10s %5s %5s %5s %5s%%  %-10s  %-10s\n";
68 // Format for the individual filesystems queried
69 const char* ffmt = "%-10s %10zu %10zu %10zu %3zu%%  %-10s  %-10s\n";
70 
71 #define KB (1lu << 10)
72 #define MB (1lu << 20)
73 #define GB (1lu << 30)
74 #define TB (1lu << 40)
75 #define PB (1lu << 50)
76 #define EB (1lu << 60)
77 
78 // Conditionally print the size if it falls within the range of the magnitude.
79 // [1.0XX, 999XX]
print_magnitude(int padding,size_t size,size_t magnitude,const char * mag_string)80 bool print_magnitude(int padding, size_t size, size_t magnitude, const char* mag_string) {
81     if (size < 10 * magnitude) {
82         printf("%*zu.%zu%s ", padding - 4, size / magnitude,
83                size / (magnitude / 10) % 10, mag_string);
84         return true;
85     } else if (size < magnitude << 10) {
86         printf("%*zu%s ", padding - 2, size / magnitude, mag_string);
87         return true;
88     }
89     return false;
90 }
91 
print_human_readable(int padding,size_t size)92 void print_human_readable(int padding, size_t size) {
93     if (size < KB) {
94         printf("%*s ", padding, "0");
95     } else if (print_magnitude(padding, size, KB, "KB")) {
96     } else if (print_magnitude(padding, size, MB, "MB")) {
97     } else if (print_magnitude(padding, size, GB, "GB")) {
98     } else if (print_magnitude(padding, size, TB, "TB")) {
99     } else if (print_magnitude(padding, size, PB, "PB")) {
100     } else {
101         printf("%*zu ", padding, size);
102     }
103 }
104 
print_fs_type(const char * name,const df_options_t * options,const fuchsia_io_FilesystemInfo * info,const char * device_path)105 void print_fs_type(const char* name, const df_options_t* options,
106                    const fuchsia_io_FilesystemInfo* info, const char* device_path) {
107     if (options->node_usage) {
108         size_t nodes_total = info ? info->total_nodes : 0;
109         size_t nodes_used = info ? info->used_nodes : 0;
110         size_t nodes_available = nodes_total - nodes_used;
111         size_t use_percentage = nodes_total ? nodes_used * 100 / nodes_total : 0;
112         printf(ffmt,
113                info != nullptr ? reinterpret_cast<const char*>(info->name) : "?",
114                nodes_total,
115                nodes_used,
116                nodes_available,
117                use_percentage,
118                name,
119                device_path ? device_path : "none");
120     } else {
121         // Block Usage
122         if (options->human_readable) {
123             size_t bytes_total = info ? info->total_bytes: 0;
124             size_t bytes_used = info ? info->used_bytes : 0;
125             size_t bytes_available = bytes_total - bytes_used;
126             size_t use_percentage = bytes_total ? bytes_used * 100 / bytes_total : 0;
127             printf("%-10s ", info != nullptr ? reinterpret_cast<const char*>(info->name) : "?");
128             print_human_readable(5, bytes_total);
129             print_human_readable(5, bytes_used);
130             print_human_readable(5, bytes_available);
131             printf("%5zu%%  ", use_percentage);
132             printf("%-10s  ", name);
133             printf("%-10s\n", device_path ? device_path : "none");
134         } else {
135             size_t blocks_total = info ? info->total_bytes >> 10 : 0;
136             size_t blocks_used = info ? info->used_bytes >> 10 : 0;
137             size_t blocks_available = blocks_total - blocks_used;
138             size_t use_percentage = blocks_total ? blocks_used * 100 / blocks_total : 0;
139             printf(ffmt,
140                    info != nullptr ? reinterpret_cast<const char*>(info->name) : "?",
141                    blocks_total,
142                    blocks_used,
143                    blocks_available,
144                    use_percentage,
145                    name,
146                    device_path ? device_path : "none");
147         }
148     }
149 
150 }
151 
main(int argc,const char ** argv)152 int main(int argc, const char** argv) {
153     const char** dirs;
154     size_t dircount;
155     df_options_t options;
156     memset(&options, 0, sizeof(df_options_t));
157     int r;
158     if ((r = parse_args(argc, argv, &options, &dirs, &dircount))) {
159         return r;
160     }
161 
162     if (options.node_usage) {
163         printf(hfmt, "Filesystem", "Inodes", "IUsed", "IFree", "IUse",
164                "Path", "Device");
165     } else {
166         if (options.human_readable) {
167             printf(hrfmt, "Filesystem", "Size", "Used", "Avail", "Use",
168                    "Path", "Device");
169         } else {
170             printf(hfmt, "Filesystem", "1K-Blocks", "Used", "Available", "Use",
171                    "Path", "Device");
172         }
173     }
174 
175     // Try to open path with O_ADMIN so we can query for underlying block devices.
176     // If we fail, open directory without O_ADMIN. Block devices will not be returned.
177     for (size_t i = 0; i < dircount; i++) {
178         fbl::unique_fd fd;
179         bool admin = true;
180         fd.reset(open(dirs[i], O_RDONLY | O_ADMIN));
181         if (!fd) {
182             fd.reset(open(dirs[i], O_RDONLY));
183             if (!fd) {
184                 fprintf(stderr, "df: Could not open target: %s\n", dirs[i]);
185                 continue;
186             }
187             admin = false;
188         }
189 
190         fuchsia_io_FilesystemInfo info;
191         zx_status_t status;
192         fzl::FdioCaller caller(std::move(fd));
193         zx_status_t io_status = fuchsia_io_DirectoryAdminQueryFilesystem(caller.borrow_channel(),
194                                                                          &status, &info);
195         if (io_status != ZX_OK || status != ZX_OK) {
196             print_fs_type(dirs[i], &options, nullptr, "Unknown; cannot query filesystem");
197             continue;
198         }
199         info.name[fuchsia_io_MAX_FS_NAME_BUFFER - 1] = '\0';
200 
201         char device_buffer[1024];
202         char* device_path = static_cast<char*>(device_buffer);
203         size_t path_len;
204         io_status = fuchsia_io_DirectoryAdminGetDevicePath(caller.borrow_channel(), &status,
205                                                            device_path, sizeof(device_buffer) - 1,
206                                                            &path_len);
207         const char* path = nullptr;
208         if (io_status == ZX_OK) {
209             if (status == ZX_OK) {
210                 device_buffer[path_len] = '\0';
211                 path = device_path;
212             } else if (!admin && status == ZX_ERR_ACCESS_DENIED) {
213                 path = "Unknown; missing O_ADMIN";
214             }
215         }
216 
217         print_fs_type(dirs[i], &options, &info, path);
218     }
219 
220     return 0;
221 }
222