1 #include <errno.h>
2 #include <string.h>
3 #ifdef HAS_INOTIFY
4 #include <sys/inotify.h>
5 #elif HAS_KQUEUE
6 // clang-format off
7 #include <sys/types.h>
8 // clang-format on
9 #include <sys/event.h>
10 #undef EV_ERROR // Avoid clashing with libev's EV_ERROR
11 #include <fcntl.h> // For O_RDONLY
12 #include <sys/time.h> // For struct timespec
13 #include <unistd.h> // For open
14 #endif
15
16 #include <ev.h>
17 #include <uthash.h>
18
19 #include "file_watch.h"
20 #include "list.h"
21 #include "log.h"
22 #include "utils.h"
23
24 struct watched_file {
25 int wd;
26 void *ud;
27 file_watch_cb_t cb;
28
29 UT_hash_handle hh;
30 };
31
32 struct file_watch_registry {
33 struct ev_io w;
34
35 struct watched_file *reg;
36 };
37
file_watch_ev_cb(EV_P attr_unused,struct ev_io * w,int revent attr_unused)38 static void file_watch_ev_cb(EV_P attr_unused, struct ev_io *w, int revent attr_unused) {
39 auto fwr = (struct file_watch_registry *)w;
40
41 while (true) {
42 int wd = -1;
43 #ifdef HAS_INOTIFY
44 struct inotify_event inotify_event;
45 auto ret = read(w->fd, &inotify_event, sizeof(struct inotify_event));
46 if (ret < 0) {
47 if (errno != EAGAIN) {
48 log_error_errno("Failed to read from inotify fd");
49 }
50 break;
51 }
52 wd = inotify_event.wd;
53 #elif HAS_KQUEUE
54 struct kevent ev;
55 struct timespec timeout = {0};
56 int ret = kevent(fwr->w.fd, NULL, 0, &ev, 1, &timeout);
57 if (ret <= 0) {
58 if (ret < 0) {
59 log_error_errno("Failed to get kevent");
60 }
61 break;
62 }
63 wd = (int)ev.ident;
64 #else
65 assert(false);
66 #endif
67
68 struct watched_file *wf = NULL;
69 HASH_FIND_INT(fwr->reg, &wd, wf);
70 if (!wf) {
71 log_warn("Got notification for a file I didn't watch.");
72 continue;
73 }
74 wf->cb(wf->ud);
75 }
76 }
77
file_watch_init(EV_P)78 void *file_watch_init(EV_P) {
79 log_debug("Starting watching for file changes");
80 int fd = -1;
81 #ifdef HAS_INOTIFY
82 fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
83 if (fd < 0) {
84 log_error_errno("inotify_init1 failed");
85 return NULL;
86 }
87 #elif HAS_KQUEUE
88 fd = kqueue();
89 if (fd < 0) {
90 log_error_errno("Failed to create kqueue");
91 return NULL;
92 }
93 #else
94 log_info("No file watching support found on the host system.");
95 return NULL;
96 #endif
97 auto fwr = ccalloc(1, struct file_watch_registry);
98 ev_io_init(&fwr->w, file_watch_ev_cb, fd, EV_READ);
99 ev_io_start(EV_A_ & fwr->w);
100
101 return fwr;
102 }
103
file_watch_destroy(EV_P_ void * _fwr)104 void file_watch_destroy(EV_P_ void *_fwr) {
105 log_debug("Stopping watching for file changes");
106 auto fwr = (struct file_watch_registry *)_fwr;
107 struct watched_file *i, *tmp;
108
109 HASH_ITER(hh, fwr->reg, i, tmp) {
110 HASH_DEL(fwr->reg, i);
111 #ifdef HAS_KQUEUE
112 // kqueue watch descriptors are file descriptors of
113 // the files we are watching, so we need to close
114 // them
115 close(i->wd);
116 #endif
117 free(i);
118 }
119
120 ev_io_stop(EV_A_ & fwr->w);
121 close(fwr->w.fd);
122 free(fwr);
123 }
124
file_watch_add(void * _fwr,const char * filename,file_watch_cb_t cb,void * ud)125 bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *ud) {
126 log_debug("Adding \"%s\" to watched files", filename);
127 auto fwr = (struct file_watch_registry *)_fwr;
128 int wd = -1;
129
130 struct stat statbuf;
131 int ret = stat(filename, &statbuf);
132 if (ret < 0) {
133 log_error_errno("Failed to retrieve information about file \"%s\"", filename);
134 return false;
135 }
136 if (!S_ISREG(statbuf.st_mode)) {
137 log_info("\"%s\" is not a regular file, not watching it.", filename);
138 return false;
139 }
140
141 #ifdef HAS_INOTIFY
142 wd = inotify_add_watch(fwr->w.fd, filename,
143 IN_CLOSE_WRITE | IN_MOVE_SELF | IN_DELETE_SELF);
144 if (wd < 0) {
145 log_error_errno("Failed to watch file \"%s\"", filename);
146 return false;
147 }
148 #elif HAS_KQUEUE
149 wd = open(filename, O_RDONLY);
150 if (wd < 0) {
151 log_error_errno("Cannot open file \"%s\" for watching", filename);
152 return false;
153 }
154
155 uint32_t fflags = NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE | NOTE_ATTRIB;
156 // NOTE_CLOSE_WRITE is relatively new, so we cannot just use it
157 #ifdef NOTE_CLOSE_WRITE
158 fflags |= NOTE_CLOSE_WRITE;
159 #else
160 // NOTE_WRITE will receive notification more frequent than necessary, so is less
161 // preferrable
162 fflags |= NOTE_WRITE;
163 #endif
164 struct kevent ev = {
165 .ident = (unsigned int)wd, // the wd < 0 case is checked above
166 .filter = EVFILT_VNODE,
167 .flags = EV_ADD | EV_CLEAR,
168 .fflags = fflags,
169 .data = 0,
170 .udata = NULL,
171 };
172 if (kevent(fwr->w.fd, &ev, 1, NULL, 0, NULL) < 0) {
173 log_error_errno("Failed to register kevent");
174 close(wd);
175 return false;
176 }
177 #else
178 assert(false);
179 #endif // HAS_KQUEUE
180
181 auto w = ccalloc(1, struct watched_file);
182 w->wd = wd;
183 w->cb = cb;
184 w->ud = ud;
185
186 HASH_ADD_INT(fwr->reg, wd, w);
187 return true;
188 }
189