/* SPDX-License-Identifier: Apache-2.0 */ /** * Copyright 2026 Thorsten Töpper * * @file directory_scanner.c * * vim:ts=4:sw=4:expandtab */ #include #include #include #include #include #include #include #include #include #include "options.h" #include "directory_scanner.h" #include "kv_manager.h" #include "trace_macros.h" /*=========== DEFINES, CONSTANTS AND TYPES ===========*/ struct path_stack { struct path_stack *next; char *fullpath; }; /*=========== GLOBAL VARIABLES ===========*/ struct path_stack *pstack = NULL; /*=========== FUNCTIONS ===========*/ int pstack_push(char *s); char *pstack_pop(); inline int pstack_push(char *s) { struct path_stack *node; if (s == NULL || s[0] == '\0') { LOGERR("ERROR: Empty path\n"); return -1; } DBGTRC("DEBUG: push %s to pstack\n", s); if ((node = calloc(1, sizeof(struct path_stack))) == NULL) { LOGERR("ERROR: Failed to allocate 16bytes...\n"); return -1; } node->next = pstack; node->fullpath = s; pstack = node; return 0; } inline char *pstack_pop() { struct path_stack *node = pstack; char *s; if (pstack == NULL) return NULL; DBGTRC("DEBUG: pop %s from pstack\n", node->fullpath); s = node->fullpath; pstack = node->next; free(node); return s; } /** * Traverse the directory tree from the given starting point and add everything to * the gdbm storage. * * @param starting_point The directory where the travel begins. If NULL or empty the current * path "." is used. * @return 0 on success * <0 on failure */ int traverse_directory_tree(const char *starting_point) { char *tmp; int rc = 0; if (starting_point == NULL || starting_point[0] == '\0') { LOGERR("WARNING: No starting point given, begin at \".\"\n"); tmp = calloc(2, 1); tmp[0]= '.'; } else { tmp = calloc(1, strlen(starting_point)+1); memcpy(tmp, starting_point, strlen(starting_point)); } /* In case there are still entries on an existing pstack, take them with you */ while (tmp != NULL) { if (! option_quiet) { LOGERR("Read directory %s\n", tmp); } DBGTRC("DEBUG: process directory %s next\n", tmp); rc = process_directory(tmp); if (rc < 0) { return -1; } if (tmp != starting_point) { free(tmp); } tmp = pstack_pop(); } return 0; } /** * Read directory the content is to be stored in a key:value storage with value being * a boolean flagging the processment state. At the end flag the own entry as true. * * @param path struct contains the path of the file to read, results will be * stored there. * * @return 0 on success * -1 on failure */ int process_directory(char *path) { char *fullpath = NULL, *fname_in_path = NULL, *stack_entry = NULL; char type = 0; size_t path_length = 0, dnamelen = 0; DIR *dir; struct dirent *de = NULL; struct stat stat_res; /* February 2026: Modern filesystems usually have their maximal fullpath length * at 4096 bytes. At least to my knowledge, so if this changes the checks need * to be corrected. */ if (path == NULL || ((path_length = strnlen(path,4100)) == 0)) { LOGERR("ERROR: No path given.\n"); return -1; } if (path_length > 4096) { LOGERR("ERROR: path longer than 4096 byte.\n"); return -1; } /* 256 byte max filename + path separator + \0 */ if ((fullpath = calloc(path_length+258, sizeof(char))) == NULL) { LOGERR("ERROR: Failed to allocate memory for fullpath.\n"); return -1; } sprintf(fullpath, "%s%c", path, ((path[path_length-1] == PATH_SEP) ? '\0' : PATH_SEP)); fname_in_path = (fullpath[path_length] == PATH_SEP) ? &(fullpath[path_length+1]) : &(fullpath[path_length]) ; if ((dir = opendir(path)) == NULL) { LOGERR("ERROR: Failed to open directory '%s': %s (errno %d)\n", path, strerror(errno), errno); free(fullpath); return -1; } while ((de = readdir(dir)) != NULL) { /* Current dir . and parent dir .. everywhere, * invisible files on UNIXoid filesystems*/ if (de->d_name[0] == '.') { if (option_show_hidden_entries == false) { continue; } dnamelen = strlen(de->d_name); if ((dnamelen == 1) || (dnamelen == 2 && de->d_name[1] == '.')) { continue; } } sprintf(fname_in_path, "%s", de->d_name); DBGTRC("DEBUG: fullpath: '%s'\n", fullpath); if (lstat(fullpath, &stat_res) != 0) { LOGERR("ERROR: lstat call on '%s' failed: %s (errno %d)\n", fullpath, strerror(errno), errno); continue; } if (S_ISDIR(stat_res.st_mode)) { type = 'D'; if ((stack_entry=calloc(1, strlen(fullpath)+1)) == NULL) { LOGERR("ERROR: memory allocation failed\n"); /* saving cleanup, this error is a crash either way. With this * location in the code may only be reached in a debugger. */ return -1; } memcpy(stack_entry, fullpath, strlen(fullpath)); pstack_push(stack_entry); } else if (S_ISREG(stat_res.st_mode)) { type = 'F'; } else { DBGTRC("DEBUG: Not a file or directory ... ignoring.\n"); continue; } /* Don't overwrite earlier runs */ if (!kv_entry_exists(fullpath)) { /* Ignore errors, missing entries shall show up in the error log * and require manual intervention */ kv_add_bool_type(fullpath, false, type); } } free(fullpath); closedir(dir); if (!kv_entry_exists(path)) { if (path[0] == '.' && option_show_hidden_entries == false) { return 0; } kv_add_bool_type(path, true, 'D'); } else { kv_set_bool(path, true); } return 0; }