bsd.c

Go to the documentation of this file.
00001 /*              $Id: $          */
00002 
00003 /*
00004  * Copyright (c) 2007 Mark Heily <devel@heily.com>
00005  *
00006  * Permission to use, copy, modify, and distribute this software for any
00007  * purpose with or without fee is hereby granted, provided that the above
00008  * copyright notice and this permission notice appear in all copies.
00009  *
00010  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
00011  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
00012  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
00013  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
00014  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
00015  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
00016  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
00017  */
00018 
00025 #include "config.h"
00026 #include "pnotify.h"
00027 #include "pnotify-internal.h"
00028 
00029 
00030 #if HAVE_KQUEUE
00031 
00032 #include <fcntl.h>
00033 #include <sys/event.h>
00034 
00035 /* Forward declarations */
00036 static int kq_directory_event_handler(struct kevent kev, struct pn_watch * watch);
00037 static int directory_open(struct pn_watch * watch);
00038 static int directory_scan(struct pn_watch * watch);
00039 
00041 static int KQUEUE_FD = -1;
00042 
00043 void *
00044 bsd_kqueue_loop(void * unused)
00045 {
00046         const int nkev = 100;
00047         struct pn_watch *watch;
00048         struct kevent _kev[nkev];
00049         struct kevent *kev;
00050         struct pnotify_event *evt;
00051         int i, rc;
00052 
00053         /* Avoid a compiler warning */
00054         watch = unused;
00055 
00056         /* Create a kqueue descriptor */
00057         if ((KQUEUE_FD = kqueue()) < 0)
00058                 err(1, "kqueue(2)");
00059 
00060         /* Loop forever waiting for events */
00061 LOOP:
00062 
00063         /* Wait for an event */
00064         dprintf("waiting for kernel event..\n");
00065         rc = kevent(KQUEUE_FD, NULL, 0, (struct kevent *) &_kev, nkev, NULL);
00066         if (rc < 0) 
00067                 err(1, "kevent(2) failed");
00068 
00069         /* Process each event */
00070         for (i = 0; i < rc; i++) {
00071 
00072                 kev = &_kev[i];
00073 
00074                 /* Find the matching watch structure */
00075                 watch = (struct pn_watch *) kev->udata;
00076 
00077                 /* Workaround:
00078 
00079                    Deleting a file in a watched directory causes two events:
00080                    NOTE_MODIFY on the directory
00081                    NOTE_DELETE on the file
00082 
00083                    We ignore the NOTE_DELETE on the file.
00084                    */
00085                 if (watch->parent_wd && kev->fflags & NOTE_DELETE) {
00086                         dprintf("ignoring NOTE_DELETE on a watched file\n");
00087                         continue;
00088                 }
00089 
00090                 /* Convert the kqueue(4) flags to pnotify_event flags */
00091                 if (!watch->is_dir) {
00092 
00093                         /* Construct a pnotify_event structure */
00094                         if ((evt = calloc(1, sizeof(*evt))) == NULL) 
00095                                 err(1, "malloc failed");
00096 
00097                         if (kev->fflags & NOTE_WRITE)
00098                                 evt->mask |= PN_MODIFY;
00099                         if (kev->fflags & NOTE_TRUNCATE)
00100                                 evt->mask |= PN_MODIFY;
00101                         if (kev->flags & NOTE_EXTEND)
00102                                 evt->mask |= PN_MODIFY;
00103                         if (kev->fflags & NOTE_ATTRIB)
00104                                 evt->mask |= PN_ATTRIB;
00105                         if (kev->fflags & NOTE_DELETE)
00106                                 evt->mask |= PN_DELETE;
00107 
00108                         /* If the event happened within a watched directory,
00109                            add the filename and the parent watch descriptor.
00110                            */
00111                         if (watch->parent_wd) {
00112 
00113                                 /* KLUDGE: remove the leading basename */
00114                                 char *fn = strrchr(watch->path, '/') ;
00115                                 if (!fn) { 
00116                                         fn = watch->path;
00117                                 } else {
00118                                         fn++;
00119                                 }
00120 
00121                                 evt->watch = watch;
00122                                 /* FIXME: more error handling */
00123                                 (void) strncpy(evt->name, fn, strlen(fn));
00124                         }
00125 
00126                         /* Add the event to the list of pending events */
00127                         pn_event_add(watch->ctx, evt);
00128 
00129                         dprint_event(evt);
00130 
00131                         /* Handle events on directories */
00132                 } else {
00133 
00134                         /* When a file is added or deleted, NOTE_WRITE is set */
00135                         if (kev->fflags & NOTE_WRITE) {
00136                                 if (kq_directory_event_handler(*kev, watch) < 0) {
00137                                         warn("error processing diretory");
00138                                         return NULL;
00139                                 }
00140                         }
00141                         /* FIXME: Handle the deletion of a watched directory */
00142                         else if (kev->fflags & NOTE_DELETE) {
00143                                 warn("unimplemented - TODO");
00144                                 return NULL;
00145                         } else {
00146                                 warn("unknown event recieved");
00147                                 return NULL;
00148                         }
00149 
00150                 }
00151         }
00152 
00153         goto LOOP;
00154 
00155         close(KQUEUE_FD);
00156         return NULL;
00157 }
00158 
00159 
00160 void
00161 bsd_init_once(void)
00162 {
00163         pthread_t tid;
00164 
00165         /* Create a dedicated kqueue thread */
00166         if (pthread_create( &tid, NULL, bsd_kqueue_loop, NULL ) != 0)
00167                 errx(1, "pthread_create(3) failed");
00168 
00169         /* TODO: push cleanup function */
00170 }
00171 
00172 
00173 void
00174 bsd_cleanup(void)
00175 {
00176         (void) close(KQUEUE_FD);
00177 }
00178 
00179 
00180 #if DEAD
00181         // example from linux
00182 
00183 int
00184 bsd_add_watch(struct pnotify_ctx *ctx, struct pn_watch *watch)
00185 {
00186         struct epoll_event *ev = &watch->epoll_evt;
00187         int mask = watch->mask;
00188         uint32_t        imask = 0;
00189 
00190         switch (watch->type) {
00191 
00192                 case WATCH_FD:
00193                         /* Generate the epoll_event structure */
00194                         ev->events = EPOLLET;
00195                         if (mask & PN_READ)
00196                                 ev->events |= EPOLLIN;
00197                         if (mask & PN_WRITE)
00198                                 ev->events |= EPOLLOUT;
00199                         ev->data.ptr = watch;
00200 
00201                         /* Add the epoll_event structure to the kernel queue */
00202                         if (epoll_ctl(EPOLL_FD, EPOLL_CTL_ADD, watch->ident.fd, ev) < 0) {
00203                                 warn("epoll_ctl(2) failed");
00204                                 return -1;
00205                         }
00206                         break;
00207 
00208                 case WATCH_VNODE:
00209                         /* Generate the mask */
00210                         if (mask & PN_ATTRIB)
00211                                 imask |= IN_ATTRIB;
00212                         if (mask & PN_CREATE)
00213                                 imask |= IN_CREATE;
00214                         if (mask & PN_DELETE)
00215                                 imask |= IN_DELETE | IN_DELETE_SELF;
00216                         if (mask & PN_MODIFY)
00217                                 imask |= IN_MODIFY;
00218                         if (mask & PN_ONESHOT)
00219                                 imask |= IN_ONESHOT;
00220 
00221                         /* Add the event to the kernel event queue */
00222                         watch->wd = inotify_add_watch(INOTIFY_FD, watch->ident.path, imask);
00223                         if (watch->wd < 0) {
00224                                 perror("inotify_add_watch(2) failed");
00225                                 return -1;
00226                         }
00227                         break;
00228 
00229                 default:
00230                         /* The default action is to do nothing. */
00231                         break;
00232         }
00233 
00234         return 0;
00235 }
00236 
00237 void
00238 bsd_dump_inotify_event(struct inotify_event *iev)
00239 {
00240         static const char *nam[] = {
00241                 "IN_ACCESS", "IN_MODIFY", "IN_ATTRIB", "IN_CLOSE_WRITE",
00242                 "IN_CLOSE_NOWRITE", "IN_OPEN", "IN_MOVED_FROM",
00243                 "IN_MOVED_TO", "IN_CREATE", "IN_DELETE", "IN_DELETE_SELF",
00244                 "IN_MOVE_SELF", "IN_UNMOUNT", "IN_Q_OVERFLOW", "IN_IGNORED",
00245                 "IN_ONLYDIR", "IN_DONT_FOLLOW", "IN_MASK_ADD", "IN_ISDIR",
00246                 "IN_ONESHOT", NULL };
00247         static const int val[] = {
00248                 IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE,
00249                 IN_CLOSE_NOWRITE, IN_OPEN, IN_MOVED_FROM,
00250                 IN_MOVED_TO, IN_CREATE, IN_DELETE, IN_DELETE_SELF,
00251                 IN_MOVE_SELF, IN_UNMOUNT, IN_Q_OVERFLOW, IN_IGNORED,
00252                 IN_ONLYDIR, IN_DONT_FOLLOW, IN_MASK_ADD, IN_ISDIR,
00253                 IN_ONESHOT, 0 };
00254         int i;
00255 
00256         fprintf(stderr, "inotify event: wd=%d mask=", iev->wd);
00257         for (i = 0; val[i] != 0; i++) {
00258                 if (iev->mask & val[i])
00259                         fprintf(stderr, "%s ", nam[i]);
00260         }
00261         fprintf(stderr, "\n");
00262 }
00263 
00264 #endif /* TODO */
00265 
00266 
00267 int
00268 bsd_add_watch(struct pn_watch *watch)
00269 {
00270         struct stat     st;
00271         struct kevent *kev = &watch->kev;
00272         int mask = watch->mask;
00273 
00274         /* Open the file */
00275         if ((watch->fd = open(watch->path, O_RDONLY)) < 0) {
00276                 warn("opening path `%s' failed", watch->path);
00277                 return -1;
00278         }
00279 
00280         /* Test if the file is a directory */
00281         if (fstat(watch->fd, &st) < 0) {
00282                 warn("fstat(2) failed");
00283                 return -1;
00284         }
00285         watch->is_dir = S_ISDIR(st.st_mode);
00286 
00287         /* Initialize the directory structure, if needed */
00288         if (watch->is_dir) 
00289                 directory_open(watch);
00290 
00291         /* Create and populate a kevent structure */
00292         EV_SET(kev, watch->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, 0, 0, watch);
00293         if (mask & PN_ONESHOT)
00294                 kev->flags |= EV_ONESHOT;
00295         switch (watch->type) {
00296 
00297                 case WATCH_FD:
00298                         if (mask & PN_READ)
00299                                 kev->filter = EVFILT_READ;
00300                         if (mask & PN_WRITE)
00301                                 kev->filter = EVFILT_WRITE;
00302                         break;
00303 
00304                 case WATCH_VNODE:
00305                         kev->filter = EVFILT_VNODE;
00306                         if (mask & PN_ATTRIB)
00307                                 kev->fflags |= NOTE_ATTRIB;
00308                         if (mask & PN_CREATE)
00309                                 kev->fflags |= NOTE_WRITE;
00310                         if (mask & PN_DELETE)
00311                                 kev->fflags |= NOTE_DELETE | NOTE_WRITE;
00312                         if (mask & PN_MODIFY)
00313                                 kev->fflags |= NOTE_WRITE;
00314                 default:
00315                         break;
00316         }
00317 
00318         /* Add the kevent to the kernel event queue */
00319         if (kevent(KQUEUE_FD, kev, 1, NULL, 0, NULL) < 0) {
00320                 perror("kevent(2)");
00321                 return -1;
00322         }
00323 
00324         return 0;
00325 }
00326 
00327 
00328 int
00329 bsd_rm_watch(struct pn_watch *watch)
00330 {
00331         /* Close the file descriptor.
00332           The kernel will automatically delete the kevent 
00333           and any pending events.
00334          */
00335         if (close(watch->fd) < 0) {
00336                 perror("unable to close watch fd");
00337                 return -1;
00338         }
00339 
00340         /* Close the directory handle */
00341         if (watch->dir.dirp != NULL) {
00342                 if (closedir(watch->dir.dirp) != 0) {
00343                         perror("closedir(3)");
00344                         return -1;
00345                 }
00346         }
00347 
00348         return 0;
00349 }
00350 
00354 static int
00355 directory_open(struct pn_watch * watch)
00356 {
00357         struct directory * dir;
00358 
00359         dir = &watch->dir;
00360 
00361         /* Initialize the li_directory structure */
00362         LIST_INIT(&dir->all);
00363         if ((dir->dirp = opendir(watch->path)) == NULL) {
00364                 perror("opendir(2)");
00365                 return -1;
00366         }
00367 
00368         /* Store the pathname */
00369         dir->path_len = strlen(watch->path);
00370         if ((dir->path_len >= PATH_MAX) || 
00371                 ((dir->path = malloc(dir->path_len + 1)) == NULL)) {
00372                         perror("malloc(3)");
00373                         return -1;
00374         }
00375         strncpy(dir->path, watch->path, dir->path_len);
00376                 
00377         /* Scan the directory */
00378         if (directory_scan(watch) < 0) {
00379                 warn("directory_scan failed");
00380                 return -1;
00381         }
00382 
00383         return 0;
00384 }
00385 
00386 
00390 static int
00391 directory_scan(struct pn_watch * watch)
00392 {
00393         struct pn_watch *wtmp;
00394         struct directory *dir;
00395         struct dirent   ent, *entp;
00396         struct dentry  *dptr;
00397         bool            found;
00398         char            path[PATH_MAX + 1];
00399         char           *cp;
00400         struct stat     st;
00401 
00402         assert(watch != NULL);
00403 
00404         dir = &watch->dir;
00405 
00406         dprintf("scanning directory\n");
00407 
00408         /* Generate the basename */
00409         (void) snprintf((char *) &path, PATH_MAX, "%s/", dir->path);
00410         cp = path + dir->path_len + 1;
00411 
00412         /* 
00413          * Invalidate the status mask for all entries.
00414          *  This is used to detect entries that have been deleted.
00415          */
00416         LIST_FOREACH(dptr, &dir->all, entries) {
00417                 dptr->mask = PN_DELETE;
00418         }
00419 
00420         /* Skip the initial '.' and '..' entries */
00421         /* XXX-FIXME doesnt work with chroot(2) when '..' doesn't exist */
00422         rewinddir(dir->dirp);
00423         if (readdir_r(dir->dirp, &ent, &entp) != 0) {
00424                 perror("readdir_r(3)");
00425                 return -1;
00426         }
00427         if (strcmp(ent.d_name, ".") == 0) {
00428                 if (readdir_r(dir->dirp, &ent, &entp) != 0) {
00429                         perror("readdir_r(3)");
00430                         return -1;
00431                 }
00432         }
00433 
00434         /* Read all entries in the directory */
00435         for (;;) {
00436 
00437                 /* Read the next entry */
00438                 if (readdir_r(dir->dirp, &ent, &entp) != 0) {
00439                         perror("readdir_r(3)");
00440                         return -1;
00441                 }
00442 
00443                 /* Check for the end-of-directory condition */
00444                 if (entp == NULL)
00445                         break;
00446  
00447                 /* Perform a linear search for the dentry */
00448                 found = false;
00449                 LIST_FOREACH(dptr, &dir->all, entries) {
00450 
00451                         /*
00452                          * FIXME - BUG - this doesnt handle hardlinks which
00453                          * have the same d_fileno but different
00454                          * dirent structs
00455                          */
00456                         //should compare the entire dirent struct...
00457                         if (dptr->ent.d_fileno != ent.d_fileno) 
00458                                 continue;
00459 
00460                         dprintf("old entry: %s\n", ent.d_name);
00461                         dptr->mask = 0;
00462 
00463                         found = true;
00464                         break;
00465                 }
00466 
00467                 /* Add the entry to the list, if needed */
00468                 if (!found) {
00469                         dprintf("new entry: %s\n", ent.d_name);
00470 
00471                         /* Allocate a new dentry structure */
00472                         if ((dptr = malloc(sizeof(*dptr))) == NULL) {
00473                                 perror("malloc(3)");
00474                                 return -1;
00475                         }
00476 
00477                         /* Copy the dirent structure */
00478                         memcpy(&dptr->ent, &ent, sizeof(ent));
00479                         dptr->mask = PN_CREATE;
00480 
00481                         /* Generate the full pathname */
00482                         // BUG: name_max is not precise enough
00483                         strncpy(cp, ent.d_name, NAME_MAX);
00484 
00485                         /* Get the file status */
00486                         if (stat((char *) &path, &st) < 0) {
00487                                 warn("stat(2) of `%s' failed", (char *) &path);
00488                                 return -1;
00489                         }
00490 
00491                         /* Add a watch if it is a regular file */
00492                         if (S_ISREG(st.st_mode)) {
00493                                 int wd;
00494 
00495                                 wd = pnotify_watch_vnode(path, watch->mask, NULL, NULL);
00496                                 if (wd < 0)
00497                                         return -1;
00498                                 wtmp = pn_get_watch_by_id(wd);
00499                                 wtmp->parent_wd = watch->wd;
00500                         }
00501 
00502                         LIST_INSERT_HEAD(&dir->all, dptr, entries);
00503                 }
00504         }
00505 
00506         return 0;
00507 }
00508 
00509 
00510 
00514 static int
00515 kq_directory_event_handler(struct kevent kev, struct pn_watch * watch)
00516 {
00517         struct pnotify_ctx * ctx;
00518         struct pnotify_event *evt;
00519         struct dentry  *dptr, *dtmp;
00520 
00521         assert(watch);
00522 
00523         ctx = watch->ctx;
00524 
00525         /* Re-scan the directory to find new and deleted files */
00526         if (directory_scan(watch) < 0) {
00527                 warn("directory_scan failed");
00528                 return -1;
00529         }
00530 
00531         /* Generate an event for each changed directory entry */
00532         LIST_FOREACH_SAFE(dptr, &watch->dir.all, entries, dtmp) {
00533 
00534                 /* Skip files that have not changed */
00535                 if (dptr->mask == 0)
00536                         continue;
00537 
00538                 /* Construct a pnotify_event structure */
00539                 if ((evt = calloc(1, sizeof(*evt))) == NULL) {
00540                         warn("malloc failed");
00541                         return -1;
00542                 }
00543                 evt->watch = watch;
00544                 evt->mask = dptr->mask;
00545                 (void) strlcpy(evt->name, dptr->ent.d_name, sizeof(evt->name));
00546                 dprint_event(ev);
00547 
00548                 /* Add the event to the list of pending events */
00549                 pn_event_add(watch->ctx, evt);
00550 
00551                 /* Remove the directory entry for a deleted file */
00552                 if (dptr->mask & PN_DELETE) {
00553                         LIST_REMOVE(dptr, entries);
00554                         free(dptr);
00555                 }
00556         }
00557 
00558         return 0;
00559 }
00560 
00561 
00562 const struct pnotify_vtable BSD_VTABLE = {
00563         .init_once = bsd_init_once,
00564         .add_watch = bsd_add_watch,
00565         .rm_watch = bsd_rm_watch,
00566         .cleanup = bsd_cleanup,
00567 };
00568 
00569 #endif

Generated on Wed Aug 22 23:15:42 2007 for pnotify by  doxygen 1.5.1