Linux file watcher: poll for missing roots

This commit is contained in:
Roman Shevchenko
2013-03-05 18:03:49 +01:00
parent 6990a4e204
commit aad75df4a7
8 changed files with 81 additions and 56 deletions

Binary file not shown.

Binary file not shown.

View 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();

View 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;
}

View 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>
@@ -56,9 +57,13 @@
"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);
}
}
}

View File

@@ -107,13 +107,20 @@ 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;
}
}

View 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();
}

View 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());