Replacing printf, putchar and snprintf in the hot path with custom functions

Replacing snprintf(), localtime() and strftime() with custom formating
functions

Reworking progress_thread(), instead of composing the progress printing
with multiple printf() and putchar, we compose it in a buffer and write
it at onse using WriteConsole in Windows and write in Linux
This commit is contained in:
2026-05-18 22:26:46 +01:00
parent 16c6aeae65
commit 4fac135dce

View File

@@ -19,7 +19,7 @@ static atomic_uint_fast64_t g_files_hashed = 0;
static atomic_uint_fast64_t g_bytes_processed = 0; static atomic_uint_fast64_t g_bytes_processed = 0;
static atomic_int g_scan_done = 0; static atomic_int g_scan_done = 0;
#define HASH_STRLEN 33 // 128-bit hex (32 chars) + null terminator #define HASH_STRLEN 32 // 128-bit hex
#define MAX_PATHLEN KiB(4) #define MAX_PATHLEN KiB(4)
// ================== OS-agnostic functions abstraction ===================== // ================== OS-agnostic functions abstraction =====================
// --------------------- Timer functions --------------------- // --------------------- Timer functions ---------------------
@@ -94,7 +94,7 @@ size_t platform_physical_cores(void) {
} }
#endif #endif
const char *get_xxhash_instruction_set(void) { char *get_xxhash_instruction_set(void) {
int vecID = XXH_featureTest(); int vecID = XXH_featureTest();
switch (vecID) { switch (vecID) {
@@ -120,7 +120,7 @@ typedef HANDLE FileHandle;
#define INVALID_FILE_HANDLE INVALID_HANDLE_VALUE #define INVALID_FILE_HANDLE INVALID_HANDLE_VALUE
// File open function // File open function
static FileHandle os_file_open(const char *path, DWORD flags) { static FileHandle os_file_open(char *path, DWORD flags) {
return CreateFileA(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, return CreateFileA(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, flags, NULL); NULL, OPEN_EXISTING, flags, NULL);
} }
@@ -144,7 +144,7 @@ typedef int FileHandle;
#define INVALID_FILE_HANDLE (-1) #define INVALID_FILE_HANDLE (-1)
// File open function // File open function
static FileHandle os_file_open(const char *path, int flags) { static FileHandle os_file_open(char *path, int flags) {
// Combine your mandatory flags with the user-provided flag // Combine your mandatory flags with the user-provided flag
int fd = open(path, O_RDONLY | O_NOFOLLOW | flags); int fd = open(path, O_RDONLY | O_NOFOLLOW | flags);
@@ -283,6 +283,67 @@ static int thread_wait_multiple(Thread *threads, size_t count) {
#endif #endif
// ======================== Get file metadata ======================== // ======================== Get file metadata ========================
// ----------------------------- Formaters -----------------------------
static void format_hash(char *dst, XXH128_hash_t h) {
static const char hex[] = "0123456789abcdef";
// High 64 bits
for (int i = 0; i < 16; i++) {
dst[i] = hex[(h.high64 >> ((15 - i) * 4)) & 0xF];
}
// Low 64 bits
for (int i = 0; i < 16; i++) {
dst[16 + i] = hex[(h.low64 >> ((15 - i) * 4)) & 0xF];
}
}
static void format_size_kib(char *dst, uint64_t size_bytes) {
// Reserve worst-case space
uint64_t kib_int = size_bytes / 1024;
uint64_t frac = ((size_bytes % 1024) * 100ULL) / 1024ULL;
char *p = dst;
// Count digits
uint64_t tmp = kib_int;
int digits = 1;
while (tmp >= 10) {
tmp /= 10;
digits++;
}
// Write integer part directly
p += digits;
uint64_t v = kib_int;
do {
*--p = (char)('0' + (v % 10));
v /= 10;
} while (v);
p = dst + digits;
// Decimal part
*p++ = '.';
*p++ = (char)('0' + (frac / 10));
*p++ = (char)('0' + (frac % 10));
*p = 0;
}
static void str_copy(char *dst, const char *src, size_t dst_size) {
size_t len = strlen(src);
if (len >= dst_size) {
len = dst_size - 1;
}
memcpy(dst, src, len);
dst[len] = '\0';
}
// -------------------- Path helpers ------------------- // -------------------- Path helpers -------------------
static void normalize_path(char *p) { static void normalize_path(char *p) {
char *src = p; char *src = p;
@@ -376,7 +437,7 @@ typedef struct {
size_t base_len; size_t base_len;
} PathBuilder; } PathBuilder;
static void path_builder_init(PathBuilder *pb, const char *base) { static void path_builder_init(PathBuilder *pb, char *base) {
pb->base_len = strlen(base); pb->base_len = strlen(base);
memcpy(pb->buffer, base, pb->base_len); memcpy(pb->buffer, base, pb->base_len);
pb->base_end = pb->buffer + pb->base_len; pb->base_end = pb->buffer + pb->base_len;
@@ -393,7 +454,7 @@ static void path_builder_init(PathBuilder *pb, const char *base) {
pb->filename_pos = pb->base_end; pb->filename_pos = pb->base_end;
} }
static void path_builder_set_filename(PathBuilder *pb, const char *filename, static void path_builder_set_filename(PathBuilder *pb, char *filename,
size_t name_len) { size_t name_len) {
memcpy(pb->filename_pos, filename, name_len); memcpy(pb->filename_pos, filename, name_len);
pb->filename_pos[name_len] = '\0'; // Ensure null termination pb->filename_pos[name_len] = '\0'; // Ensure null termination
@@ -412,23 +473,111 @@ static char *path_builder_dup_arena(PathBuilder *pb, mem_arena *arena,
// ------------------------- File time ------------------------- // ------------------------- File time -------------------------
#if FILE_TIMES #if FILE_TIMES
#if defined(_WIN32) || defined(_WIN64) static int is_leap_year(int year) {
static void format_time(uint64_t t, char *out, size_t out_sz) { return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
if (t == 0) { }
snprintf(out, out_sz, "N/A");
static void format_time(uint64_t unix_time,
char *out) { // Format time will output UTC time
if (unix_time == 0) {
memcpy(out, "N/A", 3);
return; return;
} }
time_t tt = (time_t)t; static const int days_per_month[12] = {31, 28, 31, 30, 31, 30,
struct tm tm; 31, 31, 30, 31, 30, 31};
localtime_s(&tm, &tt); uint64_t t = unix_time;
strftime(out, out_sz, "%Y-%m-%d %H:%M:%S", &tm); // ---------------------------
// Time of day
// ---------------------------
uint64_t secs_of_day = t % 86400ULL;
int hour = (int)(secs_of_day / 3600ULL);
int minute = (int)((secs_of_day % 3600ULL) / 60ULL);
int second = (int)(secs_of_day % 60ULL);
// ---------------------------
// Days since epoch
// ---------------------------
uint64_t days = t / 86400ULL;
int year = 1970;
while (1) {
int year_days = is_leap_year(year) ? 366 : 365;
if (days < (uint64_t)year_days)
break;
days -= year_days;
year++;
} }
// ---------------------------
// Month/day
// ---------------------------
int month = 0;
while (1) {
int dim = days_per_month[month];
if (month == 1 && is_leap_year(year))
dim = 29;
if (days < (uint64_t)dim)
break;
days -= dim;
month++;
}
int day = (int)days + 1;
// ---------------------------
// YYYY-MM-DD HH:MM:SS
// exactly 19 chars
// ---------------------------
out[0] = '0' + ((year / 1000) % 10);
out[1] = '0' + ((year / 100) % 10);
out[2] = '0' + ((year / 10) % 10);
out[3] = '0' + (year % 10);
out[4] = '-';
int mon = month + 1;
out[5] = '0' + (mon / 10);
out[6] = '0' + (mon % 10);
out[7] = '-';
out[8] = '0' + (day / 10);
out[9] = '0' + (day % 10);
out[10] = ' ';
out[11] = '0' + (hour / 10);
out[12] = '0' + (hour % 10);
out[13] = ':';
out[14] = '0' + (minute / 10);
out[15] = '0' + (minute % 10);
out[16] = ':';
out[17] = '0' + (second / 10);
out[18] = '0' + (second % 10);
// out[19] = '\0';
}
#if defined(_WIN32) || defined(_WIN64)
// ------------------ Convert filetime to epoch ------------------- // ------------------ Convert filetime to epoch -------------------
static uint64_t filetime_to_epoch(const FILETIME *ft) { static uint64_t filetime_to_epoch(FILETIME *ft) {
ULARGE_INTEGER ull; ULARGE_INTEGER ull;
ull.LowPart = ft->dwLowDateTime; ull.LowPart = ft->dwLowDateTime;
ull.HighPart = ft->dwHighDateTime; ull.HighPart = ft->dwHighDateTime;
@@ -437,7 +586,7 @@ static uint64_t filetime_to_epoch(const FILETIME *ft) {
return (ull.QuadPart - 116444736000000000ULL) / 10000000ULL; return (ull.QuadPart - 116444736000000000ULL) / 10000000ULL;
} }
void platform_get_file_times(const char *path, uint64_t *out_created, void platform_get_file_times(char *path, uint64_t *out_created,
uint64_t *out_modified) { uint64_t *out_modified) {
WIN32_FILE_ATTRIBUTE_DATA fad; WIN32_FILE_ATTRIBUTE_DATA fad;
if (GetFileAttributesExA(path, GetFileExInfoStandard, &fad)) { if (GetFileAttributesExA(path, GetFileExInfoStandard, &fad)) {
@@ -450,21 +599,7 @@ void platform_get_file_times(const char *path, uint64_t *out_created,
} }
#elif defined(__linux__) #elif defined(__linux__)
static void format_time(uint64_t t, char *out, size_t out_sz) { void platform_get_file_times(char *path, uint64_t *out_created,
if (t == 0) {
snprintf(out, out_sz, "N/A");
return;
}
time_t tt = (time_t)t;
struct tm tm;
localtime_r(&tt, &tm);
strftime(out, out_sz, "%Y-%m-%d %H:%M:%S", &tm);
}
void platform_get_file_times(const char *path, uint64_t *out_created,
uint64_t *out_modified) { uint64_t *out_modified) {
struct stat st; struct stat st;
if (stat(path, &st) == 0) { if (stat(path, &st) == 0) {
@@ -476,8 +611,8 @@ void platform_get_file_times(const char *path, uint64_t *out_created,
} }
} }
static int platform_get_file_times_fd(int dir_fd, const char *name, static int platform_get_file_times_fd(int dir_fd, char *name, uint64_t *created,
uint64_t *created, uint64_t *modified) { uint64_t *modified) {
struct stat st; struct stat st;
if (fstatat(dir_fd, name, &st, 0) == 0) { if (fstatat(dir_fd, name, &st, 0) == 0) {
*created = st.st_ctime; // or st.st_birthtime on systems that support it *created = st.st_ctime; // or st.st_birthtime on systems that support it
@@ -492,7 +627,7 @@ static int platform_get_file_times_fd(int dir_fd, const char *name,
// -------------------- File owner --------------------- // -------------------- File owner ---------------------
#if defined(_WIN32) || defined(_WIN64) #if defined(_WIN32) || defined(_WIN64)
#if FILE_OWNER #if FILE_OWNER
void platform_get_file_owner(const char *path, char *out_owner, void platform_get_file_owner(char *path, char *out_owner,
size_t out_owner_size) { size_t out_owner_size) {
PSID sid = NULL; PSID sid = NULL;
PSECURITY_DESCRIPTOR sd = NULL; PSECURITY_DESCRIPTOR sd = NULL;
@@ -507,12 +642,23 @@ void platform_get_file_owner(const char *path, char *out_owner,
if (LookupAccountSidA(NULL, sid, name, &name_len, domain, &domain_len, if (LookupAccountSidA(NULL, sid, name, &name_len, domain, &domain_len,
&use)) { &use)) {
snprintf(out_owner, out_owner_size, "%s\\%s", domain, name); // Format: "domain\name"
size_t domain_len_actual = strlen(domain);
size_t name_len_actual = strlen(name);
size_t total = domain_len_actual + 1 + name_len_actual;
if (total < out_owner_size) {
memcpy(out_owner, domain, domain_len_actual);
out_owner[domain_len_actual] = '\\';
memcpy(out_owner + domain_len_actual + 1, name, name_len_actual + 1);
} else { } else {
snprintf(out_owner, out_owner_size, "UNKNOWN"); str_copy(out_owner, domain, out_owner_size);
} }
} else { } else {
snprintf(out_owner, out_owner_size, "UNKNOWN"); str_copy(out_owner, "UNKNOWN", out_owner_size);
}
} else {
str_copy(out_owner, "UNKNOWN", out_owner_size);
} }
if (sd) if (sd)
@@ -522,7 +668,7 @@ void platform_get_file_owner(const char *path, char *out_owner,
#elif defined(__linux__) #elif defined(__linux__)
#if FILE_OWNER #if FILE_OWNER
void platform_get_file_owner(const char *path, char *out_owner, void platform_get_file_owner(char *path, char *out_owner,
size_t out_owner_size) { size_t out_owner_size) {
struct stat st; struct stat st;
const char *owner = "UNKNOWN"; const char *owner = "UNKNOWN";
@@ -534,25 +680,41 @@ void platform_get_file_owner(const char *path, char *out_owner,
} }
} }
snprintf(out_owner, out_owner_size, "%s", owner); str_copy(out_owner, owner, out_owner_size);
} }
static int platform_get_file_owner_fd(int dir_fd, const char *name, char *owner, static int platform_get_file_owner_fd(int dir_fd, char *name, char *owner,
size_t owner_size) { size_t owner_size) {
struct stat st; struct stat st;
if (fstatat(dir_fd, name, &st, 0) == 0) { if (fstatat(dir_fd, name, &st, 0) == 0) {
struct passwd pw; struct passwd pw;
struct passwd *result; struct passwd *result;
char buffer[4096]; // Sufficiently large buffer for passwd data char buffer[4096];
// Reentrant version (thread-safe)
if (getpwuid_r(st.st_uid, &pw, buffer, sizeof(buffer), &result) == 0 && if (getpwuid_r(st.st_uid, &pw, buffer, sizeof(buffer), &result) == 0 &&
result != NULL && result->pw_name != NULL) { result != NULL && result->pw_name != NULL) {
strncpy(owner, result->pw_name, owner_size - 1); str_copy(owner, result->pw_name, owner_size);
owner[owner_size - 1] = '\0';
} else { } else {
// Fallback to uid // Format: "uid:12345"
snprintf(owner, owner_size, "uid:%d", st.st_uid); char tmp[16];
char *tp = tmp + sizeof(tmp) - 1;
uint32_t uid = st.st_uid;
*tp = '\0';
do {
*--tp = (char)('0' + (uid % 10));
uid /= 10;
} while (uid > 0);
size_t prefix_len = 4; // "uid:"
if (prefix_len < owner_size) {
memcpy(owner, "uid:", prefix_len);
size_t uid_len = strlen(tp);
size_t remain = owner_size - prefix_len;
size_t copy_len = uid_len < remain ? uid_len : remain - 1;
memcpy(owner + prefix_len, tp, copy_len);
owner[prefix_len + copy_len] = '\0';
}
} }
return 0; return 0;
} }
@@ -681,7 +843,7 @@ typedef struct FileEntry {
} FileEntry; } FileEntry;
#if defined(_WIN32) || defined(_WIN64) #if defined(_WIN32) || defined(_WIN64)
void scan_folder(const char *base, ScannerContext *ctx) { void scan_folder(char *base, ScannerContext *ctx) {
PathBuilder pb; PathBuilder pb;
path_builder_init(&pb, base); path_builder_init(&pb, base);
@@ -743,7 +905,7 @@ void scan_folder(const char *base, ScannerContext *ctx) {
} }
#elif defined(__linux__) #elif defined(__linux__)
void scan_folder(const char *base, ScannerContext *ctx) { void scan_folder(char *base, ScannerContext *ctx) {
PathBuilder pb; PathBuilder pb;
path_builder_init(&pb, base); path_builder_init(&pb, base);
@@ -892,7 +1054,7 @@ static THREAD_RETURN scan_worker(void *arg) {
} }
// ----------------------------- Hashing helpers ----------------------------- // ----------------------------- Hashing helpers -----------------------------
static void xxh3_hash_file_stream(const char *path, char *out_hex, static void xxh3_hash_file_stream(char *path, char *out_hex,
unsigned char *buf) { unsigned char *buf) {
XXH128_hash_t h; XXH128_hash_t h;
XXH3_state_t state; XXH3_state_t state;
@@ -914,14 +1076,14 @@ static void xxh3_hash_file_stream(const char *path, char *out_hex,
os_file_close(handle); os_file_close(handle);
h = XXH3_128bits_digest(&state); h = XXH3_128bits_digest(&state);
snprintf(out_hex, HASH_STRLEN, "%016llx%016llx", (unsigned long long)h.high64, format_hash(out_hex, h);
(unsigned long long)h.low64);
} }
// ------------------------- Hash worker -------------------------------- // ------------------------- Hash worker --------------------------------
static THREAD_RETURN hash_worker(void *arg) { static THREAD_RETURN hash_worker(void *arg) {
HasherContext *ctx = (HasherContext *)arg; HasherContext *ctx = (HasherContext *)arg;
void *buf = malloc(READ_BLOCK); void *buf = malloc(READ_BLOCK);
char *separator;
for (;;) { for (;;) {
FileEntry *fe = mpmc_pop(ctx->file_queue); FileEntry *fe = mpmc_pop(ctx->file_queue);
@@ -931,7 +1093,8 @@ static THREAD_RETURN hash_worker(void *arg) {
// Hash // Hash
char *hash = arena_push(&ctx->arena, HASH_STRLEN, false); char *hash = arena_push(&ctx->arena, HASH_STRLEN, false);
xxh3_hash_file_stream(fe->path, hash, buf); xxh3_hash_file_stream(fe->path, hash, buf);
arena_trim_string(&ctx->arena, hash, ARENA_TRIM_TAB); separator = arena_push(&ctx->arena, 1, false);
*separator = '\t';
// Path // Path
u64 path_len = strlen(fe->path) + 1; u64 path_len = strlen(fe->path) + 1;
@@ -940,27 +1103,22 @@ static THREAD_RETURN hash_worker(void *arg) {
arena_trim_string(&ctx->arena, path, ARENA_TRIM_TAB); arena_trim_string(&ctx->arena, path, ARENA_TRIM_TAB);
// Size // Size
double size_kib = (double)fe->size_bytes / 1024.0;
char *size = arena_push(&ctx->arena, 32, false); char *size = arena_push(&ctx->arena, 32, false);
snprintf(size, 32, "%.2f", size_kib); format_size_kib(size, fe->size_bytes);
arena_trim_string(&ctx->arena, size, ARENA_TRIM_NONE); arena_trim_string(&ctx->arena, size, ARENA_TRIM_NONE);
// Times // Times
char *separator;
#if FILE_TIMES #if FILE_TIMES
separator = arena_push(&ctx->arena, 1, false); separator = arena_push(&ctx->arena, 1, false);
*separator = '\t'; *separator = '\t';
u64 time_size = 32; char *created = arena_push(&ctx->arena, 19, false);
format_time(fe->created_time, created);
separator = arena_push(&ctx->arena, 1, false);
*separator = '\t';
char *created = arena_push(&ctx->arena, time_size, false); char *modified = arena_push(&ctx->arena, 19, false);
format_time(fe->created_time, created, time_size); format_time(fe->modified_time, modified);
arena_trim_string(&ctx->arena, created, ARENA_TRIM_TAB);
char *modified = arena_push(&ctx->arena, time_size, false);
format_time(fe->modified_time, modified, time_size);
arena_trim_string(&ctx->arena, modified, ARENA_TRIM_NONE);
#endif #endif
// Owner // Owner
@@ -986,21 +1144,103 @@ static THREAD_RETURN hash_worker(void *arg) {
} }
// ------------------------- Progress display --------------------------- // ------------------------- Progress display ---------------------------
// ============================================================
// Console abstraction
// ============================================================
#if defined(_WIN32) || defined(_WIN64)
static HANDLE g_console_handle = NULL;
static void console_init(void) {
g_console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
}
static void console_write(const char *buf, size_t len) {
DWORD written;
WriteConsoleA(g_console_handle, buf, (DWORD)len, &written, NULL);
}
#elif defined(__linux__)
static void console_init(void) {}
static void console_write(const char *buf, size_t len) {
write(STDOUT_FILENO, buf, len);
}
#endif
// -------------------------- Append helpers ---------------------------
static inline void buf_append_char(char **p, char c) { *(*p)++ = c; }
static inline void buf_append_str(char **p, const char *s) {
while (*s) {
*(*p)++ = *s++;
}
}
static inline void buf_append_u64(char **p, uint64_t v) {
char tmp[32];
int n = 0;
do {
tmp[n++] = (char)('0' + (v % 10));
v /= 10;
} while (v);
while (n--) {
*(*p)++ = tmp[n];
}
}
static inline void buf_append_size_kib(char **p, uint64_t bytes) {
uint64_t kib_int = bytes / 1024;
uint64_t frac = ((bytes % 1024) * 100ULL) / 1024ULL;
buf_append_u64(p, kib_int);
*(*p)++ = '.';
*(*p)++ = (char)('0' + (frac / 10));
*(*p)++ = (char)('0' + (frac % 10));
}
static inline void buf_append_percent_2(char **p, uint64_t num, uint64_t den) {
if (den == 0) {
buf_append_str(p, "0.00");
return;
}
uint64_t scaled = (num * 10000ULL) / den;
uint64_t whole = scaled / 100;
uint64_t frac = scaled % 100;
buf_append_u64(p, whole);
*(*p)++ = '.';
*(*p)++ = (char)('0' + (frac / 10));
*(*p)++ = (char)('0' + (frac % 10));
}
static THREAD_RETURN progress_thread(void *arg) { static THREAD_RETURN progress_thread(void *arg) {
(void)arg; (void)arg;
console_init();
HiResTimer progress_timer; HiResTimer progress_timer;
timer_start(&progress_timer); timer_start(&progress_timer);
uint64_t last_bytes = 0; uint64_t last_bytes = 0;
double last_time = 0.0; double last_time = 0.0;
double displayed_speed = 0.0; double displayed_speed = 0.0;
const double sample_interval = 0.5; double sample_interval = 0.5;
// Hide cursor to prevent flickering console_write("\033[?25l", 6);
printf("\033[?25l");
for (;;) { for (;;) {
uint64_t found = atomic_load(&g_files_found); uint64_t found = atomic_load(&g_files_found);
uint64_t hashed = atomic_load(&g_files_hashed); uint64_t hashed = atomic_load(&g_files_hashed);
uint64_t bytes = atomic_load(&g_bytes_processed); uint64_t bytes = atomic_load(&g_bytes_processed);
@@ -1011,47 +1251,91 @@ static THREAD_RETURN progress_thread(void *arg) {
if (dt >= sample_interval) { if (dt >= sample_interval) {
uint64_t db = (bytes > last_bytes) ? bytes - last_bytes : 0; uint64_t db = (bytes > last_bytes) ? bytes - last_bytes : 0;
displayed_speed = (double)db / (1024.0 * 1024.0) / dt; displayed_speed = (double)db / (1024.0 * 1024.0) / dt;
last_bytes = bytes; last_bytes = bytes;
last_time = t; last_time = t;
} }
printf("\r"); uint64_t speed_x100 = (uint64_t)(displayed_speed * 100.0);
char buffer[512];
char *p = buffer;
buf_append_char(&p, '\r');
if (!scan_done) { if (!scan_done) {
printf("\033[1mScanning:\033[0m %llu files | Hashed: %llu | \033[32m%.2f "
"MB/s\033[0m ", buf_append_str(&p, "\033[1mScanning:\033[0m ");
(unsigned long long)found, (unsigned long long)hashed,
displayed_speed); buf_append_u64(&p, found);
buf_append_str(&p, " files | Hashed: ");
buf_append_u64(&p, hashed);
buf_append_str(&p, " | \033[32m");
buf_append_u64(&p, speed_x100 / 100);
buf_append_char(&p, '.');
buf_append_char(&p, '0' + ((speed_x100 / 10) % 10));
buf_append_char(&p, '0' + (speed_x100 % 10));
buf_append_str(&p, " MB/s\033[0m ");
} else { } else {
double pct = found ? (double)hashed / (double)found : 0.0;
int barw = 40; int barw = 40;
int filled = (int)(pct * barw); int filled = found ? (int)((hashed * barw) / found) : 0;
buf_append_char(&p, '[');
buf_append_str(&p, "\033[32m");
printf("[");
// Print filled part in Green (\033[32m)
printf("\033[32m");
for (int i = 0; i < filled; i++) for (int i = 0; i < filled; i++)
putchar('#'); buf_append_char(&p, '#');
// Reset color for empty part
printf("\033[0m");
for (int i = filled; i < barw; i++)
putchar('.');
printf("] %6.2f%% (%llu/%llu) \033[32m%.2f MB/s\033[0m ", pct * 100.0, buf_append_str(&p, "\033[0m");
(unsigned long long)hashed, (unsigned long long)found,
displayed_speed); for (int i = filled; i < barw; i++)
buf_append_char(&p, '.');
buf_append_str(&p, "] ");
buf_append_percent_2(&p, hashed, found);
buf_append_str(&p, "% (");
buf_append_u64(&p, hashed);
buf_append_char(&p, '/');
buf_append_u64(&p, found);
buf_append_str(&p, ") \033[32m");
buf_append_u64(&p, speed_x100 / 100);
buf_append_char(&p, '.');
buf_append_char(&p, '0' + ((speed_x100 / 10) % 10));
buf_append_char(&p, '0' + (speed_x100 % 10));
buf_append_str(&p, " MB/s\033[0m ");
} }
fflush(stdout); console_write(buffer, (size_t)(p - buffer));
if (scan_done && hashed == found) if (scan_done && hashed == found)
break; break;
sleep_ms(100); sleep_ms(100);
} }
// Restore cursor (\033[?25h) and move to next line console_write("\033[?25h\n", 7);
printf("\033[?25h\n");
return THREAD_RETURN_VALUE; return THREAD_RETURN_VALUE;
} }
@@ -1873,6 +2157,7 @@ static void finalize_file(ThreadIoContext *restrict thread_ctx,
FileReadContext *restrict file) { FileReadContext *restrict file) {
FileEntry *restrict fe = file->fe; FileEntry *restrict fe = file->fe;
char *separator;
os_file_close(file->file_handle); os_file_close(file->file_handle);
@@ -1882,13 +2167,10 @@ static void finalize_file(ThreadIoContext *restrict thread_ctx,
if (file->use_incremental_hash) { if (file->use_incremental_hash) {
// Large file: digest the accumulated hash state // Large file: digest the accumulated hash state
XXH128_hash_t h = XXH3_128bits_digest(&file->hash_state); XXH128_hash_t h = XXH3_128bits_digest(&file->hash_state);
snprintf(hash, HASH_STRLEN, "%016llx%016llx", format_hash(hash, h);
(unsigned long long)h.high64, (unsigned long long)h.low64);
} else { } else {
// Small file: hash already computed, stored directly in single_hash // Small file: hash already computed, stored directly in single_hash
snprintf(hash, HASH_STRLEN, "%016llx%016llx", format_hash(hash, file->single_hash);
(unsigned long long)file->single_hash.high64,
(unsigned long long)file->single_hash.low64);
} }
} else { } else {
#if IORING_DEBUG_PRINTS #if IORING_DEBUG_PRINTS
@@ -1898,34 +2180,31 @@ static void finalize_file(ThreadIoContext *restrict thread_ctx,
atomic_fetch_add(&g_io_ring_fallbacks, 1); atomic_fetch_add(&g_io_ring_fallbacks, 1);
xxh3_hash_file_stream(fe->path, hash, thread_ctx->fallback_buffer); xxh3_hash_file_stream(fe->path, hash, thread_ctx->fallback_buffer);
} }
arena_trim_string(&worker_ctx->arena, hash, ARENA_TRIM_TAB); separator = arena_push(&worker_ctx->arena, 1, false);
*separator = '\t';
// Path // Path
u64 path_len = strlen(fe->path) + 1; char *path = arena_push(&worker_ctx->arena, MAX_PATHLEN, ARENA_TRIM_TAB);
char *path = arena_push(&worker_ctx->arena, path_len, ARENA_TRIM_TAB); memcpy(path, fe->path, MAX_PATHLEN);
memcpy(path, fe->path, path_len);
arena_trim_string(&worker_ctx->arena, path, ARENA_TRIM_TAB); arena_trim_string(&worker_ctx->arena, path, ARENA_TRIM_TAB);
// Size // Size
double size_kib = (double)fe->size_bytes / 1024.0;
char *size = arena_push(&worker_ctx->arena, 32, false); char *size = arena_push(&worker_ctx->arena, 32, false);
snprintf(size, 32, "%.2f", size_kib); format_size_kib(size, fe->size_bytes);
arena_trim_string(&worker_ctx->arena, size, ARENA_TRIM_NONE); arena_trim_string(&worker_ctx->arena, size, ARENA_TRIM_NONE);
// Time // Time
char *separator;
#if FILE_TIMES #if FILE_TIMES
separator = arena_push(&worker_ctx->arena, 1, false); separator = arena_push(&worker_ctx->arena, 1, false);
*separator = '\t'; *separator = '\t';
u64 time_size = 32; char *created = arena_push(&worker_ctx->arena, 19, false);
char *created = arena_push(&worker_ctx->arena, time_size, false); format_time(fe->created_time, created);
format_time(fe->created_time, created, time_size); separator = arena_push(&worker_ctx->arena, 1, false);
arena_trim_string(&worker_ctx->arena, created, ARENA_TRIM_TAB); *separator = '\t';
char *modified = arena_push(&worker_ctx->arena, time_size, false); char *modified = arena_push(&worker_ctx->arena, 19, false);
format_time(fe->modified_time, modified, time_size); format_time(fe->modified_time, modified);
arena_trim_string(&worker_ctx->arena, modified, ARENA_TRIM_NONE);
#endif #endif
// Owner // Owner
@@ -1955,7 +2234,7 @@ static void hash_ready_files(ThreadIoContext *restrict thread_ctx,
if (!file || file->completed) if (!file || file->completed)
continue; continue;
// ---- HASH READY BUFFERS IN ORDER ---- // ---- HASH READY BUFFERS ----
while (file->head) { while (file->head) {
IoBuffer *restrict buf = file->head; IoBuffer *restrict buf = file->head;
@@ -1990,9 +2269,6 @@ static void hash_ready_files(ThreadIoContext *restrict thread_ctx,
} else if (buf->bytes_read == 0 && IORING_SUCCEEDED(buf->result)) { } else if (buf->bytes_read == 0 && IORING_SUCCEEDED(buf->result)) {
file->reads_hashed++; // EOF file->reads_hashed++; // EOF
} else {
finalize_file(thread_ctx, worker_ctx, file);
file->completed = true;
} }
return_buffer(thread_ctx, buf); return_buffer(thread_ctx, buf);
@@ -2008,7 +2284,7 @@ static void hash_ready_files(ThreadIoContext *restrict thread_ctx,
} }
} }
// Clean up completed files from the head // Clean up completed files
fq_trim(fq); fq_trim(fq);
} }