/*************************************************************************** * Copyright (C) 10/2006 by Olaf Rempel * * razzor@kopf-tisch.de * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "sys/inotify.h" #include "logging.h" #define MAX_WATCHES 8192 #define WATCH_MASK (IN_DONT_FOLLOW | IN_ONLYDIR | IN_MOVE | IN_DELETE | IN_CREATE) struct watch_entry { int parent; char *path; }; static struct watch_entry *watch_arr; /* * adds a watch * - updates watch_arr info (parent & path) * * returns watch discriptor or -1 on failure */ static int add_watch(int inotify_fd, char *path, int parent) { int wd = inotify_add_watch(inotify_fd, path, WATCH_MASK); if (wd < 0) { log_print(LOG_ERROR, "inotify_add_watch('%s')", path); return -1; } if (watch_arr[wd].path != NULL) { log_print(LOG_DEBUG, "add_watch(): removing old data: '%s' = %d", watch_arr[wd].path, wd); watch_arr[wd].parent = -1; free(watch_arr[wd].path); } watch_arr[wd].parent = parent; watch_arr[wd].path = strdup(path); log_print(LOG_DEBUG, "add_watch('%s') = %d", path, wd); return wd; } /* * removes a watch, and all childrens! * - updates watch_arr info * * returns -1 on failure */ static int remove_watch_recursive(int inotify_fd, int wd) { log_print(LOG_DEBUG, "remove_watch('%s')", watch_arr[wd].path); int i; for (i = 0; i < MAX_WATCHES; i++) if (watch_arr[i].parent == wd) remove_watch_recursive(inotify_fd, i); int ret = inotify_rm_watch(inotify_fd, wd); if (ret < 0) log_print(LOG_ERROR, "inotify_rm_watch('%s')", watch_arr[wd].path); watch_arr[wd].parent = -1; free(watch_arr[wd].path); watch_arr[wd].path = NULL; return ret; } static int add_watch_recursive_real(int inotify_fd, char *path, int parent) { struct stat statbuf; if (stat(path, &statbuf) == -1) { log_print(LOG_ERROR, "add_watch_recursive_real(): stat('%s')", path); return 0; } else if (!S_ISDIR (statbuf.st_mode)) { return 0; } int wd = add_watch(inotify_fd, path, parent); if (wd < 0) return 0; int watches = 1; DIR *dp = opendir(path); if (dp == NULL) { log_print(LOG_ERROR, "add_watch_recursive_real(): opendir('%s')", path); } else { char *end = path + strlen(path); struct dirent *dentry; while ((dentry = readdir(dp)) != NULL) { if (!strcmp(dentry->d_name, ".")) continue; if (!strcmp(dentry->d_name, "..")) continue; *end = '/'; strcpy(end +1, dentry->d_name); watches += add_watch_recursive_real(inotify_fd, path, wd); } closedir(dp); *end = '\0'; } return watches; } static char * prepare_path(int parent, char *name) { char *tmppath = malloc(PATH_MAX); if (tmppath == NULL) { log_print(LOG_ERROR, "add_recursive_watch(): out of memory"); return 0; } if (parent != -1) { char *tmp = tmppath + strlen(watch_arr[parent].path); strcpy(tmppath, watch_arr[parent].path); *tmp++ = '/'; strcpy(tmp, name); } else { strcpy(tmppath, name); } return tmppath; } static int find_child_by_name(int parent, char *name) { char *path = prepare_path(parent, name); int i; for (i = 0; i < MAX_WATCHES; i++) { if (watch_arr[i].parent == parent && !strcmp(watch_arr[i].path, path)) { free(path); return i; } } log_print(LOG_ERROR, "find_child_by_name: '%s' not found", path); free(path); return -1; } int add_watch_recursive(int fd, char *name) { char *tmppath = prepare_path(-1, name); int num = add_watch_recursive_real(fd, tmppath, -1); free(tmppath); return num; } static char * mask_to_str(int mask) { char *retval = malloc(256); *retval = '\0'; if (mask & IN_ACCESS) strcat(retval, "IN_ACCESS "); if (mask & IN_MODIFY) strcat(retval, "IN_MODIFY "); if (mask & IN_ATTRIB) strcat(retval, "IN_ATTRIB "); if (mask & IN_CLOSE_WRITE) strcat(retval, "IN_CLOSE_WRITE "); if (mask & IN_CLOSE_NOWRITE) strcat(retval, "IN_CLOSE_NOWRITE "); if (mask & IN_OPEN) strcat(retval, "IN_OPEN "); if (mask & IN_MOVED_FROM) strcat(retval, "IN_MOVED_FROM "); if (mask & IN_MOVED_TO) strcat(retval, "IN_MOVED_TO "); if (mask & IN_CREATE) strcat(retval, "IN_CREATE "); if (mask & IN_DELETE) strcat(retval, "IN_DELETE "); if (mask & IN_DELETE_SELF) strcat(retval, "IN_DELETE_SELF "); if (mask & IN_MOVE_SELF) strcat(retval, "IN_MOVE_SELF "); if (mask & IN_UNMOUNT) strcat(retval, "IN_UNMOUNT "); if (mask & IN_Q_OVERFLOW) strcat(retval, "IN_Q_OVERFLOW "); if (mask & IN_IGNORED) strcat(retval, "IN_IGNORED "); if (mask & IN_ISDIR) strcat(retval, "IN_ISDIR "); if (mask & IN_ONESHOT) strcat(retval, "IN_ONESHOT "); return retval; } int watch_callback(int fd, void *privdata) { unsigned int size; int i = ioctl(fd, FIONREAD, &size); if (i < 0) { perror ("ioctl()"); return -1; } char *buf = malloc(size); if (buf == NULL) { perror("malloc()"); return -1; } int len, j = 0; len = read(fd, buf, size); while (j < len) { struct inotify_event *event = (struct inotify_event *) &buf[j]; if (event->len) log_print(LOG_INFO, "wd=%03d mask=%08X cookie=%08X name=%s/%s %s", event->wd, event->mask, event->cookie, watch_arr[event->wd].path, event->name, mask_to_str(event->mask)); else log_print(LOG_INFO, "wd=%03d mask=%08X cookie=%08X (name=%s) %s", event->wd, event->mask, event->cookie, watch_arr[event->wd].path, mask_to_str(event->mask)); /* new directory created -> watch it */ if ((event->mask & (IN_CREATE | IN_ISDIR)) == (IN_CREATE | IN_ISDIR)) { char *tmp = prepare_path(event->wd, event->name); add_watch_recursive_real(fd, tmp, event->wd); free(tmp); } /* directory removed -> remove watch info (the watch was removed by the kernel) */ if (event->mask & IN_IGNORED) { if (watch_arr[event->wd].path != NULL) { log_print(LOG_DEBUG, "kernel removed watch ('%s')", watch_arr[event->wd].path); watch_arr[event->wd].parent = -1; free(watch_arr[event->wd].path); watch_arr[event->wd].path = NULL; } } /* directory moved away -> find child by name-serach, then remove all his children rekursive */ if ((event->mask & (IN_MOVED_FROM | IN_ISDIR)) == (IN_MOVED_FROM | IN_ISDIR)) { int wd = find_child_by_name(event->wd, event->name); remove_watch_recursive(fd, wd); } /* directory moved to us -> add watches */ if ((event->mask & (IN_MOVED_TO | IN_ISDIR)) == (IN_MOVED_TO | IN_ISDIR)) { char *tmp = prepare_path(event->wd, event->name); add_watch_recursive_real(fd, tmp, event->wd); free(tmp); } j += sizeof(struct inotify_event) + event->len; } free(buf); return 0; } int inotify_open() { int fd = inotify_init(); if (fd < 0) log_print(LOG_ERROR, "inotify_open()"); if (watch_arr == NULL) { watch_arr = malloc(MAX_WATCHES * sizeof(struct watch_entry)); if (watch_arr == NULL) { log_print(LOG_ERROR, "inotify_open(): out of memory"); return 0; } int i; for (i = 0; i < MAX_WATCHES; i++) { watch_arr[i].parent = -1; watch_arr[i].path = NULL; } } return fd; }