From 6c3ad9ffa5c6e10e90abd761e5941b981ef45011 Mon Sep 17 00:00:00 2001 From: Asger Gitz-Johansen Date: Fri, 2 Aug 2024 19:11:12 +0200 Subject: [PATCH] feat: add simple inotify prototype --- Makefile | 24 ++++++++++----- README.md | 19 ++++++++++-- include/cli.h | 25 ++++++++++++++++ include/main.h | 0 include/notify.h | 11 +++++++ include/optional.h | 11 +++++++ include/util.h | 17 +++++++++++ src/cli.c | 67 +++++++++++++++++++++++++++++++++++++++++ src/main.c | 75 +++++++++++++++------------------------------- src/notify.c | 23 ++++++++++++++ 10 files changed, 212 insertions(+), 60 deletions(-) create mode 100644 include/cli.h delete mode 100644 include/main.h create mode 100644 include/notify.h create mode 100644 include/optional.h create mode 100644 include/util.h create mode 100644 src/cli.c create mode 100644 src/notify.c diff --git a/Makefile b/Makefile index a93b18d..adc360a 100644 --- a/Makefile +++ b/Makefile @@ -4,15 +4,22 @@ .POSIX: NAME=sci +DESCRIPTION=$(NAME) is a simple contiuous integration system. VERSION = 0.1.0 -SRC = src/sci.c -OBJ = $(SRC:.c=.o) +CC = gcc OUTDIR := out/ OBJDIR := out/obj BINDIR := out/bin -CFLAGS += -DSCI_VERSION="\"$(VERSION)\"" -DSCI_NAME="\"$(NAME)\"" -CFLAGS += -Wall -Werror +# defs +CFLAGS += -DSCI_VERSION="\"$(VERSION)\"" +CFLAGS += -DSCI_NAME="\"$(NAME)\"" +CFLAGS += -DSCI_DESCRIPTION="\"$(DESCRIPTION)\"" +# compiler flags +CFLAGS += -Wall -Werror -std=c23 -g +# includes +CFLAGS += -Iinclude +# libraries .PHONY: all clean @@ -21,11 +28,14 @@ all: out/bin/sci out/obj/%.o: src/%.c | $(OBJDIR) $(CC) -c $? $(CFLAGS) -o $@ -out/bin/sci: out/obj/main.o | $(BINDIR) - $(CC) -o $@ $(CFLAGS) $+ +OBJ += out/obj/main.o +OBJ += out/obj/cli.o +OBJ += out/obj/notify.o +out/bin/sci: $(OBJ) | $(BINDIR) + $(CC) -o $@ $(CFLAGS) $^ clean: - rm -rf build + rm -rf $(OUTDIR) $(OUTDIR): mkdir -p $@ diff --git a/README.md b/README.md index bf5802f..b19b647 100644 --- a/README.md +++ b/README.md @@ -77,5 +77,20 @@ I also choose `Makefile`s! - Just to force myself to use another build system th If you want `compile_commands.json` files, you should use [bear](https://github.com/rizsotto/Bear) as it works well ### Progress - - [ ] Zeroth things first, let's create a simple CLI application with `--verbosity VAL` and `--help` options. - - [ ] First things first, let's implement something that reacts when some provided file changes. + - [x] Zeroth things first, let's create a simple CLI application with `--verbosity VAL` and `--help` options. + - [x] First things first, let's implement something that reacts when some provided file changes (not poll please). + - [ ] Second things second, implement a simple logging system with differing levels of verbosity and configurable + output file using cli options. + - [ ] Third things third, implement a thing that simultaneously watches two different files (multithreading). + - [ ] Fourth things fourth, implement a prototype that reads a space-separated file and populates a struct. + +### Note Regarding `inotify` usage +From the manpage: +``` +With careful programming, an application can use inotify to efficiently monitor and cache the state of a set of +filesystem objects. However, robust applications should allow for the fact that bugs in the monitoring logic or races +of the kind described below may leave the cache inconsistent with the filesystem state. It is probably wise to do some +consistency checking, and reā€build the cache when inconsistencies are detected. +``` +i.e., we should _also_ poll the watched files every once in a while (maybe once per minute? idk) to ensure that we catch +all events. diff --git a/include/cli.h b/include/cli.h new file mode 100644 index 0000000..5ed0f31 --- /dev/null +++ b/include/cli.h @@ -0,0 +1,25 @@ +#ifndef SCI_CLI_H +#define SCI_CLI_H +#include +#include "optional.h" + +struct cli_options { + optional_str file; + int verbosity; + bool help; + bool version; +}; + +// Construct a new cli_options struct instance. +struct cli_options new_options(); + +// Delete a cli_options struct instance. +void free_options(struct cli_options v); + +// Print the help message. +void print_help(FILE * out, char* prog_name); + +// Parse the command line arguments and give a new cli_options struct instance. +struct cli_options parse(int argc, char** argv); + +#endif diff --git a/include/main.h b/include/main.h deleted file mode 100644 index e69de29..0000000 diff --git a/include/notify.h b/include/notify.h new file mode 100644 index 0000000..1ff00e9 --- /dev/null +++ b/include/notify.h @@ -0,0 +1,11 @@ +#ifndef SCI_NOTIFY_H +#define SCI_NOTIFY_H +#include + +typedef void(*notify_callback)(struct inotify_event* const); + +// Start listening for changes to the provided file. +// Note that the `struct inotify_event*` provided is a managed pointer. +void listen_for_changes(const char* filename, notify_callback callback); + +#endif diff --git a/include/optional.h b/include/optional.h new file mode 100644 index 0000000..59094e2 --- /dev/null +++ b/include/optional.h @@ -0,0 +1,11 @@ +#ifndef SCI_OPTIONAL_H +#define SCI_OPTIONAL_H +#include + +#define optional_type(type) struct { bool has_value; type value; } +typedef optional_type(int) optional_int; +typedef optional_type(float) optional_float; +typedef optional_type(char*) optional_str; +typedef optional_type(const char*) optional_cstr; + +#endif diff --git a/include/util.h b/include/util.h new file mode 100644 index 0000000..a0939a0 --- /dev/null +++ b/include/util.h @@ -0,0 +1,17 @@ +#ifndef SCI_UTIL_H +#define SCI_UTIL_H +#include +#include +#include +#include +#include + +#define ASSERT_SYSCALL_SUCCESS(fd) \ + do { \ + if ((fd) == -1) { \ + fprintf(stderr, "Assertion failed: %s, errno: %d, error: %s\n", #fd, errno, strerror(errno)); \ + assert(fd != -1); \ + } \ + } while (0) + +#endif diff --git a/src/cli.c b/src/cli.c new file mode 100644 index 0000000..23e018f --- /dev/null +++ b/src/cli.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include "cli.h" + +struct cli_options new_options() { + struct cli_options result; + result.file.has_value = false; + result.verbosity = 1; + result.help = false; + result.version = false; + return result; +} + +void free_options(struct cli_options v) { + if(v.file.has_value) + free(v.file.value); +} + +// +#include "cli.h" +#include "notify.h" #include #include -#include -#include -struct cli_options { - int verbosity; - bool help; - bool version; -}; - -struct cli_options new_options() { - struct cli_options result; - result.verbosity = 0; - result.help = false; - return result; -} - -// wd); + fprintf(stdout, " mask: %d\n", e->mask); + fprintf(stdout, " cookie: %d\n", e->cookie); + fprintf(stdout, " len: %d\n", e->len); + fprintf(stdout, " name: %s\n", e->name); } int main(int argc, char** argv) { - struct cli_options options = new_options(); - int opt; - while((opt = getopt(argc, argv, "v:hV")) != -1) { - switch(opt) { - case 'v': - options.verbosity = atoi(optarg); - break; - case 'V': - options.version = true; - break; - case 'h': - options.help = true; - break; - default: // '?' - print_help(stderr, argv[0]); - exit(EXIT_FAILURE); - } - } - if(options.help) { + struct cli_options args = parse(argc, argv); + + if(args.help) { print_help(stdout, argv[0]); exit(EXIT_SUCCESS); } - if(options.version) { + + if(args.version) { fprintf(stdout, SCI_VERSION "\n"); exit(EXIT_SUCCESS); } + + if(args.file.has_value) { + if(access(args.file.value, F_OK) != 0) { + fprintf(stderr, "no such file or directory %s\n", args.file.value); + exit(EXIT_FAILURE); + } + listen_for_changes(args.file.value, &on_notify_event); + } + + free_options(args); } diff --git a/src/notify.c b/src/notify.c new file mode 100644 index 0000000..fab4cd1 --- /dev/null +++ b/src/notify.c @@ -0,0 +1,23 @@ +#include "notify.h" +#include "util.h" + +#define EV_SIZE sizeof(struct inotify_event) +#define BUF_LEN EV_SIZE * 32 + +void listen_for_changes(const char* filename, notify_callback callback) { + int fd = inotify_init(); + ASSERT_SYSCALL_SUCCESS(fd); + inotify_add_watch(fd, filename, IN_ATTRIB); + + fprintf(stdout, "listening for changes in file: %s\n", filename); + + char buffer[BUF_LEN]; + int r = read(fd, buffer, BUF_LEN); + assert(r != -1); + for(int i = 0; i < r; ) { + struct inotify_event* e = (struct inotify_event*)&buffer[i]; + callback(e); + i += EV_SIZE + e->len; + } + ASSERT_SYSCALL_SUCCESS(close(fd)); // TODO: have a hashmap of threads (see readme) +}