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.
3 #include "fsnotifier.h"
10 #include <sys/inotify.h>
15 #define WATCH_COUNT_NAME "/proc/sys/fs/inotify/max_user_watches"
17 #define DEFAULT_SUBDIR_COUNT 5
19 typedef struct watch_node_str {
21 struct watch_node_str* parent;
23 unsigned int path_len;
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;
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];
37 static char path_buf[2 * PATH_MAX];
39 static void read_watch_descriptors_count();
40 static void watch_limit_reached();
44 inotify_fd = inotify_init();
47 userlog(LOG_ERR, "inotify_init: %s", strerror(e));
49 message("inotify.instance.limit");
54 read_watch_descriptors_count();
55 if (watch_count <= 0) {
60 userlog(LOG_INFO, "inotify watch descriptors: %d", watch_count);
62 watches = table_create(watch_count);
63 if (watches == NULL) {
64 userlog(LOG_ERR, "out of memory");
73 static void read_watch_descriptors_count() {
74 FILE* f = fopen(WATCH_COUNT_NAME, "r");
76 userlog(LOG_ERR, "can't open %s: %s", WATCH_COUNT_NAME, strerror(errno));
80 char* str = read_line(f);
82 userlog(LOG_ERR, "can't read from %s", WATCH_COUNT_NAME);
85 watch_count = (int)strtol(str, NULL, 10);
92 void set_inotify_callback(void (* _callback)(const char*, uint32_t)) {
97 int get_inotify_fd() {
102 #define EVENT_MASK (IN_MODIFY | IN_ATTRIB | IN_CREATE | IN_DELETE | IN_MOVE | IN_DELETE_SELF | IN_MOVE_SELF)
104 static int add_watch(unsigned int path_len, watch_node* parent) {
105 int wd = inotify_add_watch(inotify_fd, path_buf, EVENT_MASK);
107 if (errno == EACCES || errno == ENOENT) {
108 userlog(LOG_INFO, "inotify_add_watch(%s): %s", path_buf, strerror(errno));
111 else if (errno == ENOSPC) {
112 userlog(LOG_WARNING, "inotify_add_watch(%s): %s", path_buf, strerror(errno));
113 watch_limit_reached();
117 userlog(LOG_ERR, "inotify_add_watch(%s): %s", path_buf, strerror(errno));
122 userlog(LOG_INFO, "watching %s: %d", path_buf, wd);
125 watch_node* node = table_get(watches, wd);
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);
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);
140 userlog(LOG_INFO, "intersection at %d: (new %s, existing %s, real %s)", wd, path_buf, node->path, normalized1);
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;
153 node->parent = parent;
156 if (parent != NULL) {
157 if (parent->kids == NULL) {
158 parent->kids = array_create(DEFAULT_SUBDIR_COUNT);
159 CHECK_NULL(parent->kids, ERR_ABORT)
161 CHECK_NULL(array_push(parent->kids, node), ERR_ABORT)
164 if (table_put(watches, wd, node) == NULL) {
165 userlog(LOG_ERR, "table error: unable to put (%d:%s)", wd, path_buf);
172 static void watch_limit_reached() {
173 if (!limit_reached) {
174 limit_reached = true;
175 message("inotify.watch.limit");
179 static void rm_watch(int wd, bool update_parent) {
180 watch_node* node = table_get(watches, wd);
185 userlog(LOG_INFO, "unwatching %s: %d (%p)", node->path, node->wd, node);
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));
191 for (int i = 0; i < array_size(node->kids); i++) {
192 watch_node* kid = array_get(node->kids, i);
194 rm_watch(kid->wd, false);
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);
207 array_delete(node->kids);
209 table_put(watches, wd, NULL);
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);
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);
230 userlog(LOG_ERR, "opendir(%s): %s", path_buf, strerror(errno));
236 int id = add_watch(path_len, parent);
246 path_buf[path_len] = '/';
248 struct dirent* entry;
249 while ((entry = readdir(dir)) != NULL) {
250 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
253 if (entry->d_type != DT_UNKNOWN && entry->d_type != DT_DIR) {
257 unsigned int name_len = strlen(entry->d_name);
258 memcpy(path_buf + path_len + 1, entry->d_name, name_len + 1);
260 if (entry->d_type == DT_UNKNOWN) {
262 if (stat(path_buf, &st) != 0) {
263 userlog(LOG_INFO, "(DT_UNKNOWN) stat(%s): %d", path_buf, errno);
266 if (!S_ISDIR(st.st_mode)) {
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) {
284 int watch(const char* root, array* mounts) {
285 bool recursive = true;
286 if (root[0] == '|') {
291 size_t path_len = strlen(root);
292 if (root[path_len - 1] == '/') {
297 if (stat(root, &st) != 0) {
298 if (errno == ENOENT) {
301 else if (errno == EACCES || errno == ELOOP || errno == ENAMETOOLONG || errno == ENOTDIR) {
302 userlog(LOG_INFO, "stat(%s): %s", root, strerror(errno));
306 userlog(LOG_ERR, "stat(%s): %s", root, strerror(errno));
311 if (S_ISREG(st.st_mode)) {
314 else if (!S_ISDIR(st.st_mode)) {
315 userlog(LOG_WARNING, "unexpected node type: %s, %d", root, st.st_mode);
319 memcpy(path_buf, root, path_len);
320 path_buf[path_len] = '\0';
321 return walk_tree(path_len, NULL, recursive, mounts);
325 void unwatch(int id) {
330 static bool process_inotify_event(struct inotify_event* event) {
331 watch_node* node = table_get(watches, event->wd);
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);
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;
348 if (callback != NULL) {
349 (*callback)(path_buf, event->mask);
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) {
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);
374 bool process_inotify_input() {
375 ssize_t len = read(inotify_fd, event_buf, EVENT_BUF_LEN);
377 userlog(LOG_ERR, "read: %s", strerror(errno));
383 struct inotify_event *event = (struct inotify_event *) &event_buf[i];
384 i += (int)EVENT_SIZE + event->len;
386 if (event->mask & IN_IGNORED) {
389 if (event->mask & IN_Q_OVERFLOW) {
390 userlog(LOG_INFO, "event queue overflow");
394 if (!process_inotify_event(event)) {
403 void close_inotify() {
404 if (watches != NULL) {
405 table_delete(watches);
408 if (inotify_fd >= 0) {