//go:build exclude #include #include #include #include #include #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 \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, SIGKILL); } cmd_pid = cmd_run_async(&cmd); } event_ptr += sizeof(struct inotify_event) + event->len; } sleep(1); } return 0; }