1 /* Copyright 2012-present Facebook, Inc.
2 * Licensed under the Apache License, Version 2.0 */
3
4 #include "watchman.h"
5
6 #ifdef HAVE_PORT_CREATE
7
8 #define WATCHMAN_PORT_EVENTS \
9 FILE_MODIFIED | FILE_ATTRIB | FILE_NOFOLLOW
10
11
12 struct portfs_root_state {
13 int port_fd;
14 /* map of file name to watchman_port_file */
15 w_ht_t *port_files;
16 /* protects port_files */
17 pthread_mutex_t lock;
18 port_event_t portevents[WATCHMAN_BATCH_LIMIT];
19 };
20
21 struct watchman_port_file {
22 file_obj_t port_file;
23 w_string_t *name;
24 };
25
26 static const struct flag_map pflags[] = {
27 {FILE_ACCESS, "FILE_ACCESS"},
28 {FILE_MODIFIED, "FILE_MODIFIED"},
29 {FILE_ATTRIB, "FILE_ATTRIB"},
30 {FILE_DELETE, "FILE_DELETE"},
31 {FILE_RENAME_TO, "FILE_RENAME_TO"},
32 {FILE_RENAME_FROM, "FILE_RENAME_FROM"},
33 {UNMOUNTED, "UNMOUNTED"},
34 {MOUNTEDOVER, "MOUNTEDOVER"},
35 {0, NULL},
36 };
37
make_port_file(w_string_t * name,struct stat * st)38 static struct watchman_port_file *make_port_file(w_string_t *name,
39 struct stat *st) {
40 struct watchman_port_file *f;
41
42 f = calloc(1, sizeof(*f));
43 if (!f) {
44 return NULL;
45 }
46 f->name = name;
47 w_string_addref(name);
48 f->port_file.fo_name = (char*)name->buf;
49 f->port_file.fo_atime = st->st_atim;
50 f->port_file.fo_mtime = st->st_mtim;
51 f->port_file.fo_ctime = st->st_ctim;
52
53 return f;
54 }
55
free_port_file(struct watchman_port_file * f)56 static void free_port_file(struct watchman_port_file *f) {
57 w_string_delref(f->name);
58 free(f);
59 }
60
portfs_del_port_file(w_ht_val_t key)61 static void portfs_del_port_file(w_ht_val_t key) {
62 free_port_file(w_ht_val_ptr(key));
63 }
64
65 const struct watchman_hash_funcs port_file_funcs = {
66 w_ht_string_copy,
67 w_ht_string_del,
68 w_ht_string_equal,
69 w_ht_string_hash,
70 NULL, // copy_val
71 portfs_del_port_file,
72 };
73
portfs_global_init(void)74 watchman_global_watcher_t portfs_global_init(void) {
75 return NULL;
76 }
77
portfs_global_dtor(watchman_global_watcher_t watcher)78 void portfs_global_dtor(watchman_global_watcher_t watcher) {
79 unused_parameter(watcher);
80 }
81
portfs_root_init(watchman_global_watcher_t watcher,w_root_t * root,char ** errmsg)82 bool portfs_root_init(watchman_global_watcher_t watcher, w_root_t *root,
83 char **errmsg) {
84 struct portfs_root_state *state;
85 unused_parameter(watcher);
86
87 state = calloc(1, sizeof(*state));
88 if (!state) {
89 *errmsg = strdup("out of memory");
90 return false;
91 }
92 root->watch = state;
93
94 pthread_mutex_init(&state->lock, NULL);
95 state->port_files = w_ht_new(HINT_NUM_DIRS, &port_file_funcs);
96
97 state->port_fd = port_create();
98 if (state->port_fd == -1) {
99 ignore_result(asprintf(errmsg, "watch(%.*s): port_create() error: %s",
100 root->root_path->len, root->root_path->buf, strerror(errno)));
101 w_log(W_LOG_ERR, "%s\n", *errmsg);
102 return false;
103 }
104 w_set_cloexec(state->port_fd);
105
106 return true;
107 }
108
portfs_root_dtor(watchman_global_watcher_t watcher,w_root_t * root)109 void portfs_root_dtor(watchman_global_watcher_t watcher, w_root_t *root) {
110 struct portfs_root_state *state = root->watch;
111 unused_parameter(watcher);
112
113 if (!state) {
114 return;
115 }
116
117 close(state->port_fd);
118 state->port_fd = -1;
119 w_ht_free(state->port_files);
120 pthread_mutex_destroy(&state->lock);
121
122 free(state);
123 root->watch = NULL;
124 }
125
portfs_root_signal_threads(watchman_global_watcher_t watcher,w_root_t * root)126 static void portfs_root_signal_threads(watchman_global_watcher_t watcher,
127 w_root_t *root) {
128 unused_parameter(watcher);
129 unused_parameter(root);
130 }
131
portfs_root_start(watchman_global_watcher_t watcher,w_root_t * root)132 static bool portfs_root_start(watchman_global_watcher_t watcher,
133 w_root_t *root) {
134 unused_parameter(watcher);
135 unused_parameter(root);
136
137 return true;
138 }
139
do_watch(struct portfs_root_state * state,w_string_t * name,struct stat * st)140 static bool do_watch(struct portfs_root_state *state, w_string_t *name,
141 struct stat *st) {
142 struct watchman_port_file *f;
143 bool success = false;
144
145 pthread_mutex_lock(&state->lock);
146 if (w_ht_get(state->port_files, w_ht_ptr_val(name))) {
147 // Already watching it
148 success = true;
149 goto out;
150 }
151
152 f = make_port_file(name, st);
153 if (!f) {
154 goto out;
155 }
156
157 if (!w_ht_set(state->port_files, w_ht_ptr_val(name), w_ht_ptr_val(f))) {
158 free_port_file(f);
159 goto out;
160 }
161
162 w_log(W_LOG_DBG, "watching %s\n", name->buf);
163 errno = 0;
164 if (port_associate(state->port_fd, PORT_SOURCE_FILE,
165 (uintptr_t)&f->port_file, WATCHMAN_PORT_EVENTS,
166 (void*)f)) {
167 w_log(W_LOG_ERR, "port_associate %s %s\n",
168 f->port_file.fo_name, strerror(errno));
169 w_ht_del(state->port_files, w_ht_ptr_val(name));
170 goto out;
171 }
172
173 success = true;
174
175 out:
176 pthread_mutex_unlock(&state->lock);
177 return success;
178 }
179
portfs_root_start_watch_file(watchman_global_watcher_t watcher,w_root_t * root,struct watchman_file * file)180 static bool portfs_root_start_watch_file(watchman_global_watcher_t watcher,
181 w_root_t *root, struct watchman_file *file) {
182 struct portfs_root_state *state = root->watch;
183 w_string_t *name;
184 bool success = false;
185
186 unused_parameter(watcher);
187
188 name = w_string_path_cat(file->parent->path, file->name);
189 if (!name) {
190 return false;
191 }
192 success = do_watch(state, name, &file->st);
193 w_string_delref(name);
194
195 return success;
196 }
197
portfs_root_stop_watch_file(watchman_global_watcher_t watcher,w_root_t * root,struct watchman_file * file)198 static void portfs_root_stop_watch_file(watchman_global_watcher_t watcher,
199 w_root_t *root, struct watchman_file *file) {
200 unused_parameter(watcher);
201 unused_parameter(root);
202 unused_parameter(file);
203 }
204
portfs_root_start_watch_dir(watchman_global_watcher_t watcher,w_root_t * root,struct watchman_dir * dir,struct timeval now,const char * path)205 static struct watchman_dir_handle *portfs_root_start_watch_dir(
206 watchman_global_watcher_t watcher,
207 w_root_t *root, struct watchman_dir *dir, struct timeval now,
208 const char *path) {
209 struct portfs_root_state *state = root->watch;
210 struct watchman_dir_handle *osdir;
211 struct stat st;
212 unused_parameter(watcher);
213
214 osdir = w_dir_open(path);
215 if (!osdir) {
216 handle_open_errno(root, dir, now, "opendir", errno, NULL);
217 return NULL;
218 }
219
220 if (fstat(dirfd(osdir), &st) == -1) {
221 // whaaa?
222 w_log(W_LOG_ERR, "fstat on opened dir %s failed: %s\n", path,
223 strerror(errno));
224 w_root_schedule_recrawl(root, "fstat failed");
225 w_dir_close(osdir);
226 return NULL;
227 }
228
229 if (!do_watch(state, dir->path, &st)) {
230 w_dir_close(osdir);
231 return NULL;
232 }
233
234 return osdir;
235 }
236
portfs_root_stop_watch_dir(watchman_global_watcher_t watcher,w_root_t * root,struct watchman_dir * dir)237 static void portfs_root_stop_watch_dir(watchman_global_watcher_t watcher,
238 w_root_t *root, struct watchman_dir *dir) {
239 unused_parameter(watcher);
240 unused_parameter(root);
241 unused_parameter(dir);
242 }
243
portfs_root_consume_notify(watchman_global_watcher_t watcher,w_root_t * root,struct watchman_pending_collection * coll)244 static bool portfs_root_consume_notify(watchman_global_watcher_t watcher,
245 w_root_t *root, struct watchman_pending_collection *coll)
246 {
247 struct portfs_root_state *state = root->watch;
248 uint_t i, n;
249 struct timeval now;
250 unused_parameter(watcher);
251
252 errno = 0;
253
254 n = 1;
255 if (port_getn(state->port_fd, state->portevents,
256 sizeof(state->portevents) / sizeof(state->portevents[0]), &n, NULL)) {
257 if (errno == EINTR) {
258 return false;
259 }
260 w_log(W_LOG_FATAL, "port_getn: %s\n",
261 strerror(errno));
262 }
263
264 w_log(W_LOG_DBG, "port_getn: n=%u\n", n);
265
266 if (n == 0) {
267 return false;
268 }
269
270 pthread_mutex_lock(&state->lock);
271
272 for (i = 0; i < n; i++) {
273 struct watchman_port_file *f;
274 uint32_t pe = state->portevents[i].portev_events;
275 char flags_label[128];
276
277 f = (struct watchman_port_file*)state->portevents[i].portev_user;
278 w_expand_flags(pflags, pe, flags_label, sizeof(flags_label));
279 w_log(W_LOG_DBG, "port: %s [0x%x %s]\n",
280 f->port_file.fo_name,
281 pe, flags_label);
282
283 if ((pe & (FILE_RENAME_FROM|UNMOUNTED|MOUNTEDOVER|FILE_DELETE))
284 && w_string_equal(f->name, root->root_path)) {
285
286 w_log(W_LOG_ERR,
287 "root dir %s has been (re)moved (code 0x%x %s), canceling watch\n",
288 root->root_path->buf, pe, flags_label);
289
290 w_root_cancel(root);
291 pthread_mutex_unlock(&state->lock);
292 return false;
293 }
294 w_pending_coll_add(coll, f->name, now,
295 W_PENDING_RECURSIVE|W_PENDING_VIA_NOTIFY);
296
297 // It was port_dissociate'd implicitly. We'll re-establish a
298 // watch later when portfs_root_start_watch_(file|dir) are called again
299 w_ht_del(state->port_files, w_ht_ptr_val(f->name));
300 }
301 pthread_mutex_unlock(&state->lock);
302
303 return true;
304 }
305
portfs_root_wait_notify(watchman_global_watcher_t watcher,w_root_t * root,int timeoutms)306 static bool portfs_root_wait_notify(watchman_global_watcher_t watcher,
307 w_root_t *root, int timeoutms) {
308 struct portfs_root_state *state = root->watch;
309 int n;
310 struct pollfd pfd;
311 unused_parameter(watcher);
312
313 pfd.fd = state->port_fd;
314 pfd.events = POLLIN;
315
316 n = poll(&pfd, 1, timeoutms);
317
318 return n == 1;
319 }
320
portfs_file_free(watchman_global_watcher_t watcher,struct watchman_file * file)321 static void portfs_file_free(watchman_global_watcher_t watcher,
322 struct watchman_file *file) {
323 unused_parameter(watcher);
324 unused_parameter(file);
325 }
326
327 struct watchman_ops portfs_watcher = {
328 "portfs",
329 0,
330 portfs_global_init,
331 portfs_global_dtor,
332 portfs_root_init,
333 portfs_root_start,
334 portfs_root_dtor,
335 portfs_root_start_watch_file,
336 portfs_root_stop_watch_file,
337 portfs_root_start_watch_dir,
338 portfs_root_stop_watch_dir,
339 portfs_root_signal_threads,
340 portfs_root_consume_notify,
341 portfs_root_wait_notify,
342 portfs_file_free
343 };
344
345 #endif // HAVE_INOTIFY_INIT
346
347 /* vim:ts=2:sw=2:et:
348 */
349