/* dircond - directory content watcher daemon Copyright (C) 2012, 2013 Sergey Poznyakoff Dircond 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 3 of the License, or (at your option) any later version. Dircond 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 dircond. If not, see . */ #include #include #include #include #include #include #include #include "dircond.h" void dirwatcher_unref(struct dirwatcher *dw) { if (--dw->refcnt) return; free(dw->dirname); free(dw); } struct dwref { int used; struct dirwatcher *dw; }; static unsigned dwname_hash(void *data, unsigned long hashsize) { struct dwref *sym = data; return hash_string(sym->dw->dirname, hashsize); } static int dwname_cmp(const void *a, const void *b) { struct dwref const *syma = a; struct dwref const *symb = b; return strcmp(syma->dw->dirname, symb->dw->dirname); } static int dwname_copy(void *a, void *b) { struct dwref *syma = a; struct dwref *symb = b; syma->used = 1; syma->dw = symb->dw; return 0; } static void dwref_free(void *p) { struct dwref *dwref = p; dirwatcher_unref(dwref->dw); free(dwref); } struct hashtab *texttab; struct dirwatcher * dirwatcher_install(const char *path, int *pnew) { struct dirwatcher *dw, dwkey; struct dwref key; struct dwref *ent; int install = 1; if (!texttab) { texttab = hashtab_create(sizeof(struct dwref), dwname_hash, dwname_cmp, dwname_copy, NULL, dwref_free); if (!texttab) { diag(LOG_CRIT, "not enough memory"); exit(1); } } dwkey.dirname = (char*) path; key.dw = &dwkey; ent = hashtab_lookup_or_install(texttab, &key, &install); if (install) { dw = ecalloc(1, sizeof(*dw)); dw->dirname = estrdup(path); dw->refcnt++; ent->dw = dw; } if (!ent) abort(); /* FIXME */ if (pnew) *pnew = install; return ent->dw; } static struct dirwatcher * dirwatcher_lookup(const char *dirname) { struct dirwatcher dwkey; struct dwref key; struct dwref *ent; if (!texttab) return NULL; dwkey.dirname = (char*) dirname; key.dw = &dwkey; ent = hashtab_lookup_or_install(texttab, &key, NULL); return ent ? ent->dw : NULL; } static void dirwatcher_remove(const char *dirname) { struct dirwatcher dwkey; struct dwref key; if (!texttab) return; dwkey.dirname = (char*) dirname; key.dw = &dwkey; hashtab_remove(texttab, &key); } struct hashtab *watchtab; static unsigned dw_hash(void *data, unsigned long hashsize) { struct dwref *ent = data; return ent->dw->wd % hashsize; } static int dw_cmp(const void *a, const void *b) { struct dwref const *ha = a; struct dwref const *hb = b; return ha->dw->wd != hb->dw->wd; } static int dw_copy(void *a, void *b) { struct dwref *ha = a; struct dwref *hb = b; ha->used = 1; ha->dw = hb->dw; ha->dw->refcnt++; return 0; } struct hashtab *dwtab; void dirwatcher_register(struct dirwatcher *dw) { struct dwref key; struct dwref *ent; int install = 1; if (!dwtab) { dwtab = hashtab_create(sizeof(struct dwref), dw_hash, dw_cmp, dw_copy, NULL, dwref_free); if (!dwtab) { diag(LOG_ERR, "not enough memory"); exit(1); } } memset(&key, 0, sizeof(key)); key.dw = dw; ent = hashtab_lookup_or_install(dwtab, &key, &install); if (!ent) { diag(LOG_ERR, "not enough memory"); exit(1); } } struct dirwatcher * dirwatcher_lookup_wd(int wd) { struct dirwatcher dwkey; struct dwref dwref, *ent; if (!dwtab) return NULL; dwkey.wd = wd; dwref.dw = &dwkey; ent = hashtab_lookup_or_install(dwtab, &dwref, NULL); return ent ? ent->dw : NULL; } void dirwatcher_remove_wd(int wd) { struct dirwatcher dwkey; struct dwref dwref; if (!dwtab) return; dwkey.wd = wd; dwref.dw = &dwkey; hashtab_remove(dwtab, &dwref); } int dirwatcher_init(struct dirwatcher *dwp) { int mask = 0; struct handler *hp; int wd; debug(1, ("creating watcher %s", dwp->dirname)); for (hp = dwp->handler_list; hp; hp = hp->next) mask |= hp->ev_mask; wd = inotify_add_watch(ifd, dwp->dirname, mask); if (wd == -1) { diag(LOG_ERR, "cannot set watch on %s: %s", dwp->dirname, strerror(errno)); return 1; } dwp->wd = wd; dirwatcher_register(dwp); return 0; } static struct dirwatcher * subwatcher_create(struct dirwatcher *parent, const char *dirname) { struct dirwatcher *dwp; int inst; dwp = dirwatcher_install(dirname, &inst); if (!inst) return 0; dwp->handler_list = parent->handler_list; dwp->parent = parent; if (parent->depth == -1) dwp->depth = parent->depth; else if (parent->depth) dwp->depth = parent->depth - 1; else dwp->depth = 0; if (dirwatcher_init(dwp)) { //FIXME dirwatcher_free(dwp); return NULL; } return dwp; } /* Check if a new watcher must be created and create it if so. A watcher must be created if its parent's autowatch has a non-null value. If it has a negative value, it will be inherited by the new watcher. Otherwise, the new watcher will inherit the parent's autowatch decreased by one. Return 0 on success, -1 on error. */ int check_new_watcher(const char *dir, const char *name) { int rc; char *fname; struct stat st; struct dirwatcher *parent; parent = dirwatcher_lookup(dir); if (!parent || !parent->depth) return 0; fname = mkfilename(dir, name); if (!fname) { diag(LOG_ERR, "cannot create watcher %s/%s: not enough memory", dir, name); return -1; } if (stat(fname, &st)) { diag(LOG_ERR, "cannot create watcher %s/%s, stat failed: %s", dir, name, strerror(errno)); rc = -1; } else if (S_ISDIR(st.st_mode)) rc = subwatcher_create(parent, fname) ? 0 : -1; else rc = 0; free(fname); return rc; } /* Recursively scan subdirectories of parent and add them to the wather list, as requested by the parent's autowatch value. */ static void watch_subdirs(struct dirwatcher *parent) { DIR *dir; struct dirent *ent; if (parent->depth == 0) return; dir = opendir(parent->dirname); if (!dir) { diag(LOG_ERR, "cannot open directory %s: %s", parent->dirname, strerror(errno)); return; } while (ent = readdir(dir)) { struct stat st; char *dirname; if (ent->d_name[0] == '.' && (ent->d_name[1] == 0 || (ent->d_name[1] == '.' && ent->d_name[2] == 0))) continue; dirname = mkfilename(parent->dirname, ent->d_name); if (!dirname) { diag(LOG_ERR, "cannot stat %s/%s: not enough memory", parent->dirname, ent->d_name); continue; } if (stat(dirname, &st)) { diag(LOG_ERR, "cannot stat %s: %s", dirname, strerror(errno)); } else if (S_ISDIR(st.st_mode)) { struct dirwatcher *dwp = subwatcher_create(parent, dirname); if (dwp) watch_subdirs(dwp); } free(dirname); } closedir(dir); } int setwatcher(struct hashent *ent, void *null) { struct dwref *dwref = (struct dwref *) ent; struct dirwatcher *dwp = dwref->dw; if (dirwatcher_init(dwp) == 0) watch_subdirs(dwp); return 0; } void setup_watchers() { if (hashtab_count(texttab) == 0) { diag(LOG_CRIT, "no event handlers configured"); exit(1); } hashtab_foreach(texttab, setwatcher, NULL); if (hashtab_count(dwtab) == 0) { diag(LOG_CRIT, "no event handlers installed"); exit(2); } } void dirwatcher_destroy(struct dirwatcher *dwp) { debug(1, ("removing watcher %s", dwp->dirname)); inotify_rm_watch(ifd, dwp->wd); dirwatcher_remove_wd(dwp->wd); dirwatcher_remove(dwp->dirname); } /* Remove a watcher identified by its directory and file name */ void remove_watcher(const char *dir, const char *name) { struct dirwatcher *dwp; char *fullname = mkfilename(dir, name); if (!fullname) { diag(LOG_EMERG, "not enough memory: " "cannot look up a watcher to delete"); return; } dwp = dirwatcher_lookup(fullname); free(fullname); if (dwp) dirwatcher_destroy(dwp); }