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