Skip to content

Commit 5e6dacb

Browse files
committed
file_watch: use kqueue on *BSD platforms
Tested on FreeBSD 12 Signed-off-by: Yuxuan Shui <[email protected]>
1 parent 9657d52 commit 5e6dacb

File tree

2 files changed

+66
-1
lines changed

2 files changed

+66
-1
lines changed

.clang-format

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ AllowShortIfStatementsOnASingleLine: false
1818
AllowShortCaseLabelsOnASingleLine: true
1919
AllowShortFunctionsOnASingleLine: false
2020
IndentCaseLabels: false
21+
IndentPPDirectives: None
2122
PenaltyReturnTypeOnItsOwnLine: 0
2223
PenaltyBreakAssignment: 0
2324
PenaltyBreakBeforeFirstCallParameter: 1

src/file_watch.c

+65-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
#include <string.h>
33
#ifdef HAS_INOTIFY
44
#include <sys/inotify.h>
5+
#elif HAS_KQUEUE
6+
#include <sys/event.h>
7+
#include <sys/types.h>
8+
#undef EV_ERROR // Avoid clashing with libev's EV_ERROR
9+
#include <fcntl.h> // For O_RDONLY
10+
#include <sys/time.h> // For struct timespec
11+
#include <unistd.h> // For open
512
#endif
613

714
#include <ev.h>
@@ -26,7 +33,7 @@ struct file_watch_registry {
2633
struct watched_file *reg;
2734
};
2835

29-
static void file_watch_ev_cb(EV_P_ struct ev_io *w, int revent attr_unused) {
36+
static void file_watch_ev_cb(EV_P attr_unused, struct ev_io *w, int revent attr_unused) {
3037
auto fwr = (struct file_watch_registry *)w;
3138

3239
while (true) {
@@ -42,6 +49,19 @@ static void file_watch_ev_cb(EV_P_ struct ev_io *w, int revent attr_unused) {
4249
break;
4350
}
4451
wd = inotify_event.wd;
52+
#elif HAS_KQUEUE
53+
struct kevent ev;
54+
struct timespec timeout = {0};
55+
int ret = kevent(fwr->w.fd, NULL, 0, &ev, 1, &timeout);
56+
if (ret <= 0) {
57+
if (ret < 0) {
58+
log_error("Failed to get kevent: %s", strerror(errno));
59+
}
60+
break;
61+
}
62+
wd = (int)ev.ident;
63+
#else
64+
assert(false);
4565
#endif
4666

4767
struct watched_file *wf = NULL;
@@ -63,6 +83,12 @@ void *file_watch_init(EV_P) {
6383
log_error("inotify_init1 failed: %s", strerror(errno));
6484
return NULL;
6585
}
86+
#elif HAS_KQUEUE
87+
fd = kqueue();
88+
if (fd < 0) {
89+
log_error("Failed to create kqueue: %s", strerror(errno));
90+
return NULL;
91+
}
6692
#else
6793
log_info("No file watching support found on the host system.");
6894
return NULL;
@@ -81,6 +107,12 @@ void file_watch_destroy(EV_P_ void *_fwr) {
81107

82108
HASH_ITER(hh, fwr->reg, i, tmp) {
83109
HASH_DEL(fwr->reg, i);
110+
#ifdef HAS_KQUEUE
111+
// kqueue watch descriptors are file descriptors of
112+
// the files we are watching, so we need to close
113+
// them
114+
close(i->wd);
115+
#endif
84116
free(i);
85117
}
86118

@@ -100,7 +132,39 @@ bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *
100132
log_error("Failed to watch file \"%s\": %s", filename, strerror(errno));
101133
return false;
102134
}
135+
#elif HAS_KQUEUE
136+
wd = open(filename, O_RDONLY);
137+
if (wd < 0) {
138+
log_error("Cannot open file \"%s\" for watching: %s", filename,
139+
strerror(errno));
140+
return false;
141+
}
142+
143+
uint32_t fflags = NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE | NOTE_ATTRIB;
144+
// NOTE_CLOSE_WRITE is relatively new, so we cannot just use it
145+
#ifdef NOTE_CLOSE_WRITE
146+
fflags |= NOTE_CLOSE_WRITE;
147+
#else
148+
// NOTE_WRITE will receive notification more frequent than necessary, so is less
149+
// preferrable
150+
fflags |= NOTE_WRITE;
103151
#endif
152+
struct kevent ev = {
153+
.ident = (unsigned int)wd, // the wd < 0 case is checked above
154+
.filter = EVFILT_VNODE,
155+
.flags = EV_ADD | EV_CLEAR,
156+
.fflags = fflags,
157+
.data = 0,
158+
.udata = NULL,
159+
};
160+
if (kevent(fwr->w.fd, &ev, 1, NULL, 0, NULL) < 0) {
161+
log_error("Failed to register kevent: %s", strerror(errno));
162+
close(wd);
163+
return false;
164+
}
165+
#else
166+
assert(false);
167+
#endif // HAS_KQUEUE
104168

105169
auto w = ccalloc(1, struct watched_file);
106170
w->wd = wd;

0 commit comments

Comments
 (0)