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