Skip to content

Instantly share code, notes, and snippets.

@skull-squadron
Last active December 9, 2025 13:12
Show Gist options
  • Select an option

  • Save skull-squadron/955f0a1e21e1356c1a6ba4f48afcd935 to your computer and use it in GitHub Desktop.

Select an option

Save skull-squadron/955f0a1e21e1356c1a6ba4f48afcd935 to your computer and use it in GitHub Desktop.
dufff - Faster and betterer than `du -sh` and `find -printf . | wc -c`
/* Output like:
$ duff some/dir
files: 86095
dirs: 2799
other: 0
bytes: 7.81 GiB (8383140523)
$
*/
#include <dirent.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <locale.h>
static const char *const UNITS[] = {"", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"};
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
#define BYTES_FMT PRIu64
typedef uint64_t bytes_t;
// Evil globals variables, now for everyone to share
uint32_t dirs = 0;
uint32_t files = 0;
uint32_t other = 0;
bytes_t bytes = 0;
struct dirent *entry;
struct stat st;
static int count_files(const char *dir) {
DIR *d = opendir(dir);
if (!d) {
perror("opendir");
fprintf(stderr, "Could not open directory '%s'\n", dir);
return -1;
}
++dirs;
while ((entry = readdir(d))) {
switch (entry->d_type) {
case DT_DIR: {
if (entry->d_name[0] == '.')
if (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))
continue;
const size_t dir_len = strlen(dir);
const size_t entry_len = strlen(entry->d_name);
char new_path[dir_len + 1 /* / */ + entry_len + 1 /* \0 */];
memcpy(new_path, dir, dir_len);
new_path[dir_len] = '/';
memcpy(&new_path[dir_len + 1], entry->d_name, entry_len + 1);
if (count_files(new_path) != 0) {
closedir(d);
return -1;
}
} break;
case DT_REG: {
++files;
const size_t dir_len = strlen(dir);
const size_t entry_len = strlen(entry->d_name);
char new_path[dir_len + entry_len + 2];
memcpy(new_path, dir, dir_len);
new_path[dir_len] = '/';
memcpy(&new_path[dir_len + 1], entry->d_name, entry_len + 1);
if (lstat(new_path, &st) != 0) {
perror("lstat");
fprintf(stderr, "Could not lstat('%s')\n", new_path);
closedir(d);
return -1;
}
bytes += st.st_size;
} break;
default:
++other;
break;
}
}
closedir(d);
return 0;
}
static void humanize_bytes(bytes_t bytes, char *output) {
size_t unit_index = 0;
double humanized_size = (double)bytes;
for (; humanized_size >= 1024 && unit_index < ARRAY_SIZE(UNITS) - 1; ++unit_index)
humanized_size /= 1024;
sprintf(output, "%.2f%*s", humanized_size, (unit_index == 0) ? 0 : 4,
UNITS[unit_index]);
}
int main(int argc, char *argv[]) {
char humanized[60];
const char *const dir_name = (argc < 2) ? "." : argv[1];
if (count_files(dir_name) != 0) exit(EXIT_FAILURE);
setlocale(LC_NUMERIC, "");
humanize_bytes(bytes, humanized);
printf(
"files: %" PRIu32 "\n"
" dirs: %" PRIu32 "\n"
"other: %" PRIu32 "\n"
"bytes: %s (%" BYTES_FMT ")\n",
files, dirs, other, humanized, bytes);
return EXIT_SUCCESS;
}
CC ?= cc
CFLAGS ?= -DNDEBUG -std=c99 -O3
dufff: dufff.c
$(CC) $(CFLAGS) dufff.c -o dufff
@skull-squadron
Copy link
Author

Added locale support so French, Italian, etc. see proper decimal seps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment