From e60ed607978b4bba4f26c427cda09919e6e44e51 Mon Sep 17 00:00:00 2001 From: Marco Cetica Date: Tue, 30 Jul 2024 16:48:07 +0200 Subject: [PATCH] First upload --- Makefile | 24 +++++ main.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 Makefile create mode 100644 main.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f993a9f --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +TARGET = wolf +DEBUG_TARGET = wolfdebug + +CC = gcc + +DEBUG_CFLAGS = -Wall -Wextra -Werror -pedantic-errors -fstack-protector-strong \ + -fsanitize=address -fsanitize=undefined -fstack-clash-protection \ + -Wwrite-strings -std=c99 -g +CFLAGS = -Wall -Wextra -Werror -pedantic-errors -Wwrite-strings -std=c99 -O3 + +GIT_HASH := $(shell git rev-parse --short HEAD) +BUILD_FLAGS = -DVERSION=\"0.0.1\" -DHASH=\"$(GIT_HASH)\" + +build: $(TARGET) +debug: $(DEBUG_TARGET) + +$(TARGET): main.c + $(CC) $(CFLAGS) $(BUILD_FLAGS) $^ -o $@ + +$(DEBUG_TARGET): main.c + $(CC) $(DEBUG_CFLAGS) $(BUILD_FLAGS) $^ -o $@ + +clean: + rm -f *.o *.a $(TARGET) $(DEBUG_TARGET) diff --git a/main.c b/main.c new file mode 100644 index 0000000..af5c1f7 --- /dev/null +++ b/main.c @@ -0,0 +1,283 @@ +#ifndef __linux__ + #error "Unsupported platform" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INOTIFY_EVENT_INC sizeof(struct inotify_event) + event->len +#define WATCHDOG_FORMAT "[%s] %c '%s' (%s)\n" // [] '' () +#define WATCHDOG_FORMAT_NOTS "%c '%s' (%s)\n" // '' () +#define FILE_FMT "%s/%s" +#define DIR_FMT "%s" + +typedef enum { + E_CREATE = 'C', + E_DELETE = 'D', + E_MOVE = 'M', + E_READ = 'R', + E_WRITE = 'W', + E_PERM = 'P', + E_UNDEF = 0xB00B5 +} watchdog_event; + +typedef enum { false, true } bool; + +static void handle_inotify_events(int fd, const int *wd, int wd_len, char **watched_files, const bool is_timestamp_enabled); +static void get_timestamp(uint8_t *timestamp, const ssize_t timestamp_len); + +volatile sig_atomic_t stop_signal = 0; +void sigint_handler() { + stop_signal = ~stop_signal; +} + +void helper(const char *name) { + printf("Wolf - Configurable file watchdog for Linux platform.\n\n" + "Syntax: '%s [-c|-d|-m|-r|-w|-p|-f] '\n" + "options:\n" + "-c, --create | Add a watchdog for file creation\n" + "-d, --delete | Add a watchdog for file deletion\n" + "-m, --move | Add a watchdog for file movements or file renaming\n" + "-r, --read | Add a watchdog for reading events\n" + "-w, --write | Add a watchdog for writing events\n" + "-p, --permission | Add a watchdog for permissions changes\n" + "-f, --full | Enable all the previous options\n" + "--no-timestamp | Disable timestamp from watchdog output\n" + "-v, --version | Show program version\n" + "-h, --help | Show this helper\n\n" + "General help with the software: https://github.com/ceticamarco/wolf\n" + "Report bugs to: Marco Cetica()\n", name); +} + +void version() { + printf("Wolf (v%s, %s) - Configurable file watchdog for Linux platform.\n" + "Copyright (c) 2024 Marco Cetica\n" + "License GPLv3+: GNU GPL version 3 or later\n\n" + "Project homepage: .\n" + "Email bug reports to: .\n", VERSION, HASH); +} + +int main(int argc, char **argv) { + int opt, opt_idx = 0; + const char *short_opts = "cdmrwpfvh"; + uint32_t mask = 0; + bool is_timestamp_enabled = true; + struct option long_opts[] = { + {"create", no_argument, NULL, 'c'}, + {"delete", no_argument, NULL, 'd'}, + {"move", no_argument, NULL, 'm'}, + {"read", no_argument, NULL, 'r'}, + {"write", no_argument, NULL, 'w'}, + {"permission", no_argument, NULL, 'p'}, + {"full", no_argument, NULL, 'f'}, + {"no-timestamp", no_argument, NULL, 0 }, + {"version", no_argument, NULL, 'v'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + // Parse inotify options from command line + while((opt = getopt_long(argc, argv, short_opts, long_opts, &opt_idx)) != -1) { + switch(opt) { + case 'c': mask |= IN_CREATE; break; + case 'd': mask |= (IN_DELETE | IN_DELETE_SELF); break; + case 'm': mask |= (IN_MOVE_SELF | IN_MOVED_FROM); break; + case 'r': mask |= IN_ACCESS; break; + case 'w': mask |= IN_MODIFY; break; + case 'p': mask |= IN_ATTRIB; break; + case 'f': mask = IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM | + IN_ACCESS | IN_MODIFY | IN_ATTRIB; break; + case 0: + if(!strcmp(long_opts[opt_idx].name, "no-timestamp")) { + is_timestamp_enabled = false; + } + break; + case 'v': version(); return 0; + case 'h': helper(argv[0]); return 0; + default: helper(argv[0]); return 1; + } + } + + // Retrieve number of non-option arguments + const int number_of_files = (argc - optind); + + // Check whether user has provided enough + // non-option arguments(i.e., files to watch) + if(number_of_files == 0) { + puts("Error: provide at least one file/directory to watch"); + helper(argv[0]); + return 1; + } + + // Check whether user has provided enough watchdog options + if(mask == 0) { + puts("Error: provide at least one watchdog option"); + helper(argv[0]); + return 1; + } + + // Register SIGINT handler + signal(SIGINT, sigint_handler); + + // Initialize inotify API + int fd = inotify_init1(IN_NONBLOCK); + if(fd == -1) { + perror("inotify_init1"); + exit(EXIT_FAILURE); + } + + // Allocate enough memory for each watch descriptor + int *wd = calloc(number_of_files, sizeof(int)); + if(wd == NULL) { + perror("calloc"); + exit(EXIT_FAILURE); + } + + // Register each file into the watchlist + int file_idx = optind; + for(size_t idx = 0; file_idx < argc; file_idx++, idx++) { + wd[idx] = inotify_add_watch(fd, argv[file_idx], mask); + if(wd[idx] == -1) { + fprintf(stderr, "Cannot watch '%s': %s\n", argv[file_idx], strerror(errno)); + exit(EXIT_FAILURE); + } + } + + // Prepare polling + nfds_t nfds = 1; + struct pollfd fds[] = { + { .fd = fd, .events = POLLIN } + }; + + while(!stop_signal) { + int poll_num = poll(fds, nfds, -1); + if(poll_num == -1) { + if(errno == EINTR) { + continue; + } + perror("poll"); + exit(EXIT_FAILURE); + } + + if(poll_num > 0) { + // Inotify events are available + if(fds->revents & POLLIN) { + handle_inotify_events(fd, wd, number_of_files, (argv + optind), is_timestamp_enabled); + } + } + } + + // Free allocated resources + close(fd); + free(wd); + + return 0; +} + +static void handle_inotify_events(int fd, const int *wd, int wd_len, char **watched_files, const bool is_timestamp_enabled) { + // Align inotify reading buffer to inotify_event struct + char inotify_read_buf[4096] + __attribute__((aligned((__alignof__(struct inotify_event))))); + const struct inotify_event *event; + + while(1) { + // Read events from inotify file descriptor + ssize_t len = read(fd, inotify_read_buf, sizeof(inotify_read_buf)); + if(len == -1 && errno != EAGAIN) { + perror("read"); + exit(EXIT_FAILURE); + } + + // Exit whether read returns nothing + if(len <= 0) { + break; + } + + // Handle each event of the buffer + for(char *ptr = inotify_read_buf; ptr < (inotify_read_buf + len); ptr += INOTIFY_EVENT_INC) { + // Retrieve single event + event = (const struct inotify_event*)ptr; + + // Set the event type + watchdog_event we = E_UNDEF; + if(event->mask & IN_CREATE) { + we = E_CREATE; + } else if(event->mask & (IN_DELETE | IN_DELETE_SELF)) { + we = E_DELETE; + } else if(event->mask & IN_MOVED_FROM) { + we = E_MOVE; + } else if(event->mask & IN_ACCESS) { + we = E_READ; + } else if(event->mask & IN_MODIFY) { + we = E_WRITE; + } else if(event->mask & IN_ATTRIB) { + we = E_PERM; + } else if(event->mask & IN_IGNORED) { + // This event is generated each time a watched file/directory is + // deleted. The event is ignored since it adds no further information + // about the watched file. + continue; + } + + // Print the watchdog event to the standard output + uint8_t *file_name = NULL; + size_t file_name_len; + for(int i = 0; i < wd_len; i++) { + if(wd[i] == event->wd) { + // Build filename + if(event->len) { + file_name_len = snprintf(NULL, 0, FILE_FMT, watched_files[i], event->name); + file_name = malloc((file_name_len+1) * sizeof(char)); + if(file_name == NULL) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + snprintf((char*)file_name, file_name_len+1, FILE_FMT, watched_files[i], event->name); + } else { + file_name_len = snprintf(NULL, 0, DIR_FMT, watched_files[i]); + file_name = malloc((file_name_len+1) * sizeof(char)); + if(file_name == NULL) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + snprintf((char*)file_name, file_name_len+1, DIR_FMT, watched_files[i]); + } + + // Get file type + const char *file_type = (event->mask & IN_ISDIR ? "dir" : "file"); + + if(is_timestamp_enabled) { + // Get timestamp + uint8_t timestamp[20]; + get_timestamp(timestamp, sizeof(timestamp)); + // Print the result + printf(WATCHDOG_FORMAT, (char*)timestamp, we, file_name, file_type); + } else { + // Print the result without timestamp + printf(WATCHDOG_FORMAT_NOTS, we, file_name, file_type); + } + break; + } + } + free(file_name); + } + memset(inotify_read_buf, 0, sizeof(inotify_read_buf)); + } +} + +static void get_timestamp(uint8_t *timestamp, const ssize_t timestamp_len) { + time_t now = time(NULL); + struct tm *timeinfo = localtime(&now); + strftime((char*)timestamp, timestamp_len, "%Y-%m-%d %H:%M:%S", timeinfo); +}