├── logo.png ├── screenshot.jpg ├── Makefile ├── .gitignore ├── LICENSE ├── drinfo.1 ├── README.md └── main.c /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lennart1978/drinfo/HEAD/logo.png -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lennart1978/drinfo/HEAD/screenshot.jpg -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -Wall -Wextra -std=c99 -O3 -s 3 | TARGET = drinfo 4 | SOURCE = main.c 5 | 6 | all: $(TARGET) 7 | 8 | $(TARGET): $(SOURCE) 9 | $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE) 10 | 11 | clean: 12 | rm -f $(TARGET) 13 | 14 | install: $(TARGET) 15 | install -Dm755 drinfo /usr/local/bin/drinfo 16 | install -Dm644 drinfo.1 /usr/share/man/man1/drinfo.1 17 | sudo mandb > /dev/null 2>&1 18 | 19 | uninstall: 20 | sudo rm -f /usr/local/bin/$(TARGET) 21 | sudo rm -f /usr/share/man/man1/$(TARGET).1 22 | 23 | .PHONY: all clean install uninstall -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # debug information files 55 | *.dwo 56 | drinfo 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Lennart Martens 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /drinfo.1: -------------------------------------------------------------------------------- 1 | .TH DRINFO 1 "November 2025" "Version 1.2.0" "User Commands" 2 | .SH NAME 3 | drinfo \- display information about drives and their storage usage 4 | .SH SYNOPSIS 5 | .B drinfo 6 | .RI [ OPTION ] 7 | .SH DESCRIPTION 8 | .B drinfo 9 | lists all detected local and network drives and shows mount point, filesystem type, device path, 10 | UUID, label, mount options, used, available and inodes + SMART status (only as root). 11 | .P 12 | It provides a visual representation of disk usage with gradient colored progress bars (green -> yellow -> red). 13 | .SH OPTIONS 14 | .TP 15 | .BR -h , --help 16 | Show help message and exit. 17 | .TP 18 | .BR -v , --version 19 | Show program version and exit. 20 | .TP 21 | .BR -j , --json 22 | Output drive information in JSON format. This is useful for parsing the output in scripts or other programs. 23 | .TP 24 | .BR -n , --no-color 25 | Disable ANSI color codes in the output. Use this option when redirecting output to a file or when running in a terminal that does not support colors. 26 | .TP 27 | .BR -s , --sort \ \fITYPE\fP 28 | Sort the drives by the specified \fITYPE\fP. Available types are: 29 | .RS 30 | .TP 31 | .B size 32 | Sort by total size (descending). This is the default. 33 | .TP 34 | .B usage 35 | Sort by usage percentage (descending). 36 | .TP 37 | .B mount 38 | Sort alphabetically by mount point. 39 | .TP 40 | .B name 41 | Sort alphabetically by device name. 42 | .RE 43 | 44 | .SH AUTHOR 45 | Lennart Martens 46 | 47 | .SH LICENSE 48 | MIT License 49 | 50 | .SH SEE ALSO 51 | .BR df (1), 52 | .BR lsblk (8) 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drinfo - drive info 2 | Version: 1.2.0 3 | 4 | ![logo](logo.png) 5 | 6 | A lightweight command-line tool to monitor disk usage on Linux systems with beautiful colored progress bars. 7 | 8 | ## Features 9 | 10 | - **Physical Drive, Network (cloud) Drive Detection**: List all physical and network drives (also cloud drives) 11 | - **Colorful Progress Bars**: Visual representation of disk usage with gradient colors (green → yellow → red) 12 | - **Human-Readable Sizes**: Displays sizes in B, KB, MB, GB, TB format 13 | - **Terminal Responsive**: Adapts to terminal width for optimal display 14 | - **Detailed Information**: Shows mount point, filesystem type, device path, UUID, label, mount options, used, available and inodes + SMART status (only as root) 15 | - **JSON Output**: Export drive information in JSON format for easy parsing 16 | - **Sorting**: Sort drives by size, usage, mount point, or name 17 | - **No Color Mode**: Disable ANSI colors for scripts or logs 18 | 19 | ## Usage 20 | 21 | ```bash 22 | drinfo [OPTIONS] 23 | ``` 24 | 25 | ### Options 26 | 27 | - `-h, --help`: Show help message 28 | - `-v, --version`: Show program version 29 | - `-j, --json`: Output in JSON format 30 | - `-n, --no-color`: Disable color output 31 | - `-s, --sort TYPE`: Sort drives by TYPE (`size`, `usage`, `mount`, `name`) 32 | 33 | ## Build executable: drinfo 34 | 35 | ```bash 36 | make 37 | ``` 38 | 39 | ## install with man page 40 | 41 | ```bash 42 | sudo make install 43 | ``` 44 | 45 | ## Uninstall 46 | 47 | ```bash 48 | sudo make uninstall 49 | ``` 50 | 51 | ## Archlinux: 52 | 53 | There is "drinfo-git" in the AUR ! 54 | 55 | ## Screenshot 56 | 57 | ![screenshot](screenshot.jpg) 58 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // Constants for terminal and display 20 | #define TERM_FALLBACK_WIDTH 80 21 | #define VERSION "1.2.0" 22 | #define COLOR_BUFFER_SIZE 32 23 | #define MAX_UNITS 5 24 | #define BYTES_PER_KB 1024.0 25 | #define MAX_UNIT_INDEX 4 26 | #define PERCENTAGE_MULTIPLIER 100.0 27 | #define USAGE_PERCENT_DIVISOR 100.0 28 | 29 | // Constants for device path lengths 30 | #define DEV_SD_PREFIX_LEN 7 31 | #define DEV_NVME_PREFIX_LEN 9 32 | #define DEV_HD_PREFIX_LEN 7 33 | #define NETWORK_PATH_PREFIX_LEN 2 34 | #define FUSE_PREFIX_LEN 5 35 | 36 | // Constants for file paths and buffers 37 | #define MAX_PATH_LENGTH 1024 38 | #define MAX_GVFS_PATH_LENGTH 256 39 | #define MAX_SIZE_STR_LENGTH 64 40 | #define MAX_PERCENT_TEXT_LENGTH 16 41 | #define MAX_TEMP_BUFFER_LENGTH 128 42 | #define MAX_BAR_BUFFER_MULTIPLIER 64 43 | #define BARLINE_BUFFER_EXTRA 16 44 | 45 | // Constants for terminal width calculations 46 | #define TERMINAL_WIDTH_PERCENTAGE 4 47 | #define TERMINAL_WIDTH_DIVISOR 5 48 | #define MAX_BOX_WIDTH 120 49 | #define MIN_BOX_WIDTH 40 50 | #define FRAME_PADDING 4 51 | #define BRACKET_PADDING 2 52 | #define MIN_BAR_LENGTH 10 53 | 54 | // Constants for color values 55 | #define MAX_COLOR_VALUE 255 56 | #define COLOR_RATIO_MULTIPLIER 2 57 | #define COLOR_RATIO_HALF 0.5f 58 | #define GRAY_BACKGROUND_R 64 59 | #define GRAY_BACKGROUND_G 64 60 | #define GRAY_BACKGROUND_B 64 61 | #define GRAY_FOREGROUND_R 160 62 | #define GRAY_FOREGROUND_G 160 63 | #define GRAY_FOREGROUND_B 160 64 | #define BLUE_TEXT_R 0 65 | #define BLUE_TEXT_G 0 66 | #define BLUE_TEXT_B 255 67 | 68 | // Constants for string formatting 69 | #define PERCENT_FORMAT "%.1f%%" 70 | #define COLOR_FORMAT "\033[38;2;%d;%d;%dm" 71 | #define BACKGROUND_COLOR_FORMAT "\033[48;2;%d;%d;%dm" 72 | #define BLUE_TEXT_FORMAT "\033[38;2;%d;%d;%dm" 73 | #define RESET_FORMAT "\033[0m" 74 | 75 | // Constants for file system types 76 | #define MOUNT_TABLE_PATH "/proc/mounts" 77 | #define GVFS_BASE_PATH "/run/user/%d/gvfs" 78 | 79 | // Maximum number of drives to handle 80 | #define MAX_DRIVES 100 81 | 82 | // Global options 83 | bool opt_json = false; 84 | bool opt_no_color = false; 85 | enum { SORT_SIZE, SORT_USAGE, SORT_MOUNT, SORT_NAME } opt_sort = SORT_SIZE; 86 | 87 | // Color strings (can be disabled) 88 | const char *c_bold_yellow = "\033[1;33m"; 89 | const char *c_reset = "\033[0m"; 90 | 91 | // Constants for file system types to skip 92 | const char *skip_filesystems[] = { 93 | "proc", "sysfs", "devpts", "tmpfs", "devtmpfs", "securityfs", 94 | "cgroup", "cgroup2", "pstore", "efivarfs", "autofs", "debugfs", 95 | "tracefs", "configfs", "fusectl", "fuse.gvfsd-fuse", "binfmt_misc", 96 | "fuse.portal"}; 97 | 98 | // Structure to hold drive information 99 | typedef struct 100 | { 101 | char mount_point[MAX_PATH_LENGTH]; 102 | char filesystem[MAX_SIZE_STR_LENGTH]; 103 | char device[MAX_PATH_LENGTH]; 104 | char uuid[128]; 105 | char label[128]; 106 | char total_str[MAX_SIZE_STR_LENGTH]; 107 | char used_str[MAX_SIZE_STR_LENGTH]; 108 | char available_str[MAX_SIZE_STR_LENGTH]; 109 | unsigned long long total_bytes; 110 | unsigned long long used_bytes; 111 | unsigned long long available_bytes; 112 | double usage_percent; 113 | const char *drive_type; 114 | char *progress_bar; 115 | bool is_cloud_storage; 116 | char cloud_service_name[MAX_SIZE_STR_LENGTH]; 117 | char mount_options[MAX_TEMP_BUFFER_LENGTH]; 118 | unsigned long long total_inodes; 119 | unsigned long long used_inodes; 120 | double inode_usage; 121 | } drive_info_t; 122 | 123 | char colorbuf[COLOR_BUFFER_SIZE]; // For bar colors 124 | 125 | // Function to compare drives by total capacity (descending order) 126 | int compare_drives_by_capacity(const void *a, const void *b) 127 | { 128 | const drive_info_t *drive_a = (const drive_info_t *)a; 129 | const drive_info_t *drive_b = (const drive_info_t *)b; 130 | 131 | if (drive_b->total_bytes > drive_a->total_bytes) 132 | return 1; 133 | if (drive_b->total_bytes < drive_a->total_bytes) 134 | return -1; 135 | return 0; 136 | } 137 | 138 | // Function to compare drives by usage (descending order) 139 | int compare_drives_by_usage(const void *a, const void *b) 140 | { 141 | const drive_info_t *drive_a = (const drive_info_t *)a; 142 | const drive_info_t *drive_b = (const drive_info_t *)b; 143 | 144 | if (drive_b->usage_percent > drive_a->usage_percent) 145 | return 1; 146 | if (drive_b->usage_percent < drive_a->usage_percent) 147 | return -1; 148 | return 0; 149 | } 150 | 151 | // Function to compare drives by mount point (alphabetical) 152 | int compare_drives_by_mount(const void *a, const void *b) 153 | { 154 | const drive_info_t *drive_a = (const drive_info_t *)a; 155 | const drive_info_t *drive_b = (const drive_info_t *)b; 156 | return strcmp(drive_a->mount_point, drive_b->mount_point); 157 | } 158 | 159 | // Function to compare drives by device name (alphabetical) 160 | int compare_drives_by_name(const void *a, const void *b) 161 | { 162 | const drive_info_t *drive_a = (const drive_info_t *)a; 163 | const drive_info_t *drive_b = (const drive_info_t *)b; 164 | return strcmp(drive_a->device, drive_b->device); 165 | } 166 | 167 | // Function to display help text 168 | void show_help(const char *program_name) 169 | { 170 | printf("Usage: %s [OPTIONS]\n", program_name); 171 | printf("\n"); 172 | printf("Display information about available drives and their storage space.\n"); 173 | printf("\n"); 174 | printf("Options:\n"); 175 | printf(" -h, --help Show this help message\n"); 176 | printf(" -v, --version Show program version\n"); 177 | printf(" -j, --json Output in JSON format\n"); 178 | printf(" -n, --no-color Disable color output\n"); 179 | printf(" -s, --sort TYPE Sort drives by TYPE (size, usage, mount, name)\n"); 180 | printf("\n"); 181 | printf("This program is licensed under the MIT License.\n"); 182 | printf("https://github.com/lennart1978/drinfo\n"); 183 | } 184 | 185 | // Function to display version 186 | void show_version() 187 | { 188 | printf("drinfo Version %s\n", VERSION); 189 | } 190 | 191 | // Function to get terminal width 192 | int get_terminal_width() 193 | { 194 | struct winsize w; 195 | if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) 196 | { 197 | return w.ws_col; 198 | } 199 | return TERM_FALLBACK_WIDTH; // Fallback-Value 200 | } 201 | 202 | // Function to format bytes into human-readable sizes 203 | void format_bytes(unsigned long long bytes, char *buffer, size_t buffer_size) 204 | { 205 | const char *units[] = {"B", "KB", "MB", "GB", "TB"}; 206 | int unit_index = 0; 207 | double size = bytes; 208 | 209 | while (size >= BYTES_PER_KB && unit_index < MAX_UNIT_INDEX) 210 | { 211 | size /= BYTES_PER_KB; 212 | unit_index++; 213 | } 214 | 215 | if (unit_index == 0) 216 | { 217 | snprintf(buffer, buffer_size, "%.0f %s", size, units[unit_index]); 218 | } 219 | else 220 | { 221 | snprintf(buffer, buffer_size, "%.2f %s", size, units[unit_index]); 222 | } 223 | } 224 | 225 | // Function to calculate usage percentage 226 | double calculate_usage_percent(unsigned long long total, unsigned long long available) 227 | { 228 | if (total == 0) 229 | return 0.0; 230 | unsigned long long used = total - available; 231 | return ((double)used / total) * PERCENTAGE_MULTIPLIER; 232 | } 233 | 234 | bool is_physical_device(const char *fsname) 235 | { 236 | // Check for /dev/sd*, /dev/nvme*, /dev/hd* 237 | return (strncmp(fsname, "/dev/sd", DEV_SD_PREFIX_LEN) == 0 || 238 | strncmp(fsname, "/dev/nvme", DEV_NVME_PREFIX_LEN) == 0 || 239 | strncmp(fsname, "/dev/hd", DEV_HD_PREFIX_LEN) == 0); 240 | } 241 | 242 | bool is_network_device(const char *fsname) 243 | { 244 | // Check for network file systems 245 | return (strncmp(fsname, "//", NETWORK_PATH_PREFIX_LEN) == 0 || // SMB/CIFS shares 246 | strncmp(fsname, "\\\\", NETWORK_PATH_PREFIX_LEN) == 0 || // Windows network paths 247 | strstr(fsname, ":") != NULL); // NFS and other network protocols 248 | } 249 | 250 | bool is_network_filesystem(const char *fstype) 251 | { 252 | // Check for network file system types 253 | return (strcmp(fstype, "nfs") == 0 || 254 | strcmp(fstype, "nfs4") == 0 || 255 | strcmp(fstype, "cifs") == 0 || 256 | strcmp(fstype, "smb") == 0 || 257 | strcmp(fstype, "smb3") == 0 || 258 | strcmp(fstype, "fuse.sshfs") == 0 || 259 | strcmp(fstype, "fuse.rclone") == 0 || 260 | strcmp(fstype, "fuse.gvfsd-fuse") == 0 || // GVFS for cloud storage 261 | strncmp(fstype, "fuse.", FUSE_PREFIX_LEN) == 0); // Other FUSE-based network file systems 262 | } 263 | 264 | bool is_appimage_or_temp(const char *fsname, const char *mountpoint) 265 | { 266 | // Filter out AppImages and temporary mounts 267 | return (strstr(fsname, ".AppImage") != NULL || 268 | strstr(mountpoint, "/tmp/.mount_") != NULL || 269 | strstr(mountpoint, "/tmp/") != NULL); 270 | } 271 | 272 | // Helper for true-color gradient (red-yellow-green) 273 | void get_bar_color(int idx, int max, char *buffer, size_t size) 274 | { 275 | if (opt_no_color) { 276 | buffer[0] = '\0'; 277 | return; 278 | } 279 | 280 | // New gradient: 0% = green (0,255,0), 50% = yellow (255,255,0), 100% = red (255,0,0) 281 | float ratio = (float)idx / (float)(max - 1); 282 | int r, g, b; 283 | if (ratio < COLOR_RATIO_HALF) 284 | { 285 | // Green to Yellow 286 | r = (int)(ratio * COLOR_RATIO_MULTIPLIER * MAX_COLOR_VALUE); 287 | g = MAX_COLOR_VALUE; 288 | b = 0; 289 | } 290 | else 291 | { 292 | // Yellow to Red 293 | r = MAX_COLOR_VALUE; 294 | g = (int)((1.0f - (ratio - COLOR_RATIO_HALF) * COLOR_RATIO_MULTIPLIER) * MAX_COLOR_VALUE); 295 | b = 0; 296 | } 297 | snprintf(buffer, size, COLOR_FORMAT, r, g, b); 298 | } 299 | 300 | // Helper: visible length of a string without ANSI sequences 301 | int visible_length(const char *s) 302 | { 303 | int len = 0; 304 | int in_escape = 0; 305 | for (; *s; ++s) 306 | { 307 | if (*s == '\033') 308 | in_escape = 1; 309 | else if (in_escape && *s == 'm') 310 | in_escape = 0; 311 | else if (!in_escape) 312 | len++; 313 | } 314 | return len; 315 | } 316 | 317 | // Function to check if a directory contains cloud storage 318 | bool is_cloud_storage_directory(const char *path) 319 | { 320 | DIR *dir = opendir(path); 321 | if (!dir) 322 | return false; 323 | 324 | struct dirent *entry; 325 | bool has_cloud_storage = false; 326 | 327 | while ((entry = readdir(dir)) != NULL) 328 | { 329 | // Use stat if d_type is not available 330 | struct stat st; 331 | char full_path[MAX_PATH_LENGTH]; 332 | snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name); 333 | 334 | if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode) && 335 | (strstr(entry->d_name, "google-drive") != NULL || 336 | strstr(entry->d_name, "dropbox") != NULL || 337 | strstr(entry->d_name, "onedrive") != NULL || 338 | strstr(entry->d_name, "mega") != NULL)) 339 | { 340 | has_cloud_storage = true; 341 | break; 342 | } 343 | } 344 | 345 | closedir(dir); 346 | return has_cloud_storage; 347 | } 348 | 349 | // Function to fill drive_info_t structure with common data 350 | void fill_drive_info(drive_info_t *drive, 351 | const char *mount_point, 352 | const char *filesystem, 353 | const char *device, 354 | const char *uuid, 355 | const char *label, 356 | const char *total_str, 357 | const char *used_str, 358 | const char *available_str, 359 | unsigned long long total_bytes, 360 | unsigned long long used_bytes, 361 | unsigned long long available_bytes, 362 | double usage_percent, 363 | const char *drive_type, 364 | char *progress_bar, 365 | bool is_cloud_storage, 366 | const char *cloud_service_name, 367 | const char *mount_options, 368 | unsigned long long total_inodes, 369 | unsigned long long used_inodes, 370 | double inode_usage) 371 | { 372 | strncpy(drive->mount_point, mount_point, sizeof(drive->mount_point) - 1); 373 | drive->mount_point[sizeof(drive->mount_point) - 1] = '\0'; 374 | strncpy(drive->filesystem, filesystem, sizeof(drive->filesystem) - 1); 375 | drive->filesystem[sizeof(drive->filesystem) - 1] = '\0'; 376 | strncpy(drive->device, device, sizeof(drive->device) - 1); 377 | drive->device[sizeof(drive->device) - 1] = '\0'; 378 | if (uuid) 379 | { 380 | strncpy(drive->uuid, uuid, sizeof(drive->uuid) - 1); 381 | drive->uuid[sizeof(drive->uuid) - 1] = '\0'; 382 | } 383 | else 384 | { 385 | drive->uuid[0] = '\0'; 386 | } 387 | if (label) 388 | { 389 | strncpy(drive->label, label, sizeof(drive->label) - 1); 390 | drive->label[sizeof(drive->label) - 1] = '\0'; 391 | } 392 | else 393 | { 394 | drive->label[0] = '\0'; 395 | } 396 | snprintf(drive->total_str, sizeof(drive->total_str), "%s", total_str); 397 | snprintf(drive->used_str, sizeof(drive->used_str), "%s", used_str); 398 | snprintf(drive->available_str, sizeof(drive->available_str), "%s", available_str); 399 | drive->total_bytes = total_bytes; 400 | drive->used_bytes = used_bytes; 401 | drive->available_bytes = available_bytes; 402 | drive->usage_percent = usage_percent; 403 | drive->drive_type = drive_type; 404 | drive->progress_bar = progress_bar; 405 | drive->is_cloud_storage = is_cloud_storage; 406 | if (cloud_service_name) 407 | { 408 | strncpy(drive->cloud_service_name, cloud_service_name, sizeof(drive->cloud_service_name) - 1); 409 | drive->cloud_service_name[sizeof(drive->cloud_service_name) - 1] = '\0'; 410 | } 411 | else 412 | { 413 | drive->cloud_service_name[0] = '\0'; 414 | } 415 | if (mount_options) 416 | { 417 | strncpy(drive->mount_options, mount_options, sizeof(drive->mount_options) - 1); 418 | drive->mount_options[sizeof(drive->mount_options) - 1] = '\0'; 419 | } 420 | else 421 | { 422 | drive->mount_options[0] = '\0'; 423 | } 424 | drive->total_inodes = total_inodes; 425 | drive->used_inodes = used_inodes; 426 | drive->inode_usage = inode_usage; 427 | } 428 | 429 | // Function to get cloud storage info from GVFS 430 | void get_cloud_storage_info(const char *gvfs_path, drive_info_t *drives, int *drive_count) 431 | { 432 | DIR *dir = opendir(gvfs_path); 433 | if (!dir) 434 | return; 435 | 436 | struct dirent *entry; 437 | 438 | while ((entry = readdir(dir)) != NULL && *drive_count < MAX_DRIVES) 439 | { 440 | // Use stat if d_type is not available 441 | struct stat st; 442 | char full_path[MAX_PATH_LENGTH]; 443 | snprintf(full_path, sizeof(full_path), "%s/%s", gvfs_path, entry->d_name); 444 | 445 | if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode) && 446 | (strstr(entry->d_name, "google-drive") != NULL || 447 | strstr(entry->d_name, "dropbox") != NULL || 448 | strstr(entry->d_name, "onedrive") != NULL || 449 | strstr(entry->d_name, "mega") != NULL)) 450 | { 451 | 452 | // Get file system information 453 | struct statvfs fs_info; 454 | if (statvfs(full_path, &fs_info) != 0) 455 | { 456 | continue; 457 | } 458 | 459 | // Calculate sizes 460 | unsigned long long total_bytes = (unsigned long long)fs_info.f_blocks * fs_info.f_frsize; 461 | unsigned long long available_bytes = (unsigned long long)fs_info.f_bavail * fs_info.f_frsize; 462 | unsigned long long used_bytes = total_bytes - available_bytes; 463 | 464 | // Format sizes for output 465 | char total_str[MAX_SIZE_STR_LENGTH], used_str[MAX_SIZE_STR_LENGTH], available_str[MAX_SIZE_STR_LENGTH]; 466 | format_bytes(total_bytes, total_str, sizeof(total_str)); 467 | format_bytes(used_bytes, used_str, sizeof(used_str)); 468 | format_bytes(available_bytes, available_str, sizeof(available_str)); 469 | 470 | // Target width calculation (same as before) 471 | int terminal_width = get_terminal_width(); 472 | int box_width = terminal_width * TERMINAL_WIDTH_PERCENTAGE / TERMINAL_WIDTH_DIVISOR; 473 | if (box_width > MAX_BOX_WIDTH) 474 | box_width = MAX_BOX_WIDTH; 475 | if (box_width < MIN_BOX_WIDTH) 476 | box_width = MIN_BOX_WIDTH; 477 | int content_width = box_width - FRAME_PADDING; 478 | int bar_length = content_width - BRACKET_PADDING; 479 | if (bar_length < MIN_BAR_LENGTH) 480 | bar_length = MIN_BAR_LENGTH; 481 | 482 | // Calculate usage 483 | double usage_percent = calculate_usage_percent(total_bytes, available_bytes); 484 | int filled_length = (int)((usage_percent / USAGE_PERCENT_DIVISOR) * bar_length); 485 | char percent_text[MAX_PERCENT_TEXT_LENGTH]; 486 | snprintf(percent_text, sizeof(percent_text), PERCENT_FORMAT, usage_percent); 487 | int text_length = strlen(percent_text); 488 | int text_start = filled_length > text_length ? (filled_length - text_length) / 2 : 0; 489 | 490 | // Create progress bar 491 | size_t bar_bufsize = bar_length * MAX_BAR_BUFFER_MULTIPLIER + 1; 492 | char *bar = malloc(bar_bufsize); 493 | if (!bar) 494 | { 495 | perror("malloc"); 496 | continue; 497 | } 498 | bar[0] = '\0'; 499 | for (int i = 0; i < bar_length; i++) 500 | { 501 | if (i >= text_start && i < text_start + text_length && i < filled_length) 502 | { 503 | if (!opt_no_color) { 504 | get_bar_color(i, bar_length, colorbuf, sizeof(colorbuf)); 505 | int r, g, b; 506 | sscanf(colorbuf, COLOR_FORMAT, &r, &g, &b); 507 | char tmp[MAX_TEMP_BUFFER_LENGTH]; 508 | snprintf(tmp, sizeof(tmp), BACKGROUND_COLOR_FORMAT BLUE_TEXT_FORMAT "%c" RESET_FORMAT, r, g, b, BLUE_TEXT_R, BLUE_TEXT_G, BLUE_TEXT_B, percent_text[i - text_start]); 509 | strncat(bar, tmp, bar_bufsize - strlen(bar) - 1); 510 | } else { 511 | char tmp[2] = {percent_text[i - text_start], '\0'}; 512 | strncat(bar, tmp, bar_bufsize - strlen(bar) - 1); 513 | } 514 | } 515 | else if (i < filled_length) 516 | { 517 | if (!opt_no_color) { 518 | get_bar_color(i, bar_length, colorbuf, sizeof(colorbuf)); 519 | strncat(bar, colorbuf, bar_bufsize - strlen(bar) - 1); 520 | strncat(bar, "█" RESET_FORMAT, bar_bufsize - strlen(bar) - 1); 521 | } else { 522 | strncat(bar, "█", bar_bufsize - strlen(bar) - 1); 523 | } 524 | } 525 | else 526 | { 527 | if (!opt_no_color) { 528 | strncat(bar, "\033[48;2;64;64;64m\033[38;2;160;160;160m░\033[0m", bar_bufsize - strlen(bar) - 1); 529 | } else { 530 | strncat(bar, "░", bar_bufsize - strlen(bar) - 1); 531 | } 532 | } 533 | } 534 | 535 | // Determine cloud service name 536 | const char *service_name = "Cloud Storage"; 537 | if (strstr(entry->d_name, "google-drive") != NULL) 538 | { 539 | service_name = "Google Drive"; 540 | } 541 | else if (strstr(entry->d_name, "dropbox") != NULL) 542 | { 543 | service_name = "Dropbox"; 544 | } 545 | else if (strstr(entry->d_name, "onedrive") != NULL) 546 | { 547 | service_name = "OneDrive"; 548 | } 549 | else if (strstr(entry->d_name, "mega") != NULL) 550 | { 551 | service_name = "MEGA"; 552 | } 553 | 554 | // Store information in drive_info_t structure 555 | drive_info_t *drive = &drives[*drive_count]; 556 | fill_drive_info(drive, full_path, "fuse.gvfsd-fuse", entry->d_name, 557 | NULL, NULL, 558 | total_str, used_str, available_str, 559 | total_bytes, used_bytes, available_bytes, 560 | usage_percent, "Network Drive", bar, true, service_name, NULL, 561 | (unsigned long long)fs_info.f_blocks, (unsigned long long)fs_info.f_files, 562 | (double)fs_info.f_files / (double)fs_info.f_blocks); 563 | 564 | (*drive_count)++; 565 | } 566 | } 567 | 568 | closedir(dir); 569 | } 570 | 571 | void get_uuid_and_label(const char *device, char *uuid, size_t uuid_size, char *label, size_t label_size) 572 | { 573 | uuid[0] = '\0'; 574 | label[0] = '\0'; 575 | 576 | char resolved_device[PATH_MAX]; 577 | if (!realpath(device, resolved_device)) 578 | { 579 | // Wenn das Gerät nicht auflösbar ist, abbrechen 580 | return; 581 | } 582 | 583 | // UUID suchen 584 | DIR *uuid_dir = opendir("/dev/disk/by-uuid/"); 585 | if (uuid_dir) 586 | { 587 | struct dirent *entry; 588 | char full_path[PATH_MAX]; 589 | char resolved_link[PATH_MAX]; 590 | while ((entry = readdir(uuid_dir)) != NULL) 591 | { 592 | if (entry->d_name[0] == '.') 593 | continue; 594 | snprintf(full_path, sizeof(full_path), "/dev/disk/by-uuid/%s", entry->d_name); 595 | if (realpath(full_path, resolved_link)) 596 | { 597 | if (strcmp(resolved_link, resolved_device) == 0) 598 | { 599 | strncpy(uuid, entry->d_name, uuid_size - 1); 600 | uuid[uuid_size - 1] = '\0'; 601 | break; 602 | } 603 | } 604 | } 605 | closedir(uuid_dir); 606 | } 607 | 608 | // Label suchen 609 | DIR *label_dir = opendir("/dev/disk/by-label/"); 610 | if (label_dir) 611 | { 612 | struct dirent *entry; 613 | char full_path[PATH_MAX]; 614 | char resolved_link[PATH_MAX]; 615 | while ((entry = readdir(label_dir)) != NULL) 616 | { 617 | if (entry->d_name[0] == '.') 618 | continue; 619 | snprintf(full_path, sizeof(full_path), "/dev/disk/by-label/%s", entry->d_name); 620 | if (realpath(full_path, resolved_link)) 621 | { 622 | if (strcmp(resolved_link, resolved_device) == 0) 623 | { 624 | strncpy(label, entry->d_name, label_size - 1); 625 | label[label_size - 1] = '\0'; 626 | break; 627 | } 628 | } 629 | } 630 | closedir(label_dir); 631 | } 632 | } 633 | 634 | // Helper function: Query SMART status (only for root and physical devices) 635 | void get_smart_status(const char *device, char *status, size_t status_size) 636 | { 637 | status[0] = '\0'; 638 | if (geteuid() != 0) 639 | return; 640 | // Allow all /dev/sd*, /dev/nvme*, /dev/hd* (including partitions) 641 | if (!( 642 | strncmp(device, "/dev/sd", 7) == 0 || 643 | strncmp(device, "/dev/nvme", 9) == 0 || 644 | strncmp(device, "/dev/hd", 7) == 0)) 645 | return; 646 | 647 | char cmd[256], line[256]; 648 | snprintf(cmd, sizeof(cmd), "smartctl -H %s 2>/dev/null", device); 649 | FILE *fp = popen(cmd, "r"); 650 | if (!fp) 651 | return; 652 | while (fgets(line, sizeof(line), fp)) 653 | { 654 | if (strstr(line, "SMART overall-health self-assessment test result") || strstr(line, "SMART Health Status")) 655 | { 656 | char *p = strchr(line, ':'); 657 | if (p) 658 | { 659 | p++; 660 | while (*p == ' ' || *p == '\t') 661 | p++; 662 | strncpy(status, p, status_size - 1); 663 | status[status_size - 1] = '\0'; 664 | char *nl = strchr(status, '\n'); 665 | if (nl) 666 | *nl = '\0'; 667 | break; 668 | } 669 | } 670 | if (strstr(line, "PASSED")) 671 | { 672 | strncpy(status, "PASSED", status_size - 1); 673 | status[status_size - 1] = '\0'; 674 | break; 675 | } 676 | if (strstr(line, "FAILED")) 677 | { 678 | strncpy(status, "FAILED", status_size - 1); 679 | status[status_size - 1] = '\0'; 680 | break; 681 | } 682 | if (strstr(line, "UNKNOWN")) 683 | { 684 | strncpy(status, "UNKNOWN", status_size - 1); 685 | status[status_size - 1] = '\0'; 686 | break; 687 | } 688 | if (strstr(line, "NOT AVAILABLE")) 689 | { 690 | strncpy(status, "NOT AVAILABLE", status_size - 1); 691 | status[status_size - 1] = '\0'; 692 | break; 693 | } 694 | } 695 | pclose(fp); 696 | } 697 | 698 | void print_json(drive_info_t *drives, int count) { 699 | printf("[\n"); 700 | for (int i = 0; i < count; i++) { 701 | drive_info_t *d = &drives[i]; 702 | printf(" {\n"); 703 | printf(" \"device\": \"%s\",\n", d->device); 704 | printf(" \"mount_point\": \"%s\",\n", d->mount_point); 705 | printf(" \"filesystem\": \"%s\",\n", d->filesystem); 706 | printf(" \"total_bytes\": %llu,\n", d->total_bytes); 707 | printf(" \"used_bytes\": %llu,\n", d->used_bytes); 708 | printf(" \"available_bytes\": %llu,\n", d->available_bytes); 709 | printf(" \"usage_percent\": %.1f,\n", d->usage_percent); 710 | printf(" \"type\": \"%s\",\n", d->drive_type); 711 | printf(" \"is_cloud\": %s,\n", d->is_cloud_storage ? "true" : "false"); 712 | printf(" \"cloud_service\": \"%s\",\n", d->cloud_service_name); 713 | printf(" \"uuid\": \"%s\",\n", d->uuid); 714 | printf(" \"label\": \"%s\",\n", d->label); 715 | printf(" \"mount_options\": \"%s\",\n", d->mount_options); 716 | printf(" \"total_inodes\": %llu,\n", d->total_inodes); 717 | printf(" \"used_inodes\": %llu,\n", d->used_inodes); 718 | printf(" \"inode_usage\": %.1f\n", d->inode_usage); 719 | if (i < count - 1) printf(" },\n"); 720 | else printf(" }\n"); 721 | } 722 | printf("]\n"); 723 | } 724 | 725 | void discover_drives(drive_info_t *drives, int *drive_count) { 726 | *drive_count = 0; 727 | 728 | // Open the mount table 729 | FILE *mtab = setmntent(MOUNT_TABLE_PATH, "r"); 730 | if (mtab == NULL) 731 | { 732 | perror("Error opening mount table"); 733 | return; 734 | } 735 | 736 | struct mntent *entry; 737 | 738 | // Loop through all mount points and collect drive information 739 | while ((entry = getmntent(mtab)) != NULL && *drive_count < MAX_DRIVES) 740 | { 741 | // Skip special file systems 742 | const int skip_count = sizeof(skip_filesystems) / sizeof(skip_filesystems[0]); 743 | 744 | bool should_skip = false; 745 | for (int i = 0; i < skip_count; i++) 746 | { 747 | if (strcmp(entry->mnt_type, skip_filesystems[i]) == 0) 748 | { 749 | should_skip = true; 750 | break; 751 | } 752 | } 753 | 754 | if (should_skip) 755 | { 756 | continue; 757 | } 758 | 759 | // Show physical drives and network drives 760 | if (!is_physical_device(entry->mnt_fsname) && 761 | !is_network_device(entry->mnt_fsname) && 762 | !is_network_filesystem(entry->mnt_type)) 763 | { 764 | continue; 765 | } 766 | 767 | // Skip AppImages and temporary mounts 768 | if (is_appimage_or_temp(entry->mnt_fsname, entry->mnt_dir)) 769 | { 770 | continue; 771 | } 772 | 773 | // Get file system information 774 | struct statvfs fs_info; 775 | if (statvfs(entry->mnt_dir, &fs_info) != 0) 776 | { 777 | continue; // Skip if no information available 778 | } 779 | 780 | // Calculate sizes 781 | unsigned long long total_bytes = (unsigned long long)fs_info.f_blocks * fs_info.f_frsize; 782 | unsigned long long available_bytes = (unsigned long long)fs_info.f_bavail * fs_info.f_frsize; 783 | unsigned long long used_bytes = total_bytes - available_bytes; 784 | 785 | // Format sizes for output 786 | char total_str[MAX_SIZE_STR_LENGTH], used_str[MAX_SIZE_STR_LENGTH], available_str[MAX_SIZE_STR_LENGTH]; 787 | format_bytes(total_bytes, total_str, sizeof(total_str)); 788 | format_bytes(used_bytes, used_str, sizeof(used_str)); 789 | format_bytes(available_bytes, available_str, sizeof(available_str)); 790 | 791 | // Inode-Infos 792 | unsigned long long total_inodes = fs_info.f_files; 793 | unsigned long long free_inodes = fs_info.f_favail; 794 | unsigned long long used_inodes = total_inodes > 0 ? total_inodes - free_inodes : 0; 795 | double inode_usage = (total_inodes > 0) ? ((double)used_inodes / total_inodes) * 100.0 : 0.0; 796 | 797 | // Target width: 80% of terminal width or max. 120 characters 798 | int terminal_width = get_terminal_width(); 799 | int box_width = terminal_width * TERMINAL_WIDTH_PERCENTAGE / TERMINAL_WIDTH_DIVISOR; 800 | if (box_width > MAX_BOX_WIDTH) 801 | box_width = MAX_BOX_WIDTH; 802 | if (box_width < MIN_BOX_WIDTH) 803 | box_width = MIN_BOX_WIDTH; 804 | int content_width = box_width - FRAME_PADDING; // for frame 805 | int bar_length = content_width - BRACKET_PADDING; // for [ and ] 806 | if (bar_length < MIN_BAR_LENGTH) 807 | bar_length = MIN_BAR_LENGTH; 808 | 809 | // Calculate usage 810 | double usage_percent = calculate_usage_percent(total_bytes, available_bytes); 811 | int filled_length = (int)((usage_percent / USAGE_PERCENT_DIVISOR) * bar_length); 812 | char percent_text[MAX_PERCENT_TEXT_LENGTH]; 813 | snprintf(percent_text, sizeof(percent_text), PERCENT_FORMAT, usage_percent); 814 | int text_length = strlen(percent_text); 815 | int text_start = filled_length > text_length ? (filled_length - text_length) / 2 : 0; 816 | 817 | // Dynamically allocate progress bar 818 | size_t bar_bufsize = bar_length * MAX_BAR_BUFFER_MULTIPLIER + 1; 819 | char *bar = malloc(bar_bufsize); 820 | if (!bar) 821 | { 822 | perror("malloc"); 823 | continue; 824 | } 825 | bar[0] = '\0'; 826 | for (int i = 0; i < bar_length; i++) 827 | { 828 | if (i >= text_start && i < text_start + text_length && i < filled_length) 829 | { 830 | if (!opt_no_color) { 831 | get_bar_color(i, bar_length, colorbuf, sizeof(colorbuf)); 832 | int r, g, b; 833 | sscanf(colorbuf, COLOR_FORMAT, &r, &g, &b); 834 | char tmp[MAX_TEMP_BUFFER_LENGTH]; 835 | snprintf(tmp, sizeof(tmp), BACKGROUND_COLOR_FORMAT BLUE_TEXT_FORMAT "%c" RESET_FORMAT, r, g, b, BLUE_TEXT_R, BLUE_TEXT_G, BLUE_TEXT_B, percent_text[i - text_start]); 836 | strncat(bar, tmp, bar_bufsize - strlen(bar) - 1); 837 | } else { 838 | char tmp[2] = {percent_text[i - text_start], '\0'}; 839 | strncat(bar, tmp, bar_bufsize - strlen(bar) - 1); 840 | } 841 | } 842 | else if (i < filled_length) 843 | { 844 | if (!opt_no_color) { 845 | get_bar_color(i, bar_length, colorbuf, sizeof(colorbuf)); 846 | strncat(bar, colorbuf, bar_bufsize - strlen(bar) - 1); 847 | strncat(bar, "█" RESET_FORMAT, bar_bufsize - strlen(bar) - 1); 848 | } else { 849 | strncat(bar, "█", bar_bufsize - strlen(bar) - 1); 850 | } 851 | } 852 | else 853 | { 854 | if (!opt_no_color) { 855 | strncat(bar, "\033[48;2;64;64;64m\033[38;2;160;160;160m░\033[0m", bar_bufsize - strlen(bar) - 1); 856 | } else { 857 | strncat(bar, "░", bar_bufsize - strlen(bar) - 1); 858 | } 859 | } 860 | } 861 | 862 | // Determine drive type 863 | const char *drive_type; 864 | if (is_physical_device(entry->mnt_fsname)) 865 | { 866 | drive_type = "Local Drive"; 867 | } 868 | else if (is_network_filesystem(entry->mnt_type) || is_network_device(entry->mnt_fsname)) 869 | { 870 | drive_type = "Network Drive"; 871 | } 872 | else 873 | { 874 | drive_type = "Other Drive"; 875 | } 876 | 877 | // UUID und Label ermitteln 878 | char uuid[128], label[128]; 879 | get_uuid_and_label(entry->mnt_fsname, uuid, sizeof(uuid), label, sizeof(label)); 880 | 881 | // Store information in drive_info_t structure 882 | drive_info_t *drive = &drives[*drive_count]; 883 | fill_drive_info(drive, entry->mnt_dir, entry->mnt_type, entry->mnt_fsname, 884 | uuid, label, 885 | total_str, used_str, available_str, 886 | total_bytes, used_bytes, available_bytes, 887 | usage_percent, drive_type, bar, false, NULL, entry->mnt_opts, 888 | total_inodes, used_inodes, inode_usage); 889 | 890 | (*drive_count)++; 891 | } 892 | 893 | endmntent(mtab); 894 | 895 | // Check for GVFS-based cloud storage 896 | char gvfs_path[MAX_GVFS_PATH_LENGTH]; 897 | snprintf(gvfs_path, sizeof(gvfs_path), GVFS_BASE_PATH, getuid()); 898 | if (is_cloud_storage_directory(gvfs_path)) 899 | { 900 | get_cloud_storage_info(gvfs_path, drives, drive_count); 901 | } 902 | } 903 | 904 | int main(int argc, char *argv[]) 905 | { 906 | static struct option long_options[] = { 907 | {"help", no_argument, 0, 'h'}, 908 | {"version", no_argument, 0, 'v'}, 909 | {"json", no_argument, 0, 'j'}, 910 | {"no-color", no_argument, 0, 'n'}, 911 | {"sort", required_argument, 0, 's'}, 912 | {0, 0, 0, 0} 913 | }; 914 | 915 | int opt; 916 | int option_index = 0; 917 | 918 | while ((opt = getopt_long(argc, argv, "hvjns:", long_options, &option_index)) != -1) 919 | { 920 | switch (opt) 921 | { 922 | case 'h': 923 | show_help(argv[0]); 924 | return 0; 925 | case 'v': 926 | show_version(); 927 | return 0; 928 | case 'j': 929 | opt_json = true; 930 | break; 931 | case 'n': 932 | opt_no_color = true; 933 | break; 934 | case 's': 935 | if (strcmp(optarg, "size") == 0) opt_sort = SORT_SIZE; 936 | else if (strcmp(optarg, "usage") == 0) opt_sort = SORT_USAGE; 937 | else if (strcmp(optarg, "mount") == 0) opt_sort = SORT_MOUNT; 938 | else if (strcmp(optarg, "name") == 0) opt_sort = SORT_NAME; 939 | else { 940 | fprintf(stderr, "Invalid sort option: %s\n", optarg); 941 | return 1; 942 | } 943 | break; 944 | default: 945 | return 1; 946 | } 947 | } 948 | 949 | if (opt_no_color) { 950 | c_bold_yellow = ""; 951 | c_reset = ""; 952 | } 953 | 954 | if (!opt_json) { 955 | printf("\n"); 956 | } 957 | 958 | // Array to store all drive information 959 | drive_info_t drives[MAX_DRIVES]; 960 | int drive_count = 0; 961 | 962 | discover_drives(drives, &drive_count); 963 | 964 | // Sort drives 965 | switch (opt_sort) { 966 | case SORT_SIZE: 967 | qsort(drives, drive_count, sizeof(drive_info_t), compare_drives_by_capacity); 968 | break; 969 | case SORT_USAGE: 970 | qsort(drives, drive_count, sizeof(drive_info_t), compare_drives_by_usage); 971 | break; 972 | case SORT_MOUNT: 973 | qsort(drives, drive_count, sizeof(drive_info_t), compare_drives_by_mount); 974 | break; 975 | case SORT_NAME: 976 | qsort(drives, drive_count, sizeof(drive_info_t), compare_drives_by_name); 977 | break; 978 | } 979 | 980 | if (opt_json) { 981 | print_json(drives, drive_count); 982 | } else { 983 | // Display sorted drives 984 | for (int i = 0; i < drive_count; i++) 985 | { 986 | drive_info_t *drive = &drives[i]; 987 | 988 | // Display drive information 989 | if (drive->is_cloud_storage) 990 | { 991 | printf(" %sNetwork Drive %d (%s)%s\n", c_bold_yellow, i + 1, drive->cloud_service_name, c_reset); 992 | } 993 | else 994 | { 995 | printf(" %s%s %d%s\n", c_bold_yellow, drive->drive_type, i + 1, c_reset); 996 | } 997 | printf(" Mount point: %s\n", drive->mount_point); 998 | printf(" Filesystem: %s\n", drive->filesystem); 999 | printf(" Device: %s\n", drive->device); 1000 | printf(" UUID: %s\n", drive->uuid[0] ? drive->uuid : "-"); 1001 | printf(" Label: %s\n", drive->label[0] ? drive->label : "-"); 1002 | printf(" Mount options: %s\n", drive->mount_options); 1003 | printf(" Total size: %s\n", drive->total_str); 1004 | printf(" Used: %s\n", drive->used_str); 1005 | printf(" Available: %s\n", drive->available_str); 1006 | printf(" Inodes: %llu/%llu (%.1f%% used)\n", drive->used_inodes, drive->total_inodes, drive->inode_usage); 1007 | 1008 | // SMART status only for root and physical devices 1009 | if (geteuid() == 0 && !drive->is_cloud_storage && strcmp(drive->drive_type, "Local Drive") == 0) 1010 | { 1011 | char smart_status[128]; 1012 | get_smart_status(drive->device, smart_status, sizeof(smart_status)); 1013 | if (smart_status[0]) 1014 | { 1015 | printf(" SMART: %s\n", smart_status); 1016 | } 1017 | else 1018 | { 1019 | printf(" SMART: No data\n"); 1020 | } 1021 | } 1022 | 1023 | // Progress bar 1024 | int terminal_width = get_terminal_width(); 1025 | int box_width = terminal_width * TERMINAL_WIDTH_PERCENTAGE / TERMINAL_WIDTH_DIVISOR; 1026 | if (box_width > MAX_BOX_WIDTH) 1027 | box_width = MAX_BOX_WIDTH; 1028 | if (box_width < MIN_BOX_WIDTH) 1029 | box_width = MIN_BOX_WIDTH; 1030 | int content_width = box_width - FRAME_PADDING; 1031 | 1032 | int bar_visible_len = visible_length(drive->progress_bar); 1033 | int bar_padding = content_width - bar_visible_len; 1034 | printf(" %s%*s\n", drive->progress_bar, bar_padding, ""); 1035 | 1036 | // Free allocated memory 1037 | free(drive->progress_bar); 1038 | } 1039 | 1040 | if (drive_count == 0) 1041 | { 1042 | printf("No drives found.\n"); 1043 | } 1044 | else 1045 | { 1046 | printf("A total of %d drives found.\n", drive_count); 1047 | } 1048 | } 1049 | 1050 | return 0; 1051 | } 1052 | --------------------------------------------------------------------------------