6 Commits
Author SHA1 Message Date
Lennart Martens 30d6ad50d8 Added additional information to the output.
Update to version 1.0.4
2025-07-04 18:34:15 +02:00
Lennart Martens d11a083db9 Refactor code to use more modern C features and improve readability.
Add man page for drinfo.
2025-06-30 03:19:06 +02:00
Lennart Martens 0f75769eb4 Update screenshot 2025-06-29 21:57:20 +02:00
Lennart Martens 825c49a796 Version 1.0.2 2025-06-29 21:47:59 +02:00
Lennart Martens 04a196dab6 Show biggest drive first (sorted by capacity) 2025-06-29 21:45:44 +02:00
Lennart Martens 9f04f782b0 Changed "Physical Drive" to "Local Drive" 2025-06-29 21:31:42 +02:00
5 changed files with 423 additions and 75 deletions

No files matched your search

+4 -1
View File
@@ -12,9 +12,12 @@ clean:
rm -f $(TARGET)
install: $(TARGET)
sudo cp $(TARGET) /usr/local/bin/
install -Dm755 drinfo /usr/local/bin/drinfo
install -Dm644 drinfo.1 /usr/share/man/man1/drinfo.1
sudo mandb > /dev/null 2>&1
uninstall:
sudo rm -f /usr/local/bin/$(TARGET)
sudo rm -f /usr/share/man/man1/$(TARGET).1
.PHONY: all clean install uninstall
+9 -3
View File
@@ -10,20 +10,26 @@ A lightweight command-line tool to monitor disk usage on Linux systems with beau
- **Colorful Progress Bars**: Visual representation of disk usage with gradient colors (green → yellow → red)
- **Human-Readable Sizes**: Displays sizes in B, KB, MB, GB, TB format
- **Terminal Responsive**: Adapts to terminal width for optimal display
- **Detailed Information**: Shows mount point, filesystem type, device path, and usage statistics
- **Detailed Information**: Shows mount point, filesystem type, device path, UUID, label, mount options, used, available and inodes + SMART status (only as root)
## Build
## Build executable: drinfo
```bash
make
```
## install
## install with man page
```bash
sudo make install
```
## Uninstall
```bash
sudo make uninstall
```
## Screenshot
![screenshot](screenshot.jpg)
+28
View File
@@ -0,0 +1,28 @@
.TH DRINFO 1 "June 2024" "Version 1.0.4" "User Commands"
.SH NAME
drinfo \- display information about drives and their storage usage
.SH SYNOPSIS
.B drinfo
.RI [ OPTION ]
.SH DESCRIPTION
.B drinfo
lists all detected local and network drives and Shows mount point, filesystem type, device path,
UUID, label, mount options, used, available and inodes + SMART status (only as root).
.SH OPTIONS
.TP
.BR -h , --help
Show help message and exit.
.TP
.BR -v , --version
Show program version and exit.
.SH AUTHOR
Lennart Martens <monkeynator78@gmail.com>
.SH LICENSE
MIT License
.SH SEE ALSO
.BR df (1),
.BR lsblk (8)
+382 -71
View File
@@ -11,10 +11,13 @@
#include <time.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stdbool.h>
#include <errno.h>
#include <sys/types.h>
// Constants for terminal and display
#define TERM_FALLBACK_WIDTH 80
#define VERSION "1.0.1"
#define VERSION "1.0.4"
#define COLOR_BUFFER_SIZE 32
#define MAX_UNITS 5
#define BYTES_PER_KB 1024.0
@@ -74,8 +77,56 @@
#define MOUNT_TABLE_PATH "/proc/mounts"
#define GVFS_BASE_PATH "/run/user/%d/gvfs"
// Maximum number of drives to handle
#define MAX_DRIVES 100
// Constants for file system types to skip
const char *skip_filesystems[] = {
"proc", "sysfs", "devpts", "tmpfs", "devtmpfs", "securityfs",
"cgroup", "cgroup2", "pstore", "efivarfs", "autofs", "debugfs",
"tracefs", "configfs", "fusectl", "fuse.gvfsd-fuse", "binfmt_misc",
"fuse.portal"};
// Structure to hold drive information
typedef struct
{
char mount_point[MAX_PATH_LENGTH];
char filesystem[MAX_SIZE_STR_LENGTH];
char device[MAX_PATH_LENGTH];
char uuid[128];
char label[128];
char total_str[MAX_SIZE_STR_LENGTH];
char used_str[MAX_SIZE_STR_LENGTH];
char available_str[MAX_SIZE_STR_LENGTH];
unsigned long long total_bytes;
unsigned long long used_bytes;
unsigned long long available_bytes;
double usage_percent;
const char *drive_type;
char *progress_bar;
bool is_cloud_storage;
char cloud_service_name[MAX_SIZE_STR_LENGTH];
char mount_options[MAX_TEMP_BUFFER_LENGTH];
unsigned long long total_inodes;
unsigned long long used_inodes;
double inode_usage;
} drive_info_t;
char colorbuf[COLOR_BUFFER_SIZE]; // For bar colors
// Function to compare drives by total capacity (descending order)
int compare_drives_by_capacity(const void *a, const void *b)
{
const drive_info_t *drive_a = (const drive_info_t *)a;
const drive_info_t *drive_b = (const drive_info_t *)b;
if (drive_b->total_bytes > drive_a->total_bytes)
return 1;
if (drive_b->total_bytes < drive_a->total_bytes)
return -1;
return 0;
}
// Function to display help text
void show_help(const char *program_name)
{
@@ -140,7 +191,7 @@ double calculate_usage_percent(unsigned long long total, unsigned long long avai
return ((double)used / total) * PERCENTAGE_MULTIPLIER;
}
int is_physical_device(const char *fsname)
bool is_physical_device(const char *fsname)
{
// Check for /dev/sd*, /dev/nvme*, /dev/hd*
return (strncmp(fsname, "/dev/sd", DEV_SD_PREFIX_LEN) == 0 ||
@@ -148,7 +199,7 @@ int is_physical_device(const char *fsname)
strncmp(fsname, "/dev/hd", DEV_HD_PREFIX_LEN) == 0);
}
int is_network_device(const char *fsname)
bool is_network_device(const char *fsname)
{
// Check for network file systems
return (strncmp(fsname, "//", NETWORK_PATH_PREFIX_LEN) == 0 || // SMB/CIFS shares
@@ -156,7 +207,7 @@ int is_network_device(const char *fsname)
strstr(fsname, ":") != NULL); // NFS and other network protocols
}
int is_network_filesystem(const char *fstype)
bool is_network_filesystem(const char *fstype)
{
// Check for network file system types
return (strcmp(fstype, "nfs") == 0 ||
@@ -170,7 +221,7 @@ int is_network_filesystem(const char *fstype)
strncmp(fstype, "fuse.", FUSE_PREFIX_LEN) == 0); // Other FUSE-based network file systems
}
int is_appimage_or_temp(const char *fsname, const char *mountpoint)
bool is_appimage_or_temp(const char *fsname, const char *mountpoint)
{
// Filter out AppImages and temporary mounts
return (strstr(fsname, ".AppImage") != NULL ||
@@ -232,14 +283,14 @@ int max_visible_line_length(const char *lines[], int n)
}
// Function to check if a directory contains cloud storage
int is_cloud_storage_directory(const char *path)
bool is_cloud_storage_directory(const char *path)
{
DIR *dir = opendir(path);
if (!dir)
return 0;
return false;
struct dirent *entry;
int has_cloud_storage = 0;
bool has_cloud_storage = false;
while ((entry = readdir(dir)) != NULL)
{
@@ -254,7 +305,7 @@ int is_cloud_storage_directory(const char *path)
strstr(entry->d_name, "onedrive") != NULL ||
strstr(entry->d_name, "mega") != NULL))
{
has_cloud_storage = 1;
has_cloud_storage = true;
break;
}
}
@@ -263,8 +314,88 @@ int is_cloud_storage_directory(const char *path)
return has_cloud_storage;
}
// Function to fill drive_info_t structure with common data
void fill_drive_info(drive_info_t *drive,
const char *mount_point,
const char *filesystem,
const char *device,
const char *uuid,
const char *label,
const char *total_str,
const char *used_str,
const char *available_str,
unsigned long long total_bytes,
unsigned long long used_bytes,
unsigned long long available_bytes,
double usage_percent,
const char *drive_type,
char *progress_bar,
bool is_cloud_storage,
const char *cloud_service_name,
const char *mount_options,
unsigned long long total_inodes,
unsigned long long used_inodes,
double inode_usage)
{
strncpy(drive->mount_point, mount_point, sizeof(drive->mount_point) - 1);
drive->mount_point[sizeof(drive->mount_point) - 1] = '\0';
strncpy(drive->filesystem, filesystem, sizeof(drive->filesystem) - 1);
drive->filesystem[sizeof(drive->filesystem) - 1] = '\0';
strncpy(drive->device, device, sizeof(drive->device) - 1);
drive->device[sizeof(drive->device) - 1] = '\0';
if (uuid)
{
strncpy(drive->uuid, uuid, sizeof(drive->uuid) - 1);
drive->uuid[sizeof(drive->uuid) - 1] = '\0';
}
else
{
drive->uuid[0] = '\0';
}
if (label)
{
strncpy(drive->label, label, sizeof(drive->label) - 1);
drive->label[sizeof(drive->label) - 1] = '\0';
}
else
{
drive->label[0] = '\0';
}
snprintf(drive->total_str, sizeof(drive->total_str), "%s", total_str);
snprintf(drive->used_str, sizeof(drive->used_str), "%s", used_str);
snprintf(drive->available_str, sizeof(drive->available_str), "%s", available_str);
drive->total_bytes = total_bytes;
drive->used_bytes = used_bytes;
drive->available_bytes = available_bytes;
drive->usage_percent = usage_percent;
drive->drive_type = drive_type;
drive->progress_bar = progress_bar;
drive->is_cloud_storage = is_cloud_storage;
if (cloud_service_name)
{
strncpy(drive->cloud_service_name, cloud_service_name, sizeof(drive->cloud_service_name) - 1);
drive->cloud_service_name[sizeof(drive->cloud_service_name) - 1] = '\0';
}
else
{
drive->cloud_service_name[0] = '\0';
}
if (mount_options)
{
strncpy(drive->mount_options, mount_options, sizeof(drive->mount_options) - 1);
drive->mount_options[sizeof(drive->mount_options) - 1] = '\0';
}
else
{
drive->mount_options[0] = '\0';
}
drive->total_inodes = total_inodes;
drive->used_inodes = used_inodes;
drive->inode_usage = inode_usage;
}
// Function to get cloud storage info from GVFS
void get_cloud_storage_info(const char *gvfs_path, int *drive_count)
void get_cloud_storage_info(const char *gvfs_path, drive_info_t *drives, int *drive_count)
{
DIR *dir = opendir(gvfs_path);
if (!dir)
@@ -272,7 +403,7 @@ void get_cloud_storage_info(const char *gvfs_path, int *drive_count)
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
while ((entry = readdir(dir)) != NULL && *drive_count < MAX_DRIVES)
{
// Use stat if d_type is not available
struct stat st;
@@ -375,20 +506,16 @@ void get_cloud_storage_info(const char *gvfs_path, int *drive_count)
service_name = "MEGA";
}
// Output
printf(" " BOLD_YELLOW_FORMAT "Network Drive %d (%s)" RESET_COLOR_FORMAT "\n", *drive_count + 1, service_name);
printf(" Mount point: %s\n", full_path);
printf(" Filesystem: %s\n", "fuse.gvfsd-fuse");
printf(" Device: %s\n", entry->d_name);
printf(" Total size: %s\n", total_str);
printf(" Used: %s\n", used_str);
printf(" Available: %s\n", available_str);
// Store information in drive_info_t structure
drive_info_t *drive = &drives[*drive_count];
fill_drive_info(drive, full_path, "fuse.gvfsd-fuse", entry->d_name,
NULL, NULL,
total_str, used_str, available_str,
total_bytes, used_bytes, available_bytes,
usage_percent, "Network Drive", bar, true, service_name, NULL,
(unsigned long long)fs_info.f_blocks, (unsigned long long)fs_info.f_files,
(double)fs_info.f_files / (double)fs_info.f_blocks);
int bar_visible_len = visible_length(bar);
int bar_padding = content_width - bar_visible_len;
printf(" %s%*s\n", bar, bar_padding, "");
free(bar);
(*drive_count)++;
}
}
@@ -396,6 +523,133 @@ void get_cloud_storage_info(const char *gvfs_path, int *drive_count)
closedir(dir);
}
void get_uuid_and_label(const char *device, char *uuid, size_t uuid_size, char *label, size_t label_size)
{
uuid[0] = '\0';
label[0] = '\0';
char resolved_device[PATH_MAX];
if (!realpath(device, resolved_device))
{
// Wenn das Gerät nicht auflösbar ist, abbrechen
return;
}
// UUID suchen
DIR *uuid_dir = opendir("/dev/disk/by-uuid/");
if (uuid_dir)
{
struct dirent *entry;
char full_path[PATH_MAX];
char resolved_link[PATH_MAX];
while ((entry = readdir(uuid_dir)) != NULL)
{
if (entry->d_name[0] == '.')
continue;
snprintf(full_path, sizeof(full_path), "/dev/disk/by-uuid/%s", entry->d_name);
if (realpath(full_path, resolved_link))
{
if (strcmp(resolved_link, resolved_device) == 0)
{
strncpy(uuid, entry->d_name, uuid_size - 1);
uuid[uuid_size - 1] = '\0';
break;
}
}
}
closedir(uuid_dir);
}
// Label suchen
DIR *label_dir = opendir("/dev/disk/by-label/");
if (label_dir)
{
struct dirent *entry;
char full_path[PATH_MAX];
char resolved_link[PATH_MAX];
while ((entry = readdir(label_dir)) != NULL)
{
if (entry->d_name[0] == '.')
continue;
snprintf(full_path, sizeof(full_path), "/dev/disk/by-label/%s", entry->d_name);
if (realpath(full_path, resolved_link))
{
if (strcmp(resolved_link, resolved_device) == 0)
{
strncpy(label, entry->d_name, label_size - 1);
label[label_size - 1] = '\0';
break;
}
}
}
closedir(label_dir);
}
}
// Helper function: Query SMART status (only for root and physical devices)
void get_smart_status(const char *device, char *status, size_t status_size)
{
status[0] = '\0';
if (geteuid() != 0)
return;
// Allow all /dev/sd*, /dev/nvme*, /dev/hd* (including partitions)
if (!(
strncmp(device, "/dev/sd", 7) == 0 ||
strncmp(device, "/dev/nvme", 9) == 0 ||
strncmp(device, "/dev/hd", 7) == 0))
return;
char cmd[256], line[256];
snprintf(cmd, sizeof(cmd), "smartctl -H %s 2>/dev/null", device);
FILE *fp = popen(cmd, "r");
if (!fp)
return;
while (fgets(line, sizeof(line), fp))
{
if (strstr(line, "SMART overall-health self-assessment test result") || strstr(line, "SMART Health Status"))
{
char *p = strchr(line, ':');
if (p)
{
p++;
while (*p == ' ' || *p == '\t')
p++;
strncpy(status, p, status_size - 1);
status[status_size - 1] = '\0';
char *nl = strchr(status, '\n');
if (nl)
*nl = '\0';
break;
}
}
if (strstr(line, "PASSED"))
{
strncpy(status, "PASSED", status_size - 1);
status[status_size - 1] = '\0';
break;
}
if (strstr(line, "FAILED"))
{
strncpy(status, "FAILED", status_size - 1);
status[status_size - 1] = '\0';
break;
}
if (strstr(line, "UNKNOWN"))
{
strncpy(status, "UNKNOWN", status_size - 1);
status[status_size - 1] = '\0';
break;
}
if (strstr(line, "NOT AVAILABLE"))
{
strncpy(status, "NOT AVAILABLE", status_size - 1);
status[status_size - 1] = '\0';
break;
}
}
pclose(fp);
}
int main(int argc, char *argv[])
{
if (argc > 1)
@@ -413,6 +667,11 @@ int main(int argc, char *argv[])
}
printf("\n");
// Array to store all drive information
drive_info_t drives[MAX_DRIVES];
int drive_count = 0;
// Open the mount table
FILE *mtab = setmntent(MOUNT_TABLE_PATH, "r");
if (mtab == NULL)
@@ -422,32 +681,28 @@ int main(int argc, char *argv[])
}
struct mntent *entry;
int drive_count = 0;
// Loop through all mount points
while ((entry = getmntent(mtab)) != NULL)
// Loop through all mount points and collect drive information
while ((entry = getmntent(mtab)) != NULL && drive_count < MAX_DRIVES)
{
// Skip special file systems
if (strcmp(entry->mnt_type, "proc") == 0 ||
strcmp(entry->mnt_type, "sysfs") == 0 ||
strcmp(entry->mnt_type, "devpts") == 0 ||
strcmp(entry->mnt_type, "tmpfs") == 0 ||
strcmp(entry->mnt_type, "devtmpfs") == 0 ||
strcmp(entry->mnt_type, "securityfs") == 0 ||
strcmp(entry->mnt_type, "cgroup") == 0 ||
strcmp(entry->mnt_type, "cgroup2") == 0 ||
strcmp(entry->mnt_type, "pstore") == 0 ||
strcmp(entry->mnt_type, "efivarfs") == 0 ||
strcmp(entry->mnt_type, "autofs") == 0 ||
strcmp(entry->mnt_type, "debugfs") == 0 ||
strcmp(entry->mnt_type, "tracefs") == 0 ||
strcmp(entry->mnt_type, "configfs") == 0 ||
strcmp(entry->mnt_type, "fusectl") == 0 ||
strcmp(entry->mnt_type, "fuse.gvfsd-fuse") == 0 ||
strcmp(entry->mnt_type, "binfmt_misc") == 0)
const int skip_count = sizeof(skip_filesystems) / sizeof(skip_filesystems[0]);
bool should_skip = false;
for (int i = 0; i < skip_count; i++)
{
if (strcmp(entry->mnt_type, skip_filesystems[i]) == 0)
{
should_skip = true;
break;
}
}
if (should_skip)
{
continue;
}
// Show physical drives and network drives
if (!is_physical_device(entry->mnt_fsname) &&
!is_network_device(entry->mnt_fsname) &&
@@ -480,6 +735,12 @@ int main(int argc, char *argv[])
format_bytes(used_bytes, used_str, sizeof(used_str));
format_bytes(available_bytes, available_str, sizeof(available_str));
// Inode-Infos
unsigned long long total_inodes = fs_info.f_files;
unsigned long long free_inodes = fs_info.f_favail;
unsigned long long used_inodes = total_inodes > 0 ? total_inodes - free_inodes : 0;
double inode_usage = (total_inodes > 0) ? ((double)used_inodes / total_inodes) * 100.0 : 0.0;
// Target width: 80% of terminal width or max. 120 characters
int terminal_width = get_terminal_width();
int box_width = terminal_width * TERMINAL_WIDTH_PERCENTAGE / TERMINAL_WIDTH_DIVISOR;
@@ -506,7 +767,7 @@ int main(int argc, char *argv[])
if (!bar)
{
perror("malloc");
exit(1);
continue;
}
bar[0] = '\0';
for (int i = 0; i < bar_length; i++)
@@ -531,22 +792,12 @@ int main(int argc, char *argv[])
strncat(bar, "\033[48;2;64;64;64m\033[38;2;160;160;160m░\033[0m", bar_bufsize - strlen(bar) - 1);
}
}
size_t barline_bufsize = bar_bufsize + BARLINE_BUFFER_EXTRA;
char *barline = malloc(barline_bufsize);
if (!barline)
{
perror("malloc");
free(bar);
exit(1);
}
snprintf(barline, barline_bufsize, "[%-*s]", bar_length, ""); // Placeholder for padding
snprintf(barline, barline_bufsize, "[%-*s]", bar_length, ""); // for padding
// Content
// Determine drive type
const char *drive_type;
if (is_physical_device(entry->mnt_fsname))
{
drive_type = "Physical Drive";
drive_type = "Local Drive";
}
else if (is_network_filesystem(entry->mnt_type) || is_network_device(entry->mnt_fsname))
{
@@ -556,20 +807,20 @@ int main(int argc, char *argv[])
{
drive_type = "Other Drive";
}
printf(" " BOLD_YELLOW_FORMAT "%s %d" RESET_COLOR_FORMAT "\n", drive_type, drive_count + 1);
printf(" Mount point: %s\n", entry->mnt_dir);
printf(" Filesystem: %s\n", entry->mnt_type);
printf(" Device: %s\n", entry->mnt_fsname);
printf(" Total size: %s\n", total_str);
printf(" Used: %s\n", used_str);
printf(" Available: %s\n", available_str);
// Progress bar
int bar_visible_len = visible_length(bar);
int bar_padding = content_width - bar_visible_len;
printf(" %s%*s\n", bar, bar_padding, "");
free(bar);
free(barline);
// UUID und Label ermitteln
char uuid[128], label[128];
get_uuid_and_label(entry->mnt_fsname, uuid, sizeof(uuid), label, sizeof(label));
// Store information in drive_info_t structure
drive_info_t *drive = &drives[drive_count];
fill_drive_info(drive, entry->mnt_dir, entry->mnt_type, entry->mnt_fsname,
uuid, label,
total_str, used_str, available_str,
total_bytes, used_bytes, available_bytes,
usage_percent, drive_type, bar, false, NULL, entry->mnt_opts,
total_inodes, used_inodes, inode_usage);
drive_count++;
}
@@ -580,7 +831,67 @@ int main(int argc, char *argv[])
snprintf(gvfs_path, sizeof(gvfs_path), GVFS_BASE_PATH, getuid());
if (is_cloud_storage_directory(gvfs_path))
{
get_cloud_storage_info(gvfs_path, &drive_count);
get_cloud_storage_info(gvfs_path, drives, &drive_count);
}
// Sort drives by capacity (largest first)
qsort(drives, drive_count, sizeof(drive_info_t), compare_drives_by_capacity);
// Display sorted drives
for (int i = 0; i < drive_count; i++)
{
drive_info_t *drive = &drives[i];
// Display drive information
if (drive->is_cloud_storage)
{
printf(" " BOLD_YELLOW_FORMAT "Network Drive %d (%s)" RESET_COLOR_FORMAT "\n", i + 1, drive->cloud_service_name);
}
else
{
printf(" " BOLD_YELLOW_FORMAT "%s %d" RESET_COLOR_FORMAT "\n", drive->drive_type, i + 1);
}
printf(" Mount point: %s\n", drive->mount_point);
printf(" Filesystem: %s\n", drive->filesystem);
printf(" Device: %s\n", drive->device);
printf(" UUID: %s\n", drive->uuid[0] ? drive->uuid : "-");
printf(" Label: %s\n", drive->label[0] ? drive->label : "-");
printf(" Mount options: %s\n", drive->mount_options);
printf(" Total size: %s\n", drive->total_str);
printf(" Used: %s\n", drive->used_str);
printf(" Available: %s\n", drive->available_str);
printf(" Inodes: %llu/%llu (%.1f%% used)\n", drive->used_inodes, drive->total_inodes, drive->inode_usage);
// SMART status only for root and physical devices
if (geteuid() == 0 && !drive->is_cloud_storage && strcmp(drive->drive_type, "Local Drive") == 0)
{
char smart_status[128];
get_smart_status(drive->device, smart_status, sizeof(smart_status));
if (smart_status[0])
{
printf(" SMART: %s\n", smart_status);
}
else
{
printf(" SMART: No data\n");
}
}
// Progress bar
int terminal_width = get_terminal_width();
int box_width = terminal_width * TERMINAL_WIDTH_PERCENTAGE / TERMINAL_WIDTH_DIVISOR;
if (box_width > MAX_BOX_WIDTH)
box_width = MAX_BOX_WIDTH;
if (box_width < MIN_BOX_WIDTH)
box_width = MIN_BOX_WIDTH;
int content_width = box_width - FRAME_PADDING;
int bar_visible_len = visible_length(drive->progress_bar);
int bar_padding = content_width - bar_visible_len;
printf(" %s%*s\n", drive->progress_bar, bar_padding, "");
// Free allocated memory
free(drive->progress_bar);
}
if (drive_count == 0)
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 244 KiB