Linux file watcher: poll for missing roots
authorRoman Shevchenko <roman.shevchenko@jetbrains.com>
Tue, 5 Mar 2013 17:03:49 +0000 (18:03 +0100)
committerRoman Shevchenko <roman.shevchenko@jetbrains.com>
Tue, 5 Mar 2013 17:05:04 +0000 (18:05 +0100)
bin/linux/fsnotifier
bin/linux/fsnotifier64
native/fsNotifier/linux/fsnotifier.h
native/fsNotifier/linux/inotify.c
native/fsNotifier/linux/main.c
native/fsNotifier/linux/util.c
platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java
platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java

index a34b7855db4f8fc3fa4bc66447e74a79d662202d..3e49fd732b8b7c41159db06e5208c4231d784313 100755 (executable)
Binary files a/bin/linux/fsnotifier and b/bin/linux/fsnotifier differ
index 812e326cd6a1136a4e233e1d3890dc7e1d94cd9a..97861b8423d3052729e9c53aabe7d90fb84d2aeb 100755 (executable)
Binary files a/bin/linux/fsnotifier64 and b/bin/linux/fsnotifier64 differ
index 0140d6be81022b6da66cb0534f223df1a21e0baf..39d81add4b696a0c6396a6b5d526331fd4f730c5 100644 (file)
@@ -46,6 +46,7 @@ void array_put(array* a, int index, void* element);
 void* array_get(array* a, int index);
 void array_delete(array* a);
 void array_delete_vs_data(array* a);
+void array_delete_data(array* a);
 
 
 // poor man's hash table
@@ -61,7 +62,8 @@ void table_delete(table* t);
 enum {
   ERR_IGNORE = -1,
   ERR_CONTINUE = -2,
-  ERR_ABORT = -3
+  ERR_ABORT = -3,
+  ERR_MISSING = -4
 };
 
 bool init_inotify();
index 8570d032e78294e0d750d7e641727be316e57f31..cf2b33bd638441de590d6ec730b635ab4628d41d 100644 (file)
@@ -113,7 +113,7 @@ inline int get_inotify_fd() {
 }
 
 
