2019-11-11 02:58:01 +08:00
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
2019-11-12 04:46:06 +08:00
|
|
|
#ifdef HAS_INOTIFY
|
2019-11-11 02:58:01 +08:00
|
|
|
#include <sys/inotify.h>
|
2019-11-12 05:10:19 +08:00
|
|
|
#elif HAS_KQUEUE
|
2019-11-12 05:59:25 +08:00
|
|
|
// clang-format off
|
2019-11-12 05:10:19 +08:00
|
|
|
#include <sys/types.h>
|
2019-11-12 05:59:25 +08:00
|
|
|
// clang-format on
|
|
|
|
#include <sys/event.h>
|
2019-11-12 05:10:19 +08:00
|
|
|
#undef EV_ERROR // Avoid clashing with libev's EV_ERROR
|
|
|
|
#include <fcntl.h> // For O_RDONLY
|
|
|
|
#include <sys/time.h> // For struct timespec
|
|
|
|
#include <unistd.h> // For open
|
2019-11-12 04:46:06 +08:00
|
|
|
#endif
|
2019-11-11 02:58:01 +08:00
|
|
|
|
|
|
|
#include <ev.h>
|
|
|
|
#include <uthash.h>
|
|
|
|
|
|
|
|
#include "file_watch.h"
|
|
|
|
#include "list.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "utils.h"
|
|
|
|
|
|
|
|
struct watched_file {
|
|
|
|
int wd;
|
|
|
|
void *ud;
|
|
|
|
file_watch_cb_t cb;
|
|
|
|
|
|
|
|
UT_hash_handle hh;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct file_watch_registry {
|
|
|
|
struct ev_io w;
|
|
|
|
|
|
|
|
struct watched_file *reg;
|
|
|
|
};
|
|
|
|
|
2019-11-12 05:10:19 +08:00
|
|
|
static void file_watch_ev_cb(EV_P attr_unused, struct ev_io *w, int revent attr_unused) {
|
2019-11-11 02:58:01 +08:00
|
|
|
auto fwr = (struct file_watch_registry *)w;
|
|
|
|
|
|
|
|
while (true) {
|
2019-11-12 04:46:06 +08:00
|
|
|
int wd = -1;
|
|
|
|
#ifdef HAS_INOTIFY
|
|
|
|
struct inotify_event inotify_event;
|
2019-11-11 02:58:01 +08:00
|
|
|
auto ret = read(w->fd, &inotify_event, sizeof(struct inotify_event));
|
|
|
|
if (ret < 0) {
|
|
|
|
if (errno != EAGAIN) {
|
|
|
|
log_error("Failed to read from inotify fd: %s",
|
|
|
|
strerror(errno));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2019-11-12 04:46:06 +08:00
|
|
|
wd = inotify_event.wd;
|
2019-11-12 05:10:19 +08:00
|
|
|
#elif HAS_KQUEUE
|
|
|
|
struct kevent ev;
|
|
|
|
struct timespec timeout = {0};
|
|
|
|
int ret = kevent(fwr->w.fd, NULL, 0, &ev, 1, &timeout);
|
|
|
|
if (ret <= 0) {
|
|
|
|
if (ret < 0) {
|
|
|
|
log_error("Failed to get kevent: %s", strerror(errno));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
wd = (int)ev.ident;
|
|
|
|
#else
|
|
|
|
assert(false);
|
2019-11-12 04:46:06 +08:00
|
|
|
#endif
|
2019-11-11 02:58:01 +08:00
|
|
|
|
|
|
|
struct watched_file *wf = NULL;
|
2019-11-12 04:46:06 +08:00
|
|
|
HASH_FIND_INT(fwr->reg, &wd, wf);
|
2019-11-11 02:58:01 +08:00
|
|
|
if (!wf) {
|
|
|
|
log_warn("Got notification for a file I didn't watch.");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
wf->cb(wf->ud);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void *file_watch_init(EV_P) {
|
|
|
|
log_debug("Starting watching for file changes");
|
2019-11-12 04:46:06 +08:00
|
|
|
int fd = -1;
|
|
|
|
#ifdef HAS_INOTIFY
|
|
|
|
fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
2019-11-11 02:58:01 +08:00
|
|
|
if (fd < 0) {
|
|
|
|
log_error("inotify_init1 failed: %s", strerror(errno));
|
|
|
|
return NULL;
|
|
|
|
}
|
2019-11-12 05:10:19 +08:00
|
|
|
#elif HAS_KQUEUE
|
|
|
|
fd = kqueue();
|
|
|
|
if (fd < 0) {
|
|
|
|
log_error("Failed to create kqueue: %s", strerror(errno));
|
|
|
|
return NULL;
|
|
|
|
}
|
2019-11-12 04:46:06 +08:00
|
|
|
#else
|
|
|
|
log_info("No file watching support found on the host system.");
|
|
|
|
return NULL;
|
|
|
|
#endif
|
2019-11-11 02:58:01 +08:00
|
|
|
auto fwr = ccalloc(1, struct file_watch_registry);
|
|
|
|
ev_io_init(&fwr->w, file_watch_ev_cb, fd, EV_READ);
|
|
|
|
ev_io_start(EV_A_ & fwr->w);
|
|
|
|
|
|
|
|
return fwr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void file_watch_destroy(EV_P_ void *_fwr) {
|
|
|
|
log_debug("Stopping watching for file changes");
|
|
|
|
auto fwr = (struct file_watch_registry *)_fwr;
|
|
|
|
struct watched_file *i, *tmp;
|
|
|
|
|
|
|
|
HASH_ITER(hh, fwr->reg, i, tmp) {
|
|
|
|
HASH_DEL(fwr->reg, i);
|
2019-11-12 05:10:19 +08:00
|
|
|
#ifdef HAS_KQUEUE
|
|
|
|
// kqueue watch descriptors are file descriptors of
|
|
|
|
// the files we are watching, so we need to close
|
|
|
|
// them
|
|
|
|
close(i->wd);
|
|
|
|
#endif
|
2019-11-11 02:58:01 +08:00
|
|
|
free(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
ev_io_stop(EV_A_ & fwr->w);
|
|
|
|
close(fwr->w.fd);
|
|
|
|
free(fwr);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *ud) {
|
|
|
|
log_debug("Adding \"%s\" to watched files", filename);
|
|
|
|
auto fwr = (struct file_watch_registry *)_fwr;
|
2019-11-12 04:46:06 +08:00
|
|
|
int wd = -1;
|
|
|
|
#ifdef HAS_INOTIFY
|
|
|
|
wd = inotify_add_watch(fwr->w.fd, filename,
|
|
|
|
IN_CLOSE_WRITE | IN_MOVE_SELF | IN_DELETE_SELF);
|
2019-11-11 02:58:01 +08:00
|
|
|
if (wd < 0) {
|
|
|
|
log_error("Failed to watch file \"%s\": %s", filename, strerror(errno));
|
|
|
|
return false;
|
|
|
|
}
|
2019-11-12 05:10:19 +08:00
|
|
|
#elif HAS_KQUEUE
|
|
|
|
wd = open(filename, O_RDONLY);
|
|
|
|
if (wd < 0) {
|
|
|
|
log_error("Cannot open file \"%s\" for watching: %s", filename,
|
|
|
|
strerror(errno));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t fflags = NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE | NOTE_ATTRIB;
|
|
|
|
// NOTE_CLOSE_WRITE is relatively new, so we cannot just use it
|
|
|
|
#ifdef NOTE_CLOSE_WRITE
|
|
|
|
fflags |= NOTE_CLOSE_WRITE;
|
|
|
|
#else
|
|
|
|
// NOTE_WRITE will receive notification more frequent than necessary, so is less
|
|
|
|
// preferrable
|
|
|
|
fflags |= NOTE_WRITE;
|
2019-11-12 04:46:06 +08:00
|
|
|
#endif
|
2019-11-12 05:10:19 +08:00
|
|
|
struct kevent ev = {
|
|
|
|
.ident = (unsigned int)wd, // the wd < 0 case is checked above
|
|
|
|
.filter = EVFILT_VNODE,
|
|
|
|
.flags = EV_ADD | EV_CLEAR,
|
|
|
|
.fflags = fflags,
|
|
|
|
.data = 0,
|
|
|
|
.udata = NULL,
|
|
|
|
};
|
|
|
|
if (kevent(fwr->w.fd, &ev, 1, NULL, 0, NULL) < 0) {
|
|
|
|
log_error("Failed to register kevent: %s", strerror(errno));
|
|
|
|
close(wd);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
assert(false);
|
|
|
|
#endif // HAS_KQUEUE
|
2019-11-11 02:58:01 +08:00
|
|
|
|
|
|
|
auto w = ccalloc(1, struct watched_file);
|
|
|
|
w->wd = wd;
|
|
|
|
w->cb = cb;
|
|
|
|
w->ud = ud;
|
|
|
|
|
|
|
|
HASH_ADD_INT(fwr->reg, wd, w);
|
|
|
|
return true;
|
|
|
|
}
|