Linux file watcher: IDEA-87821; don't follow symlinks; cleanup

This commit is contained in:
Roman Shevchenko
2012-07-17 18:23:49 +02:00
parent 3ac9b9cec2
commit 91e3a7968d
5 changed files with 82 additions and 69 deletions
Binary file not shown.
Binary file not shown.
+1 -1
View 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();
+49 -56
View 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) {
+32 -12
View 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);
}
}