200 lines
4.8 KiB
C
200 lines
4.8 KiB
C
#include <stdalign.h>
|
|
#include <dirent.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <sys/inotify.h>
|
|
|
|
#define STB_DS_IMPLEMENTATION
|
|
#include "stb/stb_ds.h"
|
|
#define GEBS_NO_PREFIX
|
|
#define GEBS_IMPLEMENTATION
|
|
#include "gebs/gebs.h"
|
|
|
|
#define EVENT_BUFFER_SIZE (5 * (sizeof(struct inotify_event) + NAME_MAX + 1))
|
|
#define EVENT_BUFFER_ALIGNMENT __attribute__((aligned(alignof(struct inotify_event))))
|
|
|
|
typedef struct {
|
|
int key; // watch descriptor
|
|
char *value; // the path
|
|
} File_Map;
|
|
|
|
typedef struct {
|
|
File_Map *fmap;
|
|
int fd;
|
|
NString_List ignored;
|
|
} Watcher;
|
|
|
|
bool watcher_make(Watcher *w)
|
|
{
|
|
String_Builder watcherignore = {0};
|
|
defer { sb_free(&watcherignore); }
|
|
if (sb_read_file(&watcherignore, "./.watcherignore")) {
|
|
sb_finish(&watcherignore);
|
|
|
|
char *line = strtok(watcherignore.items, "\n");
|
|
while (line != nil) {
|
|
char *line_copy = malloc(strlen(line)+1);
|
|
strcpy(line_copy, line);
|
|
list_append(&w->ignored, line_copy);
|
|
line = strtok(nil, "\n");
|
|
}
|
|
}
|
|
|
|
if ((w->fd = inotify_init1(IN_NONBLOCK)) < 0) {
|
|
LOGE("inotify_init1(): %s\n", strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void watcher_free(Watcher *w)
|
|
{
|
|
|
|
for (size_t i = 0; i < w->ignored.count; i++) {
|
|
free(w->ignored.items[i]);
|
|
}
|
|
list_free(&w->ignored);
|
|
|
|
for (size_t i = 0; i < hmlen(w->fmap); i++) {
|
|
free(w->fmap[i].value);
|
|
inotify_rm_watch(w->fd, w->fmap[i].key);
|
|
}
|
|
hmfree(w->fmap);
|
|
|
|
close(w->fd);
|
|
}
|
|
|
|
void watcher_add_file1(Watcher *w, int wd, char *path)
|
|
{
|
|
char *path_copy = gebs_malloc(&default_allocator, strlen(path)+1);
|
|
strcpy(path_copy, path);
|
|
hmput(w->fmap, wd, path_copy);
|
|
}
|
|
|
|
void watcher_add_file(Watcher *w, char *path)
|
|
{
|
|
uint32_t file_mask = IN_MODIFY | IN_DONT_FOLLOW;
|
|
int file_wd = inotify_add_watch(w->fd, path, file_mask);
|
|
if (file_wd < 0) {
|
|
LOGE("Could not add %s to watcher\n", path);
|
|
return;
|
|
}
|
|
|
|
watcher_add_file1(w, file_wd, path);
|
|
}
|
|
|
|
void watcher_add_all(Watcher *w, char *root)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *entry;
|
|
|
|
if ((dir = opendir(root)) == nil) {
|
|
LOGE("Could not open dir %s\n", root);
|
|
return;
|
|
}
|
|
defer { closedir(dir); }
|
|
|
|
while ((entry = readdir(dir)) != nil) {
|
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
|
|
char path[PATH_MAX];
|
|
snprintf(path, sizeof(path), "%s/%s", root, entry->d_name);
|
|
|
|
for (size_t i = 0; i < w->ignored.count; i++) {
|
|
if (strcmp(w->ignored.items[i], path) == 0) {
|
|
LOGI("Skipping %s\n", path);
|
|
goto next;
|
|
}
|
|
}
|
|
|
|
if (entry->d_type == DT_DIR) {
|
|
watcher_add_all(w, path);
|
|
} else {
|
|
watcher_add_file(w, path);
|
|
}
|
|
next:
|
|
}
|
|
}
|
|
|
|
char *prog = nil;
|
|
bool running = true;
|
|
|
|
void handle_sigint(int signo)
|
|
{
|
|
running = false;
|
|
}
|
|
|
|
int main(int argc, char ** argv)
|
|
{
|
|
prog = SHIFT(&argc, &argv);
|
|
if (argc < 2) {
|
|
LOGI("Usage: ./watcher <dir> <command>\n");
|
|
return 0;
|
|
}
|
|
|
|
char *watch_dir = SHIFT(&argc, &argv);
|
|
Cmd cmd = {0};
|
|
defer { cmd_free(&cmd); }
|
|
while (argc > 0) {
|
|
cmd_append(&cmd, SHIFT(&argc, &argv));
|
|
}
|
|
|
|
signal(SIGINT, &handle_sigint);
|
|
|
|
Watcher watcher = {0};
|
|
if (!watcher_make(&watcher)) {
|
|
LOGE("Failed to create the watcher\n");
|
|
return 1;
|
|
}
|
|
defer { watcher_free(&watcher); }
|
|
|
|
watcher_add_all(&watcher, watch_dir);
|
|
|
|
pid_t cmd_pid = cmd_run_async(&cmd);
|
|
|
|
char event_buffer[EVENT_BUFFER_SIZE] EVENT_BUFFER_ALIGNMENT;
|
|
while (running) {
|
|
ssize_t nread;
|
|
if ((nread = read(watcher.fd, event_buffer, EVENT_BUFFER_SIZE)) < 0) {
|
|
if (errno != EAGAIN) {
|
|
LOGE("read(): %s\n", strerror(errno));
|
|
return 1;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for (char *event_ptr = event_buffer; event_ptr < event_buffer + nread; ) {
|
|
const struct inotify_event *event = (const struct inotify_event *)event_ptr;
|
|
|
|
ssize_t idx = hmgeti(watcher.fmap, event->wd);
|
|
char *path = watcher.fmap[idx].value;
|
|
|
|
if (event->mask & IN_IGNORED) {
|
|
hmdel(watcher.fmap, event->wd);
|
|
watcher_add_file(&watcher, path);
|
|
}
|
|
|
|
if (event->mask & (IN_MODIFY | IN_IGNORED)) {
|
|
LOGI("%s changed (%04x)\n", path, event->mask);
|
|
|
|
if (cmd_pid != -1) {
|
|
LOGI("Killing %d\n", cmd_pid);
|
|
kill(cmd_pid, SIGINT);
|
|
}
|
|
|
|
cmd_pid = cmd_run_async(&cmd);
|
|
}
|
|
|
|
event_ptr += sizeof(struct inotify_event) + event->len;
|
|
}
|
|
|
|
sleep(1);
|
|
}
|
|
|
|
return 0;
|
|
}
|