aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/dir_monitor.c337
1 files changed, 337 insertions, 0 deletions
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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <sys/ioctl.h>
+
+
+#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<x; i++) {
+ if (fputc(c, fdout) == EOF) {
+ LOGERR("ERROR: Failed to write char 0x%02X to stream %d\n", c, fdout->_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;
+}
+