Linux file watcher: IDEA-87821; don't follow symlinks; cleanup
authorRoman Shevchenko <roman.shevchenko@jetbrains.com>
Tue, 17 Jul 2012 15:49:47 +0000 (17:49 +0200)
committerRoman Shevchenko <roman.shevchenko@jetbrains.com>
Tue, 17 Jul 2012 16:23:49 +0000 (18:23 +0200)
bin/linux/fsnotifier
bin/linux/fsnotifier64
native/fsNotifier/linux/fsnotifier.h
native/fsNotifier/linux/inotify.c
native/fsNotifier/linux/main.c

index 715b78f2d45fadecb37bb98741bfb23b28b2498e..6075b42a38a8cef81fd4ecf307a0b55701e3bc52 100755 (executable)
Binary files a/bin/linux/fsnotifier and b/bin/linux/fsnotifier differ
index 13dfd34a954923b1cb87f54ab51042a68de4f113..ed0854873662120916e4c24a738f9cdd9f1b3f01 100755 (executable)
Binary files a/bin/linux/fsnotifier64 and b/bin/linux/fsnotifier64 differ
index f3234e2fde9d79285d26068b7853b6328e9b3133..b8dd17b388311bcc03184e276fc7456587405856 100644 (file)
@@ -61,7 +61,7 @@ void set_inotify_callback(void (* callback)(char*, int));
 int get_inotify_fd();
 int get_watch_count();
 bool watch_limit_reached();
-int watch(const char* root, array* ignores);
+int watch(const char* root);
 void unwatch(int id);
 bool process_inotify_input();
 void close_inotify();
index 04d749e48d83bd9be122cd15d47ec987b4f6c39a..d71737fee507201e79542bf8a1fb48db274d7917 100644 (file)
@@ -117,8 +117,10 @@ inline bool watch_limit_reached() {
 }
 
 
