/* * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * "Portions Copyright (c) 2003 Apple Computer, Inc. All Rights * Reserved. This file contains Original Code and/or Modifications of * Original Code as defined in and that are subject to the Apple Public * Source License Version 1.0 (the 'License'). You may not use this file * except in compliance with the License. Please obtain a copy of the * License at http://www.apple.com/publicsource and read it before using * this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License." * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include "file_watcher.h" #include "daemon.h" #define IDENT_INVAL ((uintptr_t)-1) #define FS_CHECKED 0x01 #define FS_INITIAL 0x02 #define FS_REMOVED 0x04 #define FS_DEADLINK 0x08 void file_watcher_printf(watcher_t *w, FILE *fp) { file_watcher_t *f; w_event_t *e; if (w == NULL) return; if (w->type != WATCH_FILE) return; f = (file_watcher_t *)w->sub; if (f == NULL) return; fprintf(fp, "File Watcher 0x%08x\n", (uint32_t)f); if (f->ftype == FS_TYPE_FILE) fprintf(fp, "File: "); else if (f->ftype == FS_TYPE_DIR) fprintf(fp, "Dir: "); else if (f->ftype == FS_TYPE_LINK) fprintf(fp, "Link: "); else fprintf(fp, "Path: "); fprintf(fp, "%s\n", (f->path == NULL) ? "-nil-" : f->path); if (f->sb != NULL) { /* XXX print stat buf */ } if (f->kqident != IDENT_INVAL) fprintf(fp, "KQ Ident: %u\n", (uint32_t)f->kqident); e = f->contents; if (e != NULL) { fprintf(fp, "Contents:\n"); for (; e != NULL; e = e->next) { fprintf(fp, "\t%s %u %u\n", e->name, e->type, e->flags); } } if (f->parent != NULL) fprintf(fp, "Parent: %u 0x%08x\n", f->parent->wid, (uint32_t)f->parent); if (f->linktarget != NULL) fprintf(fp, "Link Target: %u 0x%08x\n", f->linktarget->wid, (uint32_t)f->linktarget); } static void file_watcher_add_kq(file_watcher_t *f) { kern_return_t status; struct kevent event; if (f == NULL) return; if (f->kqident != IDENT_INVAL) return; #ifdef DEBUG log_message(LOG_ERR, "file_watcher_add_kq(%u, %s)", f->w->wid, f->path); #endif event.ident = open(f->path, O_EVTONLY, 0); if (event.ident < 0) return; event.filter = EVFILT_VNODE; event.flags = EV_ADD | EV_CLEAR; event.fflags = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME; event.data = NULL; event.udata = (void *)f->w->wid; f->kqident = event.ident; status = kevent(kq, &event, 1, NULL, 0, NULL); if (status != 0) { close(event.ident); return; } } static void file_watcher_remove_kq(file_watcher_t *f) { struct kevent event; kern_return_t status; if (f == NULL) return; if (f->kqident == IDENT_INVAL) return; #ifdef DEBUG log_message(LOG_ERR, "file_watcher_remove_kq(%u, %s)", f->w->wid, f->path); #endif event.ident = f->kqident; event.filter = EVFILT_VNODE; event.flags = EV_DELETE; event.fflags = 0; event.data = NULL; event.udata = NULL; status = kevent(kq, &event, 1, NULL, 0, NULL); if (status != 0) { log_message(LOG_ERR, "EV_DELETE failed for file watcher %u", f->w->wid); } close(f->kqident); f->kqident = IDENT_INVAL; } static void dir_contents_update(file_watcher_t *f, w_event_t *e) { w_event_t *p, *x; int comp; if (f == NULL) return; if (e == NULL) return; if (e->name == NULL) return; if (e->type == EVENT_FILE_ADD) { x = w_event_new(e->name, e->type, 0); if (f->contents == NULL) { f->contents = x; return; } p = f->contents; comp = strcmp(p->name, x->name); if (comp == 0) return; if (comp > 0) { x->next = f->contents; f->contents = x; return; } while (p->next != NULL) { comp = strcmp(p->next->name, x->name); if (comp == 0) return; if (comp > 0) { x->next = p->next; p->next = x; return; } p = p->next; } p->next = x; return; } if (e->type == EVENT_FILE_DELETE) { if (f->contents == NULL) return; p = f->contents; if (strcmp(p->name, e->name) == 0) { f->contents = p->next; w_event_release(p); return; } while (p->next != NULL) { if (strcmp(p->next->name, e->name) == 0) { x = p->next; p->next = x->next; w_event_release(x); return; } p = p->next; } return; } } static int32_t file_watcher_check_file(file_watcher_t *f, struct stat *oldsb, w_event_t **delta) { w_event_t *e; int32_t achange, cchange, mchange, status; achange = 0; cchange = 0; mchange = 0; status = 0; if ((oldsb->st_atimespec.tv_sec != f->sb->st_atimespec.tv_sec) || (oldsb->st_atimespec.tv_nsec != f->sb->st_atimespec.tv_nsec)) achange = 1; if ((oldsb->st_ctimespec.tv_sec != f->sb->st_ctimespec.tv_sec) || (oldsb->st_ctimespec.tv_nsec != f->sb->st_ctimespec.tv_nsec)) cchange = 1; if ((oldsb->st_mtimespec.tv_sec != f->sb->st_mtimespec.tv_sec) || (oldsb->st_mtimespec.tv_nsec != f->sb->st_mtimespec.tv_nsec)) mchange = 1; if ((cchange != 0) || (mchange != 0)) status = 1; if ((f->ftype == FS_TYPE_FILE) || (f->ftype == FS_TYPE_LINK)) { if (cchange != 0) { e = w_event_new(f->path, EVENT_FILE_MOD_DATA, 0); e->next = *delta; *delta = e; } } if (oldsb->st_uid != f->sb->st_uid) { e = w_event_new(f->path, EVENT_FILE_MOD_UID, 0); e->next = *delta; *delta = e; } if (oldsb->st_gid != f->sb->st_gid) { e = w_event_new(f->path, EVENT_FILE_MOD_GID, 0); e->next = *delta; *delta = e; } if ((oldsb->st_mode & 07000) != (f->sb->st_mode & 07000)) { e = w_event_new(f->path, EVENT_FILE_MOD_STICKY, 0); e->next = *delta; *delta = e; } if ((oldsb->st_mode & 0777) != (f->sb->st_mode & 0777)) { e = w_event_new(f->path, EVENT_FILE_MOD_ACCESS, 0); e->next = *delta; *delta = e; } return status; } static uint32_t file_watcher_update_dir(file_watcher_t *f, w_event_t **delta) { DIR *dp; struct dirent *dent; w_event_t *e, *x; uint32_t do_notify; #ifdef DEBUG log_message(LOG_ERR, "file_watcher_update_dir(%u, %s)", f->w->wid, f->path); #endif dp = opendir(f->path); if (dp == NULL) return 0; do_notify = 0; for (dent = readdir(dp); dent != NULL; dent = readdir(dp)) { if (!strcmp(dent->d_name, ".")) continue; if (!strcmp(dent->d_name, "..")) continue; e = w_event_find(f->contents, dent->d_name); if (e == NULL) { do_notify = 1; e = w_event_new(dent->d_name, EVENT_FILE_ADD, 0); e->next = *delta; *delta = e; } else { e->flags |= FS_CHECKED; } } closedir(dp); /* * Walk through contents and see if all files have been checked. * Unchecked files have been deleted. */ for (e = f->contents; e != NULL; e = e->next) { if ((e->flags & FS_CHECKED) == 0) { do_notify = 1; x = w_event_new(e->name, EVENT_FILE_DELETE, 0); x->next = *delta; *delta = x; } else { /* Clear flags */ e->flags &= ~FS_CHECKED; } } /* update contents */ for (e = *delta; e != NULL; e = e->next) { dir_contents_update(f, e); } return do_notify; } static void file_append_history(file_watcher_t *f, w_event_t *delta) { uint32_t tailref; w_event_t *e, *x, *l; if (f == NULL) return; if (delta == NULL) return; /* tail's refcount tells us how many clients are watching */ tailref = f->tail->refcount - 1; if (tailref == 0) { /* nobody is watching - release the events */ x = NULL; for (e = delta; e != NULL; e = x) { x = e->next; w_event_release(e); } return; } /* swap data from head of delta and current tail */ f->tail->name = strdup(delta->name); f->tail->type = delta->type; f->tail->flags = delta->flags; f->tail->private = delta->private; f->tail->next = delta->next; f->tail->refcount = tailref; l = f->tail; e = delta->next; delta->next = NULL; w_event_release(delta); delta = e; /* * find end of list * set refcounts along the way */ for (e = delta; e != NULL; e = e->next) { l = e; e->refcount = tailref; } /* add a new tail */ x = w_event_new(NULL, EVENT_NULL, 0); x->refcount = tailref + 1; l->next = x; f->tail = x; } static uint32_t file_watcher_remove(file_watcher_t *f) { w_event_t *e, *x, *delta; char *pdir, *p; #ifdef DEBUG log_message(LOG_ERR, "file_watcher_remove(%u, %s)", f->w->wid, f->path); #endif if (f == NULL) return 0; if (f->flags == FS_REMOVED) return 0; f->flags = FS_REMOVED; if (f->parent == NULL) { pdir = strdup(f->path); p = strrchr(pdir, '/'); if (p == NULL) { /* this can't happen! */ } else if (p == pdir) { /* hit root */ p++; *p = '\0'; } else { *p = '\0'; } f->parent = file_watcher_new(pdir); if (pdir != NULL) free(pdir); } watcher_add_forward(f->parent, f->w->wid); delta = NULL; x = w_event_new(f->path, EVENT_FILE_DELETE, 0); /* If f is a directory, add events to remove all files */ if (f->ftype == FS_TYPE_DIR) { for (e = f->contents; e != NULL; e = e->next) { e->type = EVENT_FILE_DELETE; if (e->next == NULL) { e->next = x; x = NULL; } } delta = f->contents; f->contents = NULL; } else { delta = x; } file_append_history(f, delta); /* If f is a link, remove linktarget watcher */ if (f->ftype == FS_TYPE_LINK) { watcher_release_deferred(f->linktarget); f->linktarget = NULL; if (f->targetsb != NULL) free(f->targetsb); f->targetsb = NULL; } /* remove from kqueue */ if ((f->ftype == FS_TYPE_FILE) || (f->ftype == FS_TYPE_DIR)) { file_watcher_remove_kq(f); } #ifdef DEBUG log_message(LOG_ERR, "file_watcher_remove %s - removed", f->path); #endif f->ftype = FS_TYPE_NONE; return 1; } static uint32_t file_watcher_update_link(file_watcher_t *f, w_event_t **delta) { char *pdir, *p; char lpath[MAXPATHLEN + 1]; int n; w_event_t *e; struct stat sb; file_watcher_t *t; #ifdef DEBUG log_message(LOG_ERR, "file_watcher_update_link(%u, %s)", f->w->wid, f->path); #endif n = readlink(f->path, lpath, MAXPATHLEN + 1); if (n <= 0) { /* link doesn't exist! */ file_watcher_remove(f); return -1; } lpath[n] = '\0'; /* If lpath is is not absolute, it is relative to f->path parent dir. Fix it! */ if (lpath[0] != '/') { pdir = strdup(f->path); p = strrchr(pdir, '/'); if (p == pdir) { /* parent is root dir */ p = NULL; asprintf(&p, "/%s", lpath); } else { *p = '\0'; p = NULL; asprintf(&p, "%s/%s", pdir, lpath); } memcpy(lpath, p, strlen(p) + 1); free(pdir); free(p); } if (f->linktarget != NULL) { t = (file_watcher_t *)f->linktarget->sub; if (t == NULL) return 0; /* can't happen */ if (strcmp(lpath, t->path)) { /* Link changed */ watcher_release_deferred(f->linktarget); f->linktarget = file_watcher_new(lpath); f->targetsb = malloc(sizeof(struct stat)); memcpy(f->targetsb, ((file_watcher_t *)(f->linktarget->sub))->sb, sizeof(struct stat)); watcher_add_forward(f->linktarget, f->w->wid); return 1; } memset(&sb, 0, sizeof(struct stat)); n = stat(f->path, &sb); if (n < 0) { if (f->flags == FS_DEADLINK) return 0; f->flags = FS_DEADLINK; e = w_event_new(t->path, EVENT_FILE_DELETE, 0); e->next = *delta; *delta = e; } else if (f->flags == FS_DEADLINK) { f->flags = 0; e = w_event_new(t->path, EVENT_FILE_ADD, 0); e->next = *delta; *delta = e; } else if (f->targetsb != NULL) { file_watcher_check_file(t, f->targetsb, delta); free(f->targetsb); f->targetsb = malloc(sizeof(struct stat)); memcpy(f->targetsb, t->sb, sizeof(struct stat)); } } if ((f->parent != NULL) && (f->linktarget != NULL)) return 0; pdir = strdup(f->path); p = strrchr(pdir, '/'); if (p == NULL) { } else if (p == pdir) { p++; *p = '\0'; } else { *p = '\0'; } if (f->parent == NULL) { f->parent = file_watcher_new(pdir); watcher_add_forward(f->parent, f->w->wid); } if (f->linktarget == NULL) { p = NULL; if (lpath[0] != '/') { if (!strcmp(pdir, "/")) asprintf(&p, "/%s", lpath); else asprintf(&p, "%s/%s", pdir, lpath); } else asprintf(&p, "%s", lpath); f->linktarget = file_watcher_new(p); f->targetsb = malloc(sizeof(struct stat)); memcpy(f->targetsb, ((file_watcher_t *)(f->linktarget->sub))->sb, sizeof(struct stat)); if (p != NULL) free(p); watcher_add_forward(f->linktarget, f->w->wid); } if (pdir != NULL) free(pdir); return 0; } w_event_t * file_watcher_history(watcher_t *w) { file_watcher_t *f; w_event_t *n, *e, *l, *p; if (w == NULL) return NULL; if (w->type != WATCH_FILE) return NULL; f = (file_watcher_t *)w->sub; if (f == NULL) return NULL; l = NULL; p = NULL; /* * start with a list of the current directory contents */ for (n = f->contents; n != NULL; n = n->next) { e = w_event_new(n->name, n->type, 0); if (l == NULL) l = e; else p->next = e; p = e; } e = f->tail; e->refcount++; if (l == NULL) l = e; else p->next = e; return l; } static int32_t file_watcher_update(file_watcher_t *f, uint32_t flags, uint32_t level) { struct stat *oldsb; w_event_t *e, *delta; int32_t status, i, do_notify, did_remove; #ifdef DEBUG log_message(LOG_ERR, "file_watcher_update(%u, 0x%08x, %s)", f->w->wid, flags, f->path); #endif if (f == NULL) return 0; did_remove = 0; if (flags & NOTE_DELETE) { file_watcher_remove(f); did_remove = 1; } oldsb = f->sb; f->sb = (struct stat *)calloc(1, sizeof(struct stat)); status = -1; if (f->ftype == FS_TYPE_NONE) { /* new or previously removed file watcher */ status = lstat(f->path, f->sb); if (status != 0) f->ftype = FS_TYPE_NONE; else if ((f->sb->st_mode & S_IFMT) == S_IFDIR) f->ftype = FS_TYPE_DIR; else if ((f->sb->st_mode & S_IFMT) == S_IFREG) f->ftype = FS_TYPE_FILE; else if ((f->sb->st_mode & S_IFMT) == S_IFLNK) f->ftype = FS_TYPE_LINK; } else if (f->ftype == FS_TYPE_LINK) { status = lstat(f->path, f->sb); } else { status = stat(f->path, f->sb); } if (status < 0) { free(oldsb); if (did_remove == 0) status = file_watcher_remove(f); return 1; } f->w->state = f->ftype; if (f->w->name != NULL) { for (i = 0; f->w->name[i] != NULL; i++) daemon_set_state(f->w->name[i], f->w->state); } if ((f->flags == FS_REMOVED) || (f->flags == FS_INITIAL)) { if ((f->ftype == FS_TYPE_FILE) || (f->ftype == FS_TYPE_DIR)) { file_watcher_add_kq(f); } } if ((f->flags == FS_REMOVED) && (f->parent != NULL)) { watcher_remove_forward(f->parent, f->w->wid); watcher_release_deferred(f->parent); f->parent = NULL; } delta = NULL; status = 1; /* Check for file modified or attrs modified */ if ((f->flags != FS_REMOVED) && (f->flags != FS_INITIAL)) { status = file_watcher_check_file(f, oldsb, &delta); } free(oldsb); /* if file_watcher_check_file determined there's no work to do, bail out now */ if (status == 0) return 0; do_notify = 0; if (f->ftype == FS_TYPE_DIR) do_notify = file_watcher_update_dir(f, &delta); else if (f->ftype == FS_TYPE_LINK) do_notify = file_watcher_update_link(f, &delta); if ((do_notify >= 0) && (f->flags == FS_REMOVED)) { e = w_event_new(f->path, EVENT_FILE_ADD, 0); e->next = delta; delta = e; f->flags = 0; } if (f->flags == FS_INITIAL) f->flags = 0; if ((do_notify < 0) || (delta != NULL)) do_notify = 1; /* append delta to history */ file_append_history(f, delta); return do_notify; } watcher_t * file_watcher_for_path(const char *p) { watcher_t *w; file_watcher_t *f; list_t *n; if (p == NULL) return NULL; for (n = watch_list; n != NULL; n = _nc_list_next(n)) { w = _nc_list_data(n); if (w == NULL) continue; if (w->type != WATCH_FILE) continue; f = (file_watcher_t *)w->sub; if (f == NULL) continue; if (f->path == NULL) continue; if (strcmp(p, f->path) == 0) return watcher_retain(w); } return NULL; } watcher_t * file_watcher_new(const char *p) { watcher_t *w; file_watcher_t *f; if (p == NULL) return NULL; w = file_watcher_for_path(p); if (w != NULL) { return w; } w = watcher_new(); if (w == NULL) return NULL; #ifdef DEBUG log_message(LOG_ERR, "file_watcher_new(%u, %s)", w->wid, p); #endif f = (file_watcher_t *)calloc(1, sizeof(file_watcher_t)); if (f == NULL) { watcher_release(w); return NULL; } f->sb = (struct stat *)calloc(1, sizeof(struct stat)); if (f->sb == NULL) { watcher_release(w); free(f); return NULL; } f->tail = w_event_new(NULL, EVENT_NULL, 0); f->kqident = IDENT_INVAL; w->type = WATCH_FILE; w->sub = f; w->sub_trigger = file_watcher_trigger; w->sub_free = file_watcher_free; w->sub_printf = file_watcher_printf; f->w = w; f->path = strdup(p); f->flags = FS_INITIAL; file_watcher_update(f, 0, 0); #ifdef DEBUG log_message(LOG_ERR, "file_watcher_new: %s type %u trigger %u", p, f->ftype, w->wid); #endif return w; } int32_t file_watcher_trigger(watcher_t *w, uint32_t flags, uint32_t level) { file_watcher_t *f; if (w == NULL) return 0; if (w->sub == NULL) return 0; f = (file_watcher_t *)w->sub; #ifdef DEBUG log_message(LOG_ERR, "file_watcher_trigger: %s type %u wid %u flags 0x%08x", f->path, f->ftype, w->wid, flags); #endif return file_watcher_update(f, flags, level); } void file_watcher_free(watcher_t *w) { file_watcher_t *f; w_event_t *e, *n; if (w == NULL) return; if (w->sub == NULL) return; f = (file_watcher_t *)w->sub; #ifdef DEBUG log_message(LOG_ERR, "file_watcher_free(%u, %s)", f->w->wid, f->path); #endif if (f->path != NULL) free(f->path); if (f->sb != NULL) free(f->sb); if (f->targetsb != NULL) free(f->targetsb); if ((f->ftype == FS_TYPE_FILE) || (f->ftype == FS_TYPE_DIR)) { file_watcher_remove_kq(f); } if (f->parent != NULL) watcher_release(f->parent); if (f->linktarget != NULL) watcher_release(f->linktarget); n = NULL; for (e = f->contents; e != NULL; e = n) { n = e->next; w_event_release(e); } w_event_release(f->tail); free(f); }