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