From 42ba755c425682f1f54fdb40a39227035e833cfd Mon Sep 17 00:00:00 2001 From: Thorsten Töpper Date: Sat, 14 Jun 2025 16:21:27 +0200 Subject: source and output reorganized --- src/dir_monitor.c | 337 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 src/dir_monitor.c (limited to 'src') diff --git a/src/dir_monitor.c b/src/dir_monitor.c new file mode 100644 index 0000000..9e993a8 --- /dev/null +++ b/src/dir_monitor.c @@ -0,0 +1,337 @@ +/* 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 +#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 fputc_all_cols(char c, FILE *fdout); +int fputc_width_x(char c, size_t x, FILE *fdout); +struct list_head *get_data_from_directory(char *path); +int insert_sorted_by_size(struct list_head *list, struct list_node *node); +int parse_arguments(int argc, char **argv); +void print_list(struct list_head *list); +void set_option(const char *option_name, char *option_argument); + + + +/* === GLOBAL VARIABLES === */ +struct option long_options[] = { + { "show-hidden-entries", no_argument, 0, 0 }, + { "long-timestamp", no_argument, 0, 0 }, + { 0, 0, 0, 0 } +}; +bool option_show_hidden_entries = false; +bool option_timestamp_long = false; + + + +/* === 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); +} + + +inline int fputc_all_cols(char c, FILE *fdout) { + struct winsize terminal; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &terminal); + return fputc_width_x(c, ((terminal.ws_col == 0) ? 72 : terminal.ws_col), fdout); +} + + +/* Not the most performant way, but during output the time critical tasks are already done. */ +inline int fputc_width_x(char c, size_t x, FILE *fdout) { + size_t i=0; + + if (fdout == NULL) { + LOGERR("ERROR: No output stream given.\n"); + return EOF; + } + + if ( ! isprint(c) ) { c = '?'; } + + for (i=0; i_fileno); + return EOF; + } + } + + return 1; +} + + +/* 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]; + size_t total_size = 0; + + 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_long) { + strftime(timestamp, sizeof(timestamp), "%Y%m%d %H:%M:%S %Z", tm); + printf(" %20s ", timestamp); + } else { + strftime(timestamp, sizeof(timestamp), "%H:%M:%S", tm); + printf(" %8s ", timestamp); + } + printf(" %s\n", ptr->fname); + total_size += ptr->fsize; + ptr = ptr->next; + } + fputc_all_cols('=', stdout); + printf("\nTotal size: %llu %s\n", ((total_size>1024) ? total_size/1024 : total_size), + ((total_size >= 1024) ? "kB" : "")); +} + + +/* 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); + /* 258 filename and possibly required PATH_SEP and of course string termination */ + if ((fullpath = calloc(l+258, 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; +} + + +void set_option(const char *option_name, char *option_argument) { + if (option_name == NULL) + return; + + if (strcmp(option_name, "show-hidden-entries") == 0) { + option_show_hidden_entries = true; + return; + } + + if (strcmp(option_name, "long-timestamp") == 0) { + option_timestamp_long = true; + return; + } + + LOGERR("ERROR: Option '%s' not recognized\n.", option_name); +} + +int parse_arguments(int argc, char **argv) { + int c = 0, index; + + while(1) { + index = 0; + c = getopt_long(argc, argv, "tv", long_options, &index); + + if (c == -1) { + break; + } + + switch (c) { + case 0: + set_option(long_options[index].name, optarg); + break; + case 't': + option_timestamp_long = true; + break; + case 'v': + option_show_hidden_entries = true; + break; + default: + LOGERR("ERROR: unrecognized option '0x%02X'\n", c); + break; + } + } + + return optind; +} + + +int main(int argc, char **argv) { + struct list_head *list = NULL; + int path_index = 1; + /* TODO: Handle options */ + if (argc < 2) { + fprintf(stderr, "Call: %s OPTIONS path_to_open\n", argv[0]); + fprintf(stderr, "\nOPTIONS are\n"); + /* long name, short name, optional argument, explanation */ + fprintf(stderr, " %-25s %2s %10s %s\n", "--long-timestamp", "-t", "", + "Print timestamp in long form yyyymmdd HH:MM:SS ZONE"); + fprintf(stderr, " %-25s %2s %10s %s\n", "--show-hidden-entries", "-v", "", + "Show hidden entries in the directory"); + return EXIT_FAILURE; + } + path_index = parse_arguments(argc, argv); + list = get_data_from_directory(argv[path_index]); + print_list(list); + destroy_list(list); + return EXIT_SUCCESS; +} + -- cgit v1.2.3-70-g09d2