/* SPDX-License-Identifier: Apache-2.0 */ /* Copyright 2025 Thorsten Töpper * * dir_monitor - print specified stat() information from entries of a given * directory to stdout. Call it via watch for repeated output to a terminal. * * vim:ts=4:sw=4:expandtab */ #include #include #include #include #include #include #include #include #include #include #define LOGERR(...) {fprintf(stderr, "[%s:%d] %s: ", __FILE__, __LINE__, __func__); fprintf(stderr, __VA_ARGS__);} #ifdef DEBUGBUILD #define DBGTRC(...) LOGERR(__VA_ARGS__) #else #define DBGTRC(...) #endif /* === DEFINITIONS === */ #define PATH_SEP '/' struct list_node { struct list_node *next; size_t fsize; char fname[256]; time_t ftime; /* can be creation, access or modification */ }; struct list_head { struct list_node *first; }; struct list_node *create_node(char *fname, size_t fsize, time_t ftime); void destroy_list(struct list_head *list); int insert_sorted_by_size(struct list_head *list, struct list_node *node); void print_list(struct list_head *list); /* === GLOBAL VARIABLES === */ bool option_show_hidden_entries = false; bool option_timestamp_short = true; /* === IMPLEMENTATION === */ inline struct list_node *create_node(char *fname, size_t fsize, time_t ftime) { struct list_node *node = NULL; if (fname == NULL || fname[0] == '\0') { LOGERR("ERROR: No valid filename given\n"); return NULL; } if ((node = calloc(1, sizeof(struct list_node))) == NULL) { LOGERR("ERROR: Failed allocate memory for node: %s\n", strerror(errno)); return NULL; } node->next = NULL; node->fsize = fsize; node->ftime = ftime; strncpy(node->fname, fname, 256); return node; } inline void destroy_list(struct list_head *list) { struct list_node *ptr = NULL; if (list == NULL) return; while (list->first != NULL) { ptr = list->first->next; if (ptr == NULL) { free(list->first); break; } list->first->next = ptr->next; free(ptr); } free(list); } /* TODO: later when, other parameters are actively used move the action to a template */ inline int insert_sorted_by_size(struct list_head *list, struct list_node *node) { struct list_node *ptr = NULL; if (list == NULL) { LOGERR("ERROR: No list given.\n"); return -1; } if (node == NULL) { LOGERR("ERROR: No node given.\n"); return -2; } if (list->first == NULL) { list->first = node; return 0; } if (node->fsize > list->first->fsize) { node->next = list->first; list->first = node; return 0; } ptr = list->first; while (ptr != NULL) { if (ptr->next == NULL) { ptr->next = node; return 0; } if (node->fsize > ptr->next->fsize) { node->next = ptr->next; ptr->next = node; return 0; } ptr = ptr->next; } return -3; } /* The most simple default output, formatted output is planned */ void print_list(struct list_head *list) { struct list_node *ptr; struct tm *tm = NULL; char timestamp[128]; if (list == NULL) return; ptr = list->first; while (ptr != NULL) { printf(" %8lu %2s ", ((ptr->fsize>=1024) ? (ptr->fsize/1024) : ptr->fsize), ((ptr->fsize >= 1024) ? "kB" : "")); tm = localtime(&ptr->ftime); if (option_timestamp_short) { strftime(timestamp, sizeof(timestamp), "%H:%M:%S", tm); printf(" %8s ", timestamp); } else { strftime(timestamp, sizeof(timestamp), "%Y%m%d %H:%M:%S %Z", tm); printf(" %20s ", timestamp); } printf(" %s\n", ptr->fname); ptr = ptr->next; } } /* TODO: When sorting via different parameters is possible, the call needs adjustments */ struct list_head *get_data_from_directory(char *path) { char *fullpath = NULL, *fname_in_path = NULL; DIR *dir = NULL; size_t l; struct dirent *de = NULL; struct list_node *tmp = NULL; struct list_head *list = NULL; struct stat stat_res; if (path == NULL) { LOGERR("ERROR: No path given.\n"); return NULL; } l = strlen(path); /* +2 due to possibly required / and of course string termination */ if ((fullpath = calloc(l+256+2, sizeof(char))) == NULL) { LOGERR("ERROR: Failed to allocate memory for list head.\n"); return NULL; } sprintf(fullpath, "%s%c", path, ((path[l-1] == PATH_SEP) ? '\0' : PATH_SEP)); fname_in_path = (fullpath[l] == PATH_SEP) ? &(fullpath[l+1]) : &(fullpath[l]) ; if ((dir = opendir(path)) == NULL) { LOGERR("ERROR: Failed to open directory '%s': %s (errno %d)\n", path, strerror(errno), errno); return NULL; } /* TBH: In case those few bytes are not available, the process will be killed anyways */ if ((list = calloc(1, sizeof(struct list_head))) == NULL) { LOGERR("ERROR: Failed to allocate memory for list head.\n"); closedir(dir); return NULL; } errno = 0; /* Show hidden entries code is UNIXoid specific, needs to be adjusted for portability */ while ((de = readdir(dir)) != NULL) { if (de->d_name[0] == '.' && option_show_hidden_entries == false) 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; } /* currently ignoring return values */ tmp = create_node(de->d_name, stat_res.st_size, stat_res.st_mtime); insert_sorted_by_size(list, tmp); } free(fullpath); closedir(dir); return list; } int main(int argc, char **argv) { struct list_head *list = NULL; list = get_data_from_directory(argv[1]); print_list(list); destroy_list(list); return EXIT_SUCCESS; }