Linux file watcher: do not dive into flat roots
[idea/community.git] / native / fsNotifier / linux / inotify.c
1 /*
2  * Copyright 2000-2012 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "fsnotifier.h"
18
19 #include <dirent.h>
20 #include <errno.h>
21 #include <linux/limits.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/inotify.h>
26 #include <sys/stat.h>
27 #include <syslog.h>
28 #include <unistd.h>
29
30
31 #define WATCH_COUNT_NAME "/proc/sys/fs/inotify/max_user_watches"
32
33 #define DEFAULT_SUBDIR_COUNT 5
34
35 typedef struct __watch_node {
36   char* name;
37   int wd;
38   struct __watch_node* parent;
39   array* kids;
40 } watch_node;
41
42 static int inotify_fd = -1;
43 static int watch_count = 0;
44 static table* watches;
45 static bool limit_reached = false;
46 static void (* callback)(char*, int) = NULL;
47
48 #define EVENT_SIZE (sizeof(struct inotify_event))
49 #define EVENT_BUF_LEN (2048 * (EVENT_SIZE + 16))
50 static char event_buf[EVENT_BUF_LEN];
51
52
53 static void read_watch_descriptors_count() {
54   FILE* f = fopen(WATCH_COUNT_NAME, "r");
55   if (f == NULL) {
56     userlog(LOG_ERR, "can't open %s: %s", WATCH_COUNT_NAME, strerror(errno));
57     return;
58   }
59
60   char* str = read_line(f);
61   if (str == NULL) {
62     userlog(LOG_ERR, "can't read from %s", WATCH_COUNT_NAME);
63   }
64   else {
65     watch_count = atoi(str);
66   }
67
68   fclose(f);
69 }
70
71
72 bool init_inotify() {
73   inotify_fd = inotify_init();
74   if (inotify_fd < 0) {
75     userlog(LOG_ERR, "inotify_init: %s", strerror(errno));
76     return false;
77   }
78   userlog(LOG_DEBUG, "inotify fd: %d", get_inotify_fd());
79
80   read_watch_descriptors_count();
81   if (watch_count <= 0) {
82     close(inotify_fd);
83     inotify_fd = -1;
84     return false;
85   }
86   userlog(LOG_INFO, "inotify watch descriptors: %d", watch_count);
87
88   watches = table_create(watch_count);
89   if (watches == NULL) {
90     userlog(LOG_ERR, "out of memory");
91     close(inotify_fd);
92     inotify_fd = -1;
93     return false;
94   }
95
96   return true;
97 }
98
99
100 inline void set_inotify_callback(void (* _callback)(char*, int)) {
101   callback = _callback;
102 }
103
104
105 inline int get_inotify_fd() {
106   return inotify_fd;
107 }
108
109
110 inline int get_watch_count() {
111   return watch_count;
112 }
113
114
115 inline bool watch_limit_reached() {
116   return limit_reached;
117 }
118
119
120 static int add_watch(const char* path, watch_node* parent) {
121   int wd = inotify_add_watch(inotify_fd, path, IN_MODIFY | IN_ATTRIB | IN_CREATE | IN_DELETE | IN_MOVE | IN_DELETE_SELF);
122   if (wd < 0) {
123     if (errno == ENOSPC) {
124       limit_reached = true;
125     }
126     userlog(LOG_ERR, "inotify_add_watch(%s): %s", path, strerror(errno));
127     return ERR_CONTINUE;
128   }
129   else {
130     userlog(LOG_DEBUG, "watching %s: %d", path, wd);
131   }
132
133   watch_node* node = table_get(watches, wd);
134   if (node != NULL) {
135     if (node->wd != wd || strcmp(node->name, path) != 0) {
136       char buf1[PATH_MAX], buf2[PATH_MAX];
137       const char* normalized1 = realpath(node->name, buf1);
138       const char* normalized2 = realpath(path, buf2);
139       if (normalized1 == NULL || normalized2 == NULL || strcmp(normalized1, normalized2) != 0) {
140         userlog(LOG_ERR, "table error: collision at %d (new %s, existing %s)", wd, path, node->name);
141         return ERR_ABORT;
142       }
143       else {
144         userlog(LOG_WARNING, "intersection at %d: (new %s, existing %s, real %s)", wd, path, node->name, normalized1);
145         return ERR_IGNORE;
146       }
147     }
148
149     return wd;
150   }
151
152   node = malloc(sizeof(watch_node));
153
154   CHECK_NULL(node, ERR_ABORT);
155   node->name = strdup(path);
156   CHECK_NULL(node->name, ERR_ABORT);
157   node->wd = wd;
158   node->parent = parent;
159   node->kids = NULL;
160
161   if (parent != NULL) {
162     if (parent->kids == NULL) {
163       parent->kids = array_create(DEFAULT_SUBDIR_COUNT);
164       CHECK_NULL(parent->kids, ERR_ABORT);
165     }
166     CHECK_NULL(array_push(parent->kids, node), ERR_ABORT);
167   }
168
169   if (table_put(watches, wd, node) == NULL) {
170     userlog(LOG_ERR, "table error: unable to put (%d:%s)", wd, path);
171     return ERR_ABORT;
172   }
173
174   return wd;
175 }
176
177
178 static void rm_watch(int wd, bool update_parent) {
179   watch_node* node = table_get(watches, wd);
180   if (node == NULL) {
181     return;
182   }
183
184   userlog(LOG_DEBUG, "unwatching %s: %d (%p)", node->name, node->wd, node);
185
186   if (inotify_rm_watch(inotify_fd, node->wd) < 0) {
187     userlog(LOG_DEBUG, "inotify_rm_watch(%d:%s): %s", node->wd, node->name, strerror(errno));
188   }
189
190   for (int i=0; i<array_size(node->kids); i++) {
191     watch_node* kid = array_get(node->kids, i);
192     if (kid != NULL) {
193       rm_watch(kid->wd, false);
194     }
195   }
196
197   if (update_parent && node->parent != NULL) {
198     for (int i=0; i<array_size(node->parent->kids); i++) {
199       if (array_get(node->parent->kids, i) == node) {
200         array_put(node->parent->kids, i, NULL);
201         break;
202       }
203     }
204   }
205
206   free(node->name);
207   array_delete(node->kids);
208   free(node);
209   table_put(watches, wd, NULL);
210 }
211
212
213 static bool is_directory(struct dirent* entry, const char* path) {
214   if (entry->d_type == DT_DIR) {
215     return true;
216   }
217   else if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) {
218     struct stat st;
219     return (stat(path, &st) == 0 && S_ISDIR(st.st_mode));
220   }
221   return false;
222 }
223
224 static bool is_ignored(const char* path, array* ignores) {
225   if (ignores != NULL) {
226     int pl = strlen(path);
227     for (int i=0; i<array_size(ignores); i++) {
228       const char* ignore = array_get(ignores, i);
229       int il = strlen(ignore);
230       if (pl >= il && strncmp(path, ignore, il) == 0) {
231         userlog(LOG_DEBUG, "path %s is under unwatchable %s - ignoring", path, ignore);
232         return true;
233       }
234     }
235   }
236   return false;
237 }
238
239 static int walk_tree(const char* path, watch_node* parent, array* ignores, bool recursive) {
240   if (is_ignored(path, ignores)) {
241     return ERR_IGNORE;
242   }
243
244   DIR* dir;
245   if (recursive) {
246     dir = opendir(path);
247     if (dir == NULL) {
248       if (errno == EACCES) {
249         return ERR_IGNORE;
250       }
251       else if (errno == ENOTDIR) {  // "future" root
252         return add_watch(path, parent);
253       }
254       userlog(LOG_ERR, "opendir(%s): %s", path, strerror(errno));
255       return ERR_CONTINUE;
256     }
257   }
258
259   int id = add_watch(path, parent);
260   if (!recursive) {
261     return id;
262   }
263   else if (id < 0) {
264     closedir(dir);
265     return id;
266   }
267
268   struct dirent* entry;
269   char subdir[PATH_MAX];
270   strcpy(subdir, path);
271   if (subdir[strlen(subdir) - 1] != '/') {
272     strcat(subdir, "/");
273   }
274   char* p = subdir + strlen(subdir);
275
276   while ((entry = readdir(dir)) != NULL) {
277     if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
278       continue;
279     }
280
281     strcpy(p, entry->d_name);
282     if (!is_directory(entry, subdir)) {
283       continue;
284     }
285
286     int subdir_id = walk_tree(subdir, table_get(watches, id), ignores, recursive);
287     if (subdir_id < 0 && subdir_id != ERR_IGNORE) {
288       rm_watch(id, true);
289       id = subdir_id;
290       break;
291     }
292   }
293
294   closedir(dir);
295   return id;
296 }
297
298
299 int watch(const char* root, array* ignores) {
300   bool recursive = true;
301   if (root[0] == '|') {
302     root++;
303     recursive = false;
304   }
305
306   char buf[PATH_MAX];
307   const char* normalized = realpath(root, buf);
308   return walk_tree((normalized != NULL ? normalized : root), NULL, ignores, recursive);
309 }
310
311
312 void unwatch(int id) {
313   rm_watch(id, true);
314 }
315
316
317 static bool process_inotify_event(struct inotify_event* event) {
318   watch_node* node = table_get(watches, event->wd);
319   if (node == NULL) {
320     return true;
321   }
322
323   userlog(LOG_DEBUG, "inotify: wd=%d mask=%d dir=%d name=%s",
324       event->wd, event->mask & (~IN_ISDIR), (event->mask & IN_ISDIR) != 0, node->name);
325
326   char path[PATH_MAX];
327   strcpy(path, node->name);
328   if (event->len > 0) {
329     if (path[strlen(path) - 1] != '/') {
330       strcat(path, "/");
331     }
332     strcat(path, event->name);
333   }
334
335   if ((event->mask & IN_CREATE || event->mask & IN_MOVED_TO) && event->mask & IN_ISDIR) {
336     int result = walk_tree(path, node, NULL, true);
337     if (result < 0 && result != ERR_IGNORE) {
338       return false;
339     }
340   }
341
342   if ((event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) && event->mask & IN_ISDIR) {
343     for (int i=0; i<array_size(node->kids); i++) {
344       watch_node* kid = array_get(node->kids, i);
345       if (kid != NULL && strcmp(kid->name, path) == 0) {
346         rm_watch(kid->wd, false);
347         array_put(node->kids, i, NULL);
348         break;
349       }
350     }
351   }
352
353   if (callback != NULL) {
354     (*callback)(path, event->mask);
355   }
356   return true;
357 }
358
359
360 bool process_inotify_input() {
361   ssize_t len = read(inotify_fd, event_buf, EVENT_BUF_LEN);
362   if (len < 0) {
363     userlog(LOG_ERR, "read: %s", strerror(errno));
364     return false;
365   }
366
367   int i = 0;
368   while (i < len) {
369     struct inotify_event* event = (struct inotify_event*) &event_buf[i];
370     i += EVENT_SIZE + event->len;
371
372     if (event->mask & IN_IGNORED) {
373       continue;
374     }
375     if (event->mask & IN_Q_OVERFLOW) {
376       userlog(LOG_ERR, "event queue overflow");
377       continue;
378     }
379
380     if (!process_inotify_event(event)) {
381       return false;
382     }
383   }
384
385   return true;
386 }
387
388
389 void close_inotify() {
390   if (watches != NULL) {
391     table_delete(watches);
392   }
393
394   if (inotify_fd >= 0) {
395     close(inotify_fd);
396   }
397 }