feat: add simple inotify prototype
This commit is contained in:
parent
94a4ac27c5
commit
6c3ad9ffa5
24
Makefile
24
Makefile
@ -4,15 +4,22 @@
|
|||||||
.POSIX:
|
.POSIX:
|
||||||
|
|
||||||
NAME=sci
|
NAME=sci
|
||||||
|
DESCRIPTION=$(NAME) is a simple contiuous integration system.
|
||||||
VERSION = 0.1.0
|
VERSION = 0.1.0
|
||||||
|
|
||||||
SRC = src/sci.c
|
CC = gcc
|
||||||
OBJ = $(SRC:.c=.o)
|
|
||||||
OUTDIR := out/
|
OUTDIR := out/
|
||||||
OBJDIR := out/obj
|
OBJDIR := out/obj
|
||||||
BINDIR := out/bin
|
BINDIR := out/bin
|
||||||
CFLAGS += -DSCI_VERSION="\"$(VERSION)\"" -DSCI_NAME="\"$(NAME)\""
|
# defs
|
||||||
CFLAGS += -Wall -Werror
|
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
|
.PHONY: all clean
|
||||||
|
|
||||||
@ -21,11 +28,14 @@ all: out/bin/sci
|
|||||||
out/obj/%.o: src/%.c | $(OBJDIR)
|
out/obj/%.o: src/%.c | $(OBJDIR)
|
||||||
$(CC) -c $? $(CFLAGS) -o $@
|
$(CC) -c $? $(CFLAGS) -o $@
|
||||||
|
|
||||||
out/bin/sci: out/obj/main.o | $(BINDIR)
|
OBJ += out/obj/main.o
|
||||||
$(CC) -o $@ $(CFLAGS) $+
|
OBJ += out/obj/cli.o
|
||||||
|
OBJ += out/obj/notify.o
|
||||||
|
out/bin/sci: $(OBJ) | $(BINDIR)
|
||||||
|
$(CC) -o $@ $(CFLAGS) $^
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf build
|
rm -rf $(OUTDIR)
|
||||||
|
|
||||||
$(OUTDIR):
|
$(OUTDIR):
|
||||||
mkdir -p $@
|
mkdir -p $@
|
||||||
|
19
README.md
19
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
|
If you want `compile_commands.json` files, you should use [bear](https://github.com/rizsotto/Bear) as it works well
|
||||||
|
|
||||||
### Progress
|
### Progress
|
||||||
- [ ] Zeroth things first, let's create a simple CLI application with `--verbosity VAL` and `--help` options.
|
- [x] 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] 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.
|
||||||
|
25
include/cli.h
Normal file
25
include/cli.h
Normal 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
|
11
include/notify.h
Normal file
11
include/notify.h
Normal 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
11
include/optional.h
Normal 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
17
include/util.h
Normal 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
67
src/cli.c
Normal 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;
|
||||||
|
}
|
75
src/main.c
75
src/main.c
@ -1,64 +1,37 @@
|
|||||||
#include <stdio.h>
|
#include "cli.h"
|
||||||
|
#include "notify.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdbool.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
|
|
||||||
struct cli_options {
|
void on_notify_event(struct inotify_event* const e) {
|
||||||
int verbosity;
|
fprintf(stdout, "got an event:\n");
|
||||||
bool help;
|
fprintf(stdout, " wd: %d\n", e->wd);
|
||||||
bool version;
|
fprintf(stdout, " mask: %d\n", e->mask);
|
||||||
};
|
fprintf(stdout, " cookie: %d\n", e->cookie);
|
||||||
|
fprintf(stdout, " len: %d\n", e->len);
|
||||||
struct cli_options new_options() {
|
fprintf(stdout, " name: %s\n", e->name);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
struct cli_options options = new_options();
|
struct cli_options args = parse(argc, argv);
|
||||||
int opt;
|
|
||||||
while((opt = getopt(argc, argv, "v:hV")) != -1) {
|
if(args.help) {
|
||||||
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) {
|
|
||||||
print_help(stdout, argv[0]);
|
print_help(stdout, argv[0]);
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
if(options.version) {
|
|
||||||
|
if(args.version) {
|
||||||
fprintf(stdout, SCI_VERSION "\n");
|
fprintf(stdout, SCI_VERSION "\n");
|
||||||
exit(EXIT_SUCCESS);
|
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
23
src/notify.c
Normal 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)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user