diff --git a/docs/usage/commands/du.md b/docs/usage/commands/du.md new file mode 100644 index 0000000..f48dfcd --- /dev/null +++ b/docs/usage/commands/du.md @@ -0,0 +1,90 @@ +# du + +`du` (disk usage) reports the disk space used by files and directories. + +## Usage + +```sh +du [OPTIONS]... [FILE]... +``` + +## Description + +By default, `du` prints human-readable sizes for each file and directory it encounters, starting from the current directory (`.`) if no path is given. + +## Options + +| Option | Description | +| :--- | :--- | +| `-s, --summarize` | Show only a total for each argument, suppressing per-entry output. | +| `-a, --all` | Write counts for all files, not just directories. | +| `-d, --max-depth=N` | Stop at depth N; show only entries at or above depth N. | +| `-c, --total` | Print a grand total after all arguments have been processed. | +| `-b, --bytes` | Print sizes in exact bytes instead of human-readable units. | +| `-H, --human-readable` | Accepted for compatibility; human-readable is the default. | +| `--help` | Display usage information and exit. | + +## Output Format + +Each line shows a size followed by the path: + +``` +SIZE PATH +``` + +Sizes are formatted as `B`, `KB`, `MB`, or `GB` by default, with one decimal place when appropriate (e.g., `1.5 GB`). The `-b` option overrides this to show exact byte counts. + +## Examples + +Show disk usage for the current directory: + +```sh +du +``` + +Show disk usage for a specific path: + +```sh +du /bin +``` + +Show only totals per argument (`-s`): + +```sh +du -s /bin /home +``` + +Show all files and directories recursively (`-a`): + +```sh +du -a /bin +``` + +Limit output to depth 1 (`-d`): + +```sh +du -d 1 / +``` + +Print a grand total after processing (`-c`): + +```sh +du -c /bin /home +``` + +Show exact byte counts (`-b`): + +```sh +du -b /bin +``` + +## How It Works + +`du` uses `sys_get_file_info()` to read file sizes and `sys_list()` to enumerate directory contents recursively. The command skips the synthetic `.` and `..` entries and continues processing remaining paths if one path is inaccessible, printing an error for the failed path. + +The size reported is the **apparent file size** (the logical size stored in the directory entry), not the allocated disk blocks. This is consistent with how BoredOS reports file sizes through the filesystem API. + +## Exit Status + +- `0`: Success +- `1`: One or more paths could not be accessed or listed \ No newline at end of file diff --git a/docs/usage/terminal.md b/docs/usage/terminal.md index 4b806c2..3ee2b96 100644 --- a/docs/usage/terminal.md +++ b/docs/usage/terminal.md @@ -54,6 +54,7 @@ Below are some of the most used commands available in `/bin`: | `mkdir` | Create a new directory. | | `man` | View the manual for a specific command (e.g., `man ls`). | | `lsblk` | List block devices and partitions with size, type, filesystem, label, and flags. | +| `du` | Report disk usage for files and directories, recursively. | | `sysfetch` | Display system and hardware information. | diff --git a/src/userland/cli/du.c b/src/userland/cli/du.c new file mode 100644 index 0000000..744724e --- /dev/null +++ b/src/userland/cli/du.c @@ -0,0 +1,203 @@ +// Copyright (c) 2026 zeyadhost (https://github.com/zeyadhost) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#include +#include +#include +#include +#include + +#define MAX_ENTRIES 1024 +#define DU_KB 1024ULL +#define DU_MB (1024ULL * 1024ULL) +#define DU_GB (1024ULL * 1024ULL * 1024ULL) + +static int opt_summarize = 0; +static int opt_all = 0; +static int opt_max_depth = -1; +static int opt_total = 0; +static int opt_bytes = 0; + +static uint64_t grand_total = 0; + +static void usage(void) { + printf("Usage: du [options]..[file]\n"); + printf("Summarize disk usage of the set of FILEs, recursively for directories.\n\n"); + printf("Options:\n"); + printf(" -s, --summarize display only a total for each argument\n"); + printf(" -a, --all write counts for all files, not just directories\n"); + printf(" -d, --max-depth=N print the total for a directory only if it is N or\n"); + printf(" fewer levels below the command line argument\n"); + printf(" -c, --total produce a grand total\n"); + printf(" -b, --bytes print sizes in bytes\n"); + printf(" -H, --human-readable print sizes in human readable format (default)\n"); + printf(" --help display this help and exit\n"); +} + +static void print_size(uint64_t bytes, const char *path) { + if (opt_bytes) { + printf("%llu\t%s\n", (unsigned long long)bytes, path); + return; + } + + char size_str[32]; + uint64_t unit = 1; + const char *suffix = "B"; + + if (bytes >= DU_GB) { + unit = DU_GB; + suffix = "GB"; + } else if (bytes >= DU_MB) { + unit = DU_MB; + suffix = "MB"; + } else if (bytes >= DU_KB) { + unit = DU_KB; + suffix = "KB"; + } + + if (unit == 1) { + snprintf(size_str, sizeof(size_str), "%llu%s", (unsigned long long)bytes, suffix); + } else { + // Round to one decimal place + uint64_t whole = bytes / unit; + uint64_t rem = bytes % unit; + uint64_t tenth = (rem * 10ULL + unit / 2ULL) / unit; + + if (tenth >= 10ULL) { + whole++; + tenth = 0; + } + + if (tenth == 0) { + snprintf(size_str, sizeof(size_str), "%llu%s", (unsigned long long)whole, suffix); + } else { + snprintf(size_str, sizeof(size_str), "%llu.%llu%s", (unsigned long long)whole, (unsigned long long)tenth, suffix); + } + } + printf("%s\t%s\n", size_str, path); +} + +static void join_path(char *dest, size_t size, const char *p1, const char *p2) { + if (strcmp(p1, "/") == 0) { + snprintf(dest, size, "/%s", p2); + } else if (p1[strlen(p1) - 1] == '/') { + snprintf(dest, size, "%s%s", p1, p2); + } else { + snprintf(dest, size, "%s/%s", p1, p2); + } +} + +static uint64_t do_du(const char *path, int depth) { + FAT32_FileInfo info; + if (sys_get_file_info(path, &info) < 0) { + printf("du: cannot access '%s'\n", path); + return 0; + } + + if (!info.is_directory) { + if (opt_all || (depth == 0)) { + if (opt_max_depth == -1 || depth <= opt_max_depth) { + if (!opt_summarize || depth == 0) { + print_size(info.size, path); + } + } + } + return info.size; + } + + uint64_t total_size = info.size; + + FAT32_FileInfo *entries = malloc(sizeof(FAT32_FileInfo) * MAX_ENTRIES); + if (!entries) { + printf("du: out of memory for '%s'\n", path); + return total_size; + } + + int count = sys_list(path, entries, MAX_ENTRIES); + if (count < 0) { + printf("du: cannot read directory '%s'\n", path); + free(entries); + return total_size; + } + + // Recurse into subdirectories + for (int i = 0; i < count; i++) { + if (strcmp(entries[i].name, ".") == 0 || strcmp(entries[i].name, "..") == 0) { + continue; + } + + char child_path[1024]; + join_path(child_path, sizeof(child_path), path, entries[i].name); + + total_size += do_du(child_path, depth + 1); + } + + free(entries); + + // Print directory size at this depth + if (!opt_summarize) { + if (opt_max_depth == -1 || depth <= opt_max_depth) { + print_size(total_size, path); + } + } else if (depth == 0) { + // With -s, only print the root of each requested path + print_size(total_size, path); + } + + return total_size; +} + +int main(int argc, char **argv) { + char **paths = malloc(sizeof(char*) * argc); + int num_paths = 0; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--summarize") == 0) { + opt_summarize = 1; + } else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--all") == 0) { + opt_all = 1; + } else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--total") == 0) { + opt_total = 1; + } else if (strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--bytes") == 0) { + opt_bytes = 1; + } else if (strcmp(argv[i], "-H") == 0 || strcmp(argv[i], "--human-readable") == 0) { + // No-op: human-readable is the default + } else if (strcmp(argv[i], "-d") == 0) { + if (i + 1 < argc) { + opt_max_depth = atoi(argv[++i]); + } else { + printf("du: option requires an argument -- '-d'\n"); + return 1; + } + } else if (strncmp(argv[i], "--max-depth=", 12) == 0) { + opt_max_depth = atoi(argv[i] + 12); + } else if (strcmp(argv[i], "--help") == 0) { + usage(); + free(paths); + return 0; + } else if (argv[i][0] == '-') { + printf("du: invalid option -- '%s'\n", argv[i]); + usage(); + free(paths); + return 1; + } else { + paths[num_paths++] = argv[i]; + } + } + + if (num_paths == 0) { + grand_total += do_du(".", 0); + } else { + for (int i = 0; i < num_paths; i++) { + grand_total += do_du(paths[i], 0); + } + } + + if (opt_total) { + print_size(grand_total, "total"); + } + + free(paths); + return 0; +}