-#define EVENT_MASK IN_MODIFY | IN_ATTRIB | IN_CREATE | IN_DELETE | IN_MOVE | IN_DELETE_SELF
+#define EVENT_MASK IN_MODIFY | IN_ATTRIB | IN_CREATE | IN_DELETE | IN_MOVE | IN_DELETE_SELF | IN_MOVE_SELF
 
 static int add_watch(const char* path, watch_node* parent) {
   int wd = inotify_add_watch(inotify_fd, path, EVENT_MASK);
@@ -301,7 +301,7 @@ int watch(const char* root, array* mounts) {
       return ERR_IGNORE;
     }
     else if (errno == ENOENT) {
-      return ERR_CONTINUE;
+      return ERR_MISSING;
     }
     userlog(LOG_ERR, "stat(%s): %s", root, strerror(errno));
     return ERR_ABORT;
@@ -342,14 +342,14 @@ static bool process_inotify_event(struct inotify_event* event) {
     strcat(path, event->name);
   }
 
-  if (is_dir && ((event->mask & IN_CREATE) == IN_CREATE || (event->mask & IN_MOVED_TO) == IN_MOVED_TO)) {
+  if (is_dir && event->mask & (IN_CREATE | IN_MOVED_TO)) {
     int result = walk_tree(path, node, true, NULL);
     if (result < 0 && result != ERR_IGNORE && result != ERR_CONTINUE) {
       return false;
     }
   }
 
-  if (is_dir && ((event->mask & IN_DELETE) == IN_DELETE || (event->mask & IN_MOVED_FROM) == IN_MOVED_FROM)) {
+  if (is_dir && event->mask & (IN_DELETE | 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) {
@@ -363,6 +363,7 @@ static bool process_inotify_event(struct inotify_event* event) {
   if (callback != NULL) {
     (*callback)(path, event->mask);
   }
+
   return true;
 }
 
index 27372e35acc109da02ac20630fc97cb3980ede2e..e4984a80b2bfafe629001dbbd63d2e01806432b3 100644 (file)
@@ -25,6 +25,7 @@
 #include <string.h>
 #include <sys/inotify.h>
 #include <sys/select.h>
+#include <sys/stat.h>
 #include <syslog.h>
 #include <unistd.h>
 
     "The current <b>inotify</b>(7) watch limit is too low. " \
     "<a href=\"http://confluence.jetbrains.net/display/IDEADEV/Inotify+Watches+Limit\">More details.</a>\n"
 
+#define MISSING_ROOT_TIMEOUT 1
+
+#define UNFLATTEN(root) (root[0] == '|' ? root + 1 : root)
+
 typedef struct {
-  char* name;
-  int id;
+  char* path;
+  int id;  // negative value means missing root
 } watch_root;
 
 static array* roots = NULL;
@@ -76,6 +81,8 @@ static array* unwatchable_mounts();
 static void inotify_callback(char* path, int event);
 static void report_event(char* event, char* path);
 static void output(const char* format, ...);
+static void check_missing_roots();
+static void check_root_removal(char*);
 
 
 int main(int argc, char** argv) {
@@ -208,13 +215,16 @@ static void main_loop() {
   int input_fd = fileno(stdin), inotify_fd = get_inotify_fd();
   int nfds = (inotify_fd > input_fd ? inotify_fd : input_fd) + 1;
   fd_set rfds;
+  struct timeval timeout;
   bool go_on = true;
 
   while (go_on) {
     FD_ZERO(&rfds);
     FD_SET(input_fd, &rfds);
     FD_SET(inotify_fd, &rfds);
-    if (select(nfds, &rfds, NULL, NULL, NULL) < 0) {
+    timeout = (struct timeval){MISSING_ROOT_TIMEOUT, 0};
+
+    if (select(nfds, &rfds, NULL, NULL, &timeout) < 0) {
       userlog(LOG_ERR, "select: %s", strerror(errno));
       go_on = false;
     }
@@ -224,6 +234,9 @@ static void main_loop() {
     else if (FD_ISSET(inotify_fd, &rfds)) {
       go_on = process_inotify_input();
     }
+    else {
+      check_missing_roots();
+    }
   }
 }
 
@@ -311,9 +324,9 @@ static bool update_roots(array* new_roots) {
 static void unregister_roots() {
   watch_root* root;
   while ((root = array_pop(roots)) != NULL) {
-    userlog(LOG_INFO, "unregistering root: %s", root->name);
+    userlog(LOG_INFO, "unregistering root: %s", root->path);
     unwatch(root->id);
-    free(root->name);
+    free(root->path);
     free(root);
   };
 }
@@ -322,12 +335,11 @@ 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;
+    char* unflattened = UNFLATTEN(new_root);
     userlog(LOG_INFO, "registering root: %s", new_root);
 
     if (unflattened[0] != '/') {
-      userlog(LOG_WARNING, "  ... not valid, skipped");
+      userlog(LOG_WARNING, "invalid root: %s", new_root);
       continue;
     }
 
@@ -357,12 +369,12 @@ static bool register_roots(array* new_roots, array* unwatchable, array* mounts)
     int id = watch(new_root, inner_mounts);
     array_delete(inner_mounts);
 
-    if (id >= 0) {
+    if (id >= 0 || id == ERR_MISSING) {
       watch_root* root = malloc(sizeof(watch_root));
       CHECK_NULL(root, false);
       root->id = id;
-      root->name = strdup(new_root);
-      CHECK_NULL(root->name, false);
+      root->path = strdup(new_root);
+      CHECK_NULL(root->path, false);
       CHECK_NULL(array_push(roots, root), false);
     }
     else if (id == ERR_ABORT) {
@@ -407,31 +419,25 @@ static array* unwatchable_mounts() {
 
 
 static void inotify_callback(char* path, int event) {
-  if (event & IN_CREATE || event & IN_MOVED_TO) {
+  if (event & (IN_CREATE | IN_MOVED_TO)) {
     report_event("CREATE", path);
     report_event("CHANGE", path);
-    return;
   }
-
-  if (event & IN_MODIFY) {
+  else if (event & IN_MODIFY) {
     report_event("CHANGE", path);
-    return;
   }
-
-  if (event & IN_ATTRIB) {
+  else if (event & IN_ATTRIB) {
     report_event("STATS", path);
-    return;
   }
-
-  if (event & IN_DELETE || event & IN_MOVED_FROM) {
+  else if (event & (IN_DELETE | IN_MOVED_FROM)) {
     report_event("DELETE", path);
-    return;
   }
-
-  if (event & IN_UNMOUNT) {
+  if (event & (IN_DELETE_SELF | IN_MOVE_SELF)) {
+    check_root_removal(path);
+  }
+  else if (event & IN_UNMOUNT) {
     output("RESET\n");
     userlog(LOG_DEBUG, "RESET");
-    return;
   }
 }
 
@@ -466,3 +472,32 @@ static void output(const char* format, ...) {
 
   fflush(stdout);
 }
+
+
+static void check_missing_roots() {
+  struct stat st;
+  for (int i=0; i<array_size(roots); i++) {
+    watch_root* root = array_get(roots, i);
+    if (root->id < 0) {
+      char* unflattened = UNFLATTEN(root->path);
+      if (stat(unflattened, &st) == 0) {
+        root->id = watch(root->path, NULL);
+        userlog(LOG_INFO, "root restored: %s\n", root->path);
+        report_event("CREATE", unflattened);
+        report_event("CHANGE", unflattened);
+      }
+    }
+  }
+}
+
+static void check_root_removal(char* path) {
+  for (int i=0; i<array_size(roots); i++) {
+    watch_root* root = array_get(roots, i);
+    if (root->id >= 0 && strcmp(path, UNFLATTEN(root->path)) == 0) {
+      unwatch(root->id);
+      root->id = -1;
+      userlog(LOG_INFO, "root deleted: %s\n", root->path);
+      report_event("DELETE", path);
+    }
+  }
+}
index 179b3105a4229b80fcf707892ac79e27006b8b53..854a48c7da6f0dca3bd1b2b9d646f88da29dfb20 100644 (file)
@@ -108,12 +108,19 @@ void array_delete(array* a) {
 
 void array_delete_vs_data(array* a) {
   if (a != NULL) {
+    array_delete_data(a);
+    array_delete(a);
+  }
+}
+
+void array_delete_data(array* a) {
+  if (a != NULL) {
     for (int i=0; i<a->size; i++) {
       if (a->data[i] != NULL) {
         free(a->data[i]);
       }
     }
-    array_delete(a);
+    a->size = 0;
   }
 }
 
index 19580adc66f23fe84a18dcedf50f8e39c6901676..fe9bfe3c7e8a237983e8ed1b114e39e1a829cb09 100644 (file)
@@ -173,7 +173,7 @@ public class FileWatcher {
   private static boolean isUpToDate(File executable) {
     long length = SystemInfo.isWindows ? 70216 :
                   SystemInfo.isMac ? 13924 :
-                  SystemInfo.isLinux ? SystemInfo.isAMD64 ? 29227 : 22734 :
+                  SystemInfo.isLinux ? SystemInfo.isAMD64 ? 29269 : 22768 :
                   -1;
     return length < 0 || length == executable.length();
   }
index f6dfb8b39b55ff96e88e61f87aeb3126b0938c46..3d2188a0afd50a5d0e2b12615b6532047fb31ae3 100644 (file)
@@ -147,12 +147,9 @@ public class FileWatcherTest extends PlatformLangTestCase {
       FileUtil.delete(file);
       assertEvent(VFileDeleteEvent.class, file.getAbsolutePath());
 
-      if (!SystemInfo.isLinux) {
-        // todo[r.sh] fix Linux watcher
-        myAccept = true;
-        FileUtil.writeToFile(file, "re-creation");
-        assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
-      }
+      myAccept = true;
+      FileUtil.writeToFile(file, "re-creation");
+      assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
     }
     finally {
       unwatch(request);
@@ -274,12 +271,6 @@ public class FileWatcherTest extends PlatformLangTestCase {
   }
 
   public void testDirectoryNonExisting() throws Exception {
-    if (SystemInfo.isLinux) {
-      // todo[r.sh]: fix Linux watcher
-      System.err.println("Ignored: to be fixed on Linux");
-      return;
-    }
-
     File topDir = createTestDir("top");
     File subDir = new File(topDir, "subDir");
     File file = new File(subDir, "file.txt");
@@ -493,12 +484,6 @@ public class FileWatcherTest extends PlatformLangTestCase {
   }
 
   public void testWatchRootRecreation() throws Exception {
-    if (SystemInfo.isLinux) {
-      // todo[r.sh]: fix Linux watcher
-      System.err.println("Ignored: to be fixed on Linux");
-      return;
-    }
-
     File rootDir = createTestDir("root");
     File file1 = createTestFile(rootDir, "file1.txt", "abc");
     File file2 = createTestFile(rootDir, "file2.txt", "123");
@@ -509,6 +494,7 @@ public class FileWatcherTest extends PlatformLangTestCase {
       myAccept = true;
       assertTrue(FileUtil.delete(rootDir));
       assertTrue(rootDir.mkdir());
+      if (SystemInfo.isLinux) TimeoutUtil.sleep(1100);  // implementation specific
       assertTrue(file1.createNewFile());
       assertTrue(file2.createNewFile());
       assertEvent(VFileContentChangeEvent.class, file1.getPath(), file2.getPath());
@@ -520,12 +506,6 @@ public class FileWatcherTest extends PlatformLangTestCase {
   }
 
   public void testWatchRootRenameRemove() throws Exception {
-    if (SystemInfo.isLinux) {
-      // todo[r.sh]: fix Linux watcher
-      System.err.println("Ignored: to be fixed on Linux");
-      return;
-    }
-
     File topDir = createTestDir("top");
     File rootDir = createTestDir(topDir, "root");
     File rootDir2 = new File(topDir, "_" + rootDir.getName());