+#define EVENT_MASK IN_MODIFY | IN_ATTRIB | IN_CREATE | IN_DELETE | IN_MOVE | IN_DELETE_SELF
+
 static int add_watch(const char* path, watch_node* parent) {
-  int wd = inotify_add_watch(inotify_fd, path, IN_MODIFY | IN_ATTRIB | IN_CREATE | IN_DELETE | IN_MOVE | IN_DELETE_SELF);
+  int wd = inotify_add_watch(inotify_fd, path, EVENT_MASK);
   if (wd < 0) {
     if (errno == ENOSPC) {
       limit_reached = true;
@@ -132,7 +134,11 @@ static int add_watch(const char* path, watch_node* parent) {
 
   watch_node* node = table_get(watches, wd);
   if (node != NULL) {
-    if (node->wd != wd || strcmp(node->name, path) != 0) {
+    if (node->wd != wd) {
+      userlog(LOG_ERR, "table error: corruption at %d:%s / %d:%s)", wd, path, node->wd, node->name);
+      return ERR_ABORT;
+    }
+    else if (strcmp(node->name, path) != 0) {
       char buf1[PATH_MAX], buf2[PATH_MAX];
       const char* normalized1 = realpath(node->name, buf1);
       const char* normalized2 = realpath(path, buf2);
@@ -141,7 +147,7 @@ static int add_watch(const char* path, watch_node* parent) {
         return ERR_ABORT;
       }
       else {
-        userlog(LOG_WARNING, "intersection at %d: (new %s, existing %s, real %s)", wd, path, node->name, normalized1);
+        userlog(LOG_INFO, "intersection at %d: (new %s, existing %s, real %s)", wd, path, node->name, normalized1);
         return ERR_IGNORE;
       }
     }
@@ -210,54 +216,24 @@ static void rm_watch(int wd, bool update_parent) {
 }
 
 
-static bool is_directory(struct dirent* entry, const char* path) {
-  if (entry->d_type == DT_DIR) {
-    return true;
-  }
-  else if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) {
-    struct stat st;
-    return (stat(path, &st) == 0 && S_ISDIR(st.st_mode));
-  }
-  return false;
-}
-
-static bool is_ignored(const char* path, array* ignores) {
-  if (ignores != NULL) {
-    int pl = strlen(path);
-    for (int i=0; i<array_size(ignores); i++) {
-      const char* ignore = array_get(ignores, i);
-      int il = strlen(ignore);
-      if (pl >= il && strncmp(path, ignore, il) == 0) {
-        userlog(LOG_DEBUG, "path %s is under unwatchable %s - ignoring", path, ignore);
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-static int walk_tree(const char* path, watch_node* parent, array* ignores, bool recursive) {
-  if (is_ignored(path, ignores)) {
-    return ERR_IGNORE;
-  }
-
+static int walk_tree(const char* path, watch_node* parent, bool recursive) {
   DIR* dir;
   if (recursive) {
-    dir = opendir(path);
-    if (dir == NULL) {
-      if (errno == EACCES) {
+    if ((dir = opendir(path)) == NULL) {
+      if (errno == EACCES || errno == ENOENT) {
+        userlog(LOG_DEBUG, "opendir(%s): %d", path, errno);
         return ERR_IGNORE;
       }
-      else if (errno == ENOTDIR) {  // "future" root
-        return add_watch(path, parent);
+      else {
+        userlog(LOG_ERR, "opendir(%s): %s", path, strerror(errno));
+        return ERR_CONTINUE;
       }
-      userlog(LOG_ERR, "opendir(%s): %s", path, strerror(errno));
-      return ERR_CONTINUE;
     }
   }
 
   int id = add_watch(path, parent);
-  if (!recursive) {
+
+  if (dir == NULL) {
     return id;
   }
   else if (id < 0) {
@@ -265,7 +241,6 @@ static int walk_tree(const char* path, watch_node* parent, array* ignores, bool
     return id;
   }
 
-  struct dirent* entry;
   char subdir[PATH_MAX];
   strcpy(subdir, path);
   if (subdir[strlen(subdir) - 1] != '/') {
@@ -273,17 +248,16 @@ static int walk_tree(const char* path, watch_node* parent, array* ignores, bool
   }
   char* p = subdir + strlen(subdir);
 
+  struct dirent* entry;
   while ((entry = readdir(dir)) != NULL) {
-    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
+    if (entry->d_type != DT_DIR ||
+        strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
       continue;
     }
 
     strcpy(p, entry->d_name);
-    if (!is_directory(entry, subdir)) {
-      continue;
-    }
 
-    int subdir_id = walk_tree(subdir, table_get(watches, id), ignores, recursive);
+    int subdir_id = walk_tree(subdir, table_get(watches, id), recursive);
     if (subdir_id < 0 && subdir_id != ERR_IGNORE) {
       rm_watch(id, true);
       id = subdir_id;
@@ -296,16 +270,34 @@ static int walk_tree(const char* path, watch_node* parent, array* ignores, bool
 }
 
 
-int watch(const char* root, array* ignores) {
+int watch(const char* root) {
   bool recursive = true;
   if (root[0] == '|') {
     root++;
     recursive = false;
   }
 
-  char buf[PATH_MAX];
-  const char* normalized = realpath(root, buf);
-  return walk_tree((normalized != NULL ? normalized : root), NULL, ignores, recursive);
+  struct stat st;
+  if (stat(root, &st) != 0) {
+    if (errno == EACCES) {
+      return ERR_IGNORE;
+    }
+    else if (errno == ENOENT) {
+      return ERR_CONTINUE;
+    }
+    userlog(LOG_ERR, "stat(%s): %s", root, strerror(errno));
+    return ERR_ABORT;
+  }
+
+  if (S_ISREG(st.st_mode)) {
+    recursive = false;
+  }
+  else if (!S_ISDIR(st.st_mode)) {
+    userlog(LOG_WARNING, "unexpected node type: %s, %d", root, st.st_mode);
+    return ERR_IGNORE;
+  }
+
+  return walk_tree(root, NULL, recursive);
 }
 
 
@@ -320,8 +312,9 @@ static bool process_inotify_event(struct inotify_event* event) {
     return true;
   }
 
+  bool is_dir = (event->mask & IN_ISDIR) == IN_ISDIR;
   userlog(LOG_DEBUG, "inotify: wd=%d mask=%d dir=%d name=%s",
-      event->wd, event->mask & (~IN_ISDIR), (event->mask & IN_ISDIR) != 0, node->name);
+      event->wd, event->mask & (~IN_ISDIR), is_dir, node->name);
 
   char path[PATH_MAX];
   strcpy(path, node->name);
@@ -332,14 +325,14 @@ static bool process_inotify_event(struct inotify_event* event) {
     strcat(path, event->name);
   }
 
-  if ((event->mask & IN_CREATE || event->mask & IN_MOVED_TO) && event->mask & IN_ISDIR) {
-    int result = walk_tree(path, node, NULL, true);
+  if (is_dir && ((event->mask & IN_CREATE) == IN_CREATE || (event->mask & IN_MOVED_TO) == IN_MOVED_TO)) {
+    int result = walk_tree(path, node, true);
     if (result < 0 && result != ERR_IGNORE) {
       return false;
     }
   }
 
-  if ((event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) && event->mask & IN_ISDIR) {
+  if (is_dir && ((event->mask & IN_DELETE) == IN_DELETE || (event->mask & IN_MOVED_FROM) == IN_MOVED_FROM)) {
     for (int i=0; i<array_size(node->kids); i++) {
       watch_node* kid = array_get(node->kids, i);
       if (kid != NULL && strcmp(kid->name, path) == 0) {
index ce9d6c275ea7ca948184cf9391a579a4adaff4b5..180140b344f7d28faa57eccb57f277320ebe914a 100644 (file)
@@ -33,7 +33,7 @@
 #define LOG_ENV_ERROR "error"
 #define LOG_ENV_OFF "off"
 
-#define VERSION "1.1"
+#define VERSION "1.2"
 #define VERSION_MSG "fsnotifier " VERSION "\n"
 
 #define USAGE_MSG \
@@ -215,6 +215,7 @@ static bool read_input() {
   userlog(LOG_DEBUG, "input: %s", (line ? line : "<null>"));
 
   if (line == NULL || strcmp(line, "EXIT") == 0) {
+    userlog(LOG_INFO, "exiting: %s", line);
     return false;
   }
 
@@ -241,6 +242,12 @@ static bool read_input() {
     return update_roots(new_roots);
   }
 
+  if (strcmp(line, "VERSION") == 0) {
+    output(VERSION "\n");
+    return true;
+  }
+
+  userlog(LOG_INFO, "unrecognised command: %s", line);
   return true;
 }
 
@@ -302,15 +309,22 @@ static void unregister_roots() {
 static bool register_roots(array* new_roots, array* unwatchable, array* mounts) {
   for (int i=0; i<array_size(new_roots); i++) {
     char* new_root = array_get(new_roots, i);
+    char* unflattened = new_root;
+    if (unflattened[0] == '|') ++unflattened;
     userlog(LOG_INFO, "registering root: %s", new_root);
 
-    char* root = new_root;
-    if (*root == '|') ++root;
+    if (unflattened[0] != '/') {
+      userlog(LOG_WARNING, "  ... not valid, skipped");
+      free(new_root);
+      continue;
+    }
+
     char* skip = NULL;
     for (int j=0; j<array_size(mounts); j++) {
       char* mount = array_get(mounts, j);
-      if (strncmp(mount, root, strlen(mount)) == 0) {
-        skip = root;
+      if (strncmp(mount, unflattened, strlen(mount)) == 0) {
+        userlog(LOG_DEBUG, "path %s is under unwatchable %s - ignoring", unflattened, mount);
+        skip = unflattened;
         break;
       }
     }
@@ -319,19 +333,22 @@ static bool register_roots(array* new_roots, array* unwatchable, array* mounts)
       continue;
     }
 
-    // todo: consider a mount point under a watch root
-    int id = watch(new_root, unwatchable);
+    // todo: consider a mount point under a watch root?
+    int id = watch(new_root);
 
-    if (id == ERR_ABORT) {
-      return false;
-    }
-    else if (id >= 0) {
+    if (id >= 0) {
       watch_root* root = malloc(sizeof(watch_root));
       CHECK_NULL(root, false);
       root->id = id;
       root->name = new_root;
       CHECK_NULL(array_push(roots, root), false);
     }
+    else if (id == ERR_ABORT) {
+      return false;
+    }
+    else if (id == ERR_IGNORE) {
+      free(new_root);
+    }
     else {
       if (show_warning && watch_limit_reached()) {
         int limit = get_watch_count();
@@ -339,7 +356,10 @@ static bool register_roots(array* new_roots, array* unwatchable, array* mounts)
         output("MESSAGE\n" INOTIFY_LIMIT_MSG, limit);
         show_warning = false;  // warn only once
       }
-      CHECK_NULL(array_push(unwatchable, new_root), false);
+      char* copy = strdup(unflattened);
+      CHECK_NULL(copy, false);
+      CHECK_NULL(array_push(unwatchable, copy), false);
+      free(new_root);
     }
   }