| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fidl/fuchsia.fshost/cpp/wire.h> |
| #include <fidl/fuchsia.io/cpp/wire.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <stdalign.h> |
| #include <stdarg.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <string> |
| #include <utility> |
| |
| #include <fbl/unique_fd.h> |
| |
| #include "src/storage/fshost/admin-client.h" |
| |
| namespace fio = fuchsia_io; |
| |
| int usage(void) { |
| fprintf(stderr, "usage: df [ <option>* ] [paths]\n"); |
| fprintf(stderr, "df displays the mounted filesystems for a list of paths\n"); |
| fprintf(stderr, " -i : List inode information instead of block usage\n"); |
| fprintf(stderr, " -h : Show sizes in human readable format (e.g., 1K 2M 3G)\n"); |
| fprintf(stderr, " --help : Show this help message\n"); |
| return -1; |
| } |
| |
| typedef struct { |
| bool node_usage; |
| bool human_readable; |
| } df_options_t; |
| |
| const char* root = "/"; |
| |
| int parse_args(int argc, const char** argv, df_options_t* options, const char*** dirs, |
| size_t* count) { |
| while (argc > 1) { |
| if (!strcmp(argv[1], "-i")) { |
| options->node_usage = true; |
| } else if (!strcmp(argv[1], "-h")) { |
| options->human_readable = true; |
| } else if (!strcmp(argv[1], "--help")) { |
| return usage(); |
| } else { |
| break; |
| } |
| argc--; |
| argv++; |
| } |
| if (argc >= 2) { |
| *dirs = &argv[1]; |
| *count = argc - 1; |
| } else { |
| printf("(hint: to see filesystems in root, use 'df *' or 'df <path>')\n"); |
| *dirs = &root; |
| *count = 1; |
| } |
| return 0; |
| } |
| |
| // Format for the header |
| const char* hfmt = "%-10s %10s %10s %10s %3s%% %-10s %-10s\n"; |
| // Format for the human-readable header |
| const char* hrfmt = "%-10s %5s %5s %5s %5s%% %-10s %-10s\n"; |
| // Format for the individual filesystems queried |
| const char* ffmt = "%-10s %10zu %10zu %10zu %3zu%% %-10s %-10s\n"; |
| |
| #define KB (1lu << 10) |
| #define MB (1lu << 20) |
| #define GB (1lu << 30) |
| #define TB (1lu << 40) |
| #define PB (1lu << 50) |
| #define EB (1lu << 60) |
| |
| // Conditionally print the size if it falls within the range of the magnitude. |
| // [1.0XX, 999XX] |
| bool print_magnitude(int padding, size_t size, size_t magnitude, const char* mag_string) { |
| if (size < 10 * magnitude) { |
| printf("%*zu.%zu%s ", padding - 4, size / magnitude, size / (magnitude / 10) % 10, mag_string); |
| return true; |
| } else if (size < magnitude << 10) { |
| printf("%*zu%s ", padding - 2, size / magnitude, mag_string); |
| return true; |
| } |
| return false; |
| } |
| |
| void print_human_readable(int padding, size_t size) { |
| if (size < KB) { |
| printf("%*s ", padding, "0"); |
| } else if (print_magnitude(padding, size, KB, "KB")) { |
| } else if (print_magnitude(padding, size, MB, "MB")) { |
| } else if (print_magnitude(padding, size, GB, "GB")) { |
| } else if (print_magnitude(padding, size, TB, "TB")) { |
| } else if (print_magnitude(padding, size, PB, "PB")) { |
| } else { |
| printf("%*zu ", padding, size); |
| } |
| } |
| |
| void print_fs_type(const char* name, const df_options_t* options, |
| const fuchsia_io::wire::FilesystemInfo* info, const char* device_path) { |
| if (options->node_usage) { |
| size_t nodes_total = info ? info->total_nodes : 0; |
| size_t nodes_used = info ? info->used_nodes : 0; |
| size_t nodes_available = nodes_total - nodes_used; |
| size_t use_percentage = nodes_total ? nodes_used * 100 / nodes_total : 0; |
| printf(ffmt, info != nullptr ? reinterpret_cast<const char*>(info->name.data()) : "?", |
| nodes_total, nodes_used, nodes_available, use_percentage, name, device_path); |
| } else { |
| // Block Usage |
| if (options->human_readable) { |
| size_t bytes_total = info ? info->total_bytes : 0; |
| size_t bytes_used = info ? info->used_bytes : 0; |
| size_t bytes_available = bytes_total - bytes_used; |
| size_t use_percentage = bytes_total ? bytes_used * 100 / bytes_total : 0; |
| printf("%-10s ", info != nullptr ? reinterpret_cast<const char*>(info->name.data()) : "?"); |
| print_human_readable(5, bytes_total); |
| print_human_readable(5, bytes_used); |
| print_human_readable(5, bytes_available); |
| printf("%5zu%% ", use_percentage); |
| printf("%-10s ", name); |
| printf("%-10s\n", device_path); |
| } else { |
| size_t blocks_total = info ? info->total_bytes >> 10 : 0; |
| size_t blocks_used = info ? info->used_bytes >> 10 : 0; |
| size_t blocks_available = blocks_total - blocks_used; |
| size_t use_percentage = blocks_total ? blocks_used * 100 / blocks_total : 0; |
| printf(ffmt, info != nullptr ? reinterpret_cast<const char*>(info->name.data()) : "?", |
| blocks_total, blocks_used, blocks_available, use_percentage, name, device_path); |
| } |
| } |
| } |
| |
| int main(int argc, const char** argv) { |
| const char** dirs; |
| size_t dircount; |
| df_options_t options; |
| memset(&options, 0, sizeof(df_options_t)); |
| int r; |
| if ((r = parse_args(argc, argv, &options, &dirs, &dircount))) { |
| return r; |
| } |
| |
| if (options.node_usage) { |
| printf(hfmt, "Filesystem", "Inodes", "IUsed", "IFree", "IUse", "Path", "Device"); |
| } else { |
| if (options.human_readable) { |
| printf(hrfmt, "Filesystem", "Size", "Used", "Avail", "Use", "Path", "Device"); |
| } else { |
| printf(hfmt, "Filesystem", "1K-Blocks", "Used", "Available", "Use", "Path", "Device"); |
| } |
| } |
| |
| auto fshost_or = fshost::ConnectToAdmin(); |
| if (fshost_or.is_error()) { |
| fprintf(stderr, "Error connecting to fshost: %s\n", fshost_or.status_string()); |
| // Continue... |
| } |
| |
| for (size_t i = 0; i < dircount; i++) { |
| fbl::unique_fd fd; |
| fd.reset(open(dirs[i], O_RDONLY)); |
| if (!fd) { |
| fprintf(stderr, "df: Could not open target: %s\n", dirs[i]); |
| continue; |
| } |
| |
| fuchsia_io::wire::FilesystemInfo info; |
| fdio_cpp::FdioCaller caller(std::move(fd)); |
| auto result = |
| fidl::WireCall(fidl::UnownedClientEnd<fuchsia_io::Directory>(caller.borrow_channel())) |
| ->QueryFilesystem(); |
| if (!result.ok() || result.value().s != ZX_OK) { |
| print_fs_type(dirs[i], &options, nullptr, "Unknown; cannot query filesystem"); |
| continue; |
| } |
| info = *result.value().info; |
| info.name[fuchsia_io::wire::kMaxFsNameBuffer - 1] = '\0'; |
| |
| std::string device_path; |
| if (fshost_or.is_ok()) { |
| auto result = fidl::WireCall(*fshost_or)->GetDevicePath(info.fs_id); |
| if (!result.ok()) { |
| fprintf(stderr, "Error getting device path, fidl error: %s\n", |
| result.FormatDescription().c_str()); |
| return EXIT_FAILURE; |
| } |
| if (result->is_error()) |
| device_path = zx_status_get_string(result->error_value()); |
| else |
| device_path = std::string(result->value()->path.get()); |
| } |
| |
| print_fs_type(dirs[i], &options, &info, device_path.c_str()); |
| } |
| |
| return 0; |
| } |