feat: add simple inotify prototype

This commit is contained in:
Asger Gitz-Johansen 2024-08-02 19:11:12 +02:00
parent 94a4ac27c5
commit 6c3ad9ffa5
10 changed files with 212 additions and 60 deletions

View File

@ -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 $@

View File

@ -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 rebuild 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.

25
include/cli.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef SCI_CLI_H
#define SCI_CLI_H
#include <stdio.h>
#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

View File

11
include/notify.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef SCI_NOTIFY_H
#define SCI_NOTIFY_H
#include <sys/inotify.h>
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

11
include/optional.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef SCI_OPTIONAL_H
#define SCI_OPTIONAL_H
#include <stdbool.h>
#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

17
include/util.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef SCI_UTIL_H
#define SCI_UTIL_H
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <sys/errno.h>
#include <unistd.h>
#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

67
src/cli.c Normal file
View File

@ -0,0 +1,67 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#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);
}
// <max
const char* optstring = "f:v:hV";
const char* help_msg =
"Usage: %s [-v level] [-h] [-V]\n"
"\n"
SCI_DESCRIPTION "\n"
"\n"
"THIS PROGRAM IS STILL JUST A PROTOTYPE, AND NOT\n"
"ACTUALLY USEFUL YET\n"
"\n"
"OPTIONS:\n"
" -f file set file\n"
" -v level Set verbosity level [0-3]\n"
" -h Show this message and exit\n"
" -V Show version and exit\n"
;
// <max
void print_help(FILE * out, char* prog_name) {
fprintf(out, help_msg, prog_name);
}
struct cli_options parse(int argc, char** argv) {
struct cli_options options = new_options();
int opt;
while((opt = getopt(argc, argv, optstring)) != -1) {
switch(opt) {
case 'f':
options.file.value = strdup(optarg);
options.file.has_value = true;
break;
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);
}
}
return options;
}

View File

@ -1,64 +1,37 @@
#include <stdio.h>
#include "cli.h"
#include "notify.h"
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <getopt.h>
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;
}
// <max
char* help_msg =
"Usage: %s [-v level] [-h] [-V]\n"
"\n"
SCI_NAME " is a simple contiuous integration system.\n"
"\n"
"OPTIONS:\n"
" -v level Set verbosity level [0-3]\n"
" -h Show this message and exit\n"
" -V Show version and exit\n"
;
// <max
void print_help(FILE * out, char* prog_name) {
fprintf(out, help_msg, prog_name);
void on_notify_event(struct inotify_event* const e) {
fprintf(stdout, "got an event:\n");
fprintf(stdout, " wd: %d\n", e->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);
}

23
src/notify.c Normal file
View File

@ -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)
}