static int watches_clear(uint32_t wd, char *path) { struct watch_enum we; uint32_t rm_wd; int ret; inotify_rm_watch(inofd, wd); db_watch_delete_bywd(wd); memset(&we, 0, sizeof(struct watch_enum)); we.match = path; ret = db_watch_enum_start(&we); if (ret < 0) return -1; while ((db_watch_enum_fetchwd(&we, &rm_wd) == 0) && (rm_wd)) { inotify_rm_watch(inofd, rm_wd); } db_watch_enum_end(&we); db_watch_delete_bymatch(path); return 0; }
/* Thread: scan */ static void kqueue_cb(int fd, short event, void *arg) { struct kevent kev; struct timespec ts; struct watch_info wi; struct watch_enum we; struct stacked_dir *rescan; struct stacked_dir *d; struct stacked_dir *dprev; char *path; uint32_t wd; int d_len; int w_len; int need_rescan; int ret; ts.tv_sec = 0; ts.tv_nsec = 0; we.cookie = 0; rescan = NULL; DPRINTF(E_DBG, L_SCAN, "Library changed!\n"); /* We can only monitor directories with kqueue; to monitor files, we'd need * to have an open fd on every file in the library, which is totally insane. * Unfortunately, that means we only know when directories get renamed, * deleted or changed. We don't get directory/file names when directories/files * are created/deleted/renamed in the directory, so we have to rescan. */ while (kevent(fd, NULL, 0, &kev, 1, &ts) > 0) { /* This should not happen, and if it does, we'll end up in * an infinite loop. */ if (kev.filter != EVFILT_VNODE) continue; wi.wd = kev.ident; ret = db_watch_get_bywd(&wi); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Found no matching watch for kevent, killing this event\n"); close(kev.ident); continue; } /* Whatever the type of event that happened, disable matching watches and * files before we trigger an eventual rescan. */ we.match = wi.path; ret = db_watch_enum_start(&we); if (ret < 0) { free(wi.path); continue; } while ((db_watch_enum_fetchwd(&we, &wd) == 0) && (wd)) { close(wd); } db_watch_enum_end(&we); db_watch_delete_bymatch(wi.path); close(wi.wd); db_watch_delete_bywd(wi.wd); /* Disable files */ db_file_disable_bymatch(wi.path, "", 0); db_pl_disable_bymatch(wi.path, "", 0); if (kev.flags & EV_ERROR) { DPRINTF(E_LOG, L_SCAN, "kevent reports EV_ERROR (%s): %s\n", wi.path, strerror(kev.data)); ret = access(wi.path, F_OK); if (ret != 0) { free(wi.path); continue; } /* The directory still exists, so try to add it back to the library */ kev.fflags |= NOTE_WRITE; } /* No further action on NOTE_DELETE & NOTE_RENAME; NOTE_WRITE on the * parent directory will trigger a rescan in both cases and the * renamed directory will be picked up then. */ if (kev.fflags & NOTE_WRITE) { DPRINTF(E_DBG, L_SCAN, "Got NOTE_WRITE (%s)\n", wi.path); need_rescan = 1; w_len = strlen(wi.path); /* Abusing stacked_dir a little bit here */ dprev = NULL; d = rescan; while (d) { d_len = strlen(d->path); if (d_len > w_len) { /* Stacked dir child of watch dir? */ if ((d->path[w_len] == '/') && (strncmp(d->path, wi.path, w_len) == 0)) { DPRINTF(E_DBG, L_SCAN, "Watched directory is a parent\n"); if (dprev) dprev->next = d->next; else rescan = d->next; free(d->path); free(d); if (dprev) d = dprev->next; else d = rescan; continue; } } else if (w_len > d_len) { /* Watch dir child of stacked dir? */ if ((wi.path[d_len] == '/') && (strncmp(wi.path, d->path, d_len) == 0)) { DPRINTF(E_DBG, L_SCAN, "Watched directory is a child\n"); need_rescan = 0; break; } } else if (strcmp(wi.path, d->path) == 0) { DPRINTF(E_DBG, L_SCAN, "Watched directory already listed\n"); need_rescan = 0; break; } dprev = d; d = d->next; } if (need_rescan) push_dir(&rescan, wi.path); } free(wi.path); } while ((path = pop_dir(&rescan))) { process_directories(path, 0); free(path); if (rescan) DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n"); } event_add(&inoev, NULL); }
/* Thread: scan */ static void process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie) { struct watch_enum we; uint32_t rm_wd; int flags = 0; int ret; DPRINTF(E_DBG, L_SCAN, "Directory event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); if (ie->mask & IN_UNMOUNT) { db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); } if (ie->mask & IN_MOVE_SELF) { /* A directory we know about, that got moved from a place * we know about to a place we know nothing about */ if (wi->cookie) { memset(&we, 0, sizeof(struct watch_enum)); we.cookie = wi->cookie; ret = db_watch_enum_start(&we); if (ret < 0) return; while ((db_watch_enum_fetchwd(&we, &rm_wd) == 0) && (rm_wd)) { inotify_rm_watch(inofd, rm_wd); } db_watch_enum_end(&we); db_watch_delete_bycookie(wi->cookie); } else { /* If the directory exists, it has been moved and we've * kept track of it successfully, so we're done */ ret = access(path, F_OK); if (ret == 0) return; /* Most probably a top-level dir is getting moved, * and we can't tell where it's going */ inotify_rm_watch(inofd, ie->wd); db_watch_delete_bywd(ie->wd); memset(&we, 0, sizeof(struct watch_enum)); we.match = path; ret = db_watch_enum_start(&we); if (ret < 0) return; while ((db_watch_enum_fetchwd(&we, &rm_wd) == 0) && (rm_wd)) { inotify_rm_watch(inofd, rm_wd); } db_watch_enum_end(&we); db_watch_delete_bymatch(path); db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); } } if (ie->mask & IN_MOVED_FROM) { db_watch_mark_bypath(path, path, ie->cookie); db_watch_mark_bymatch(path, path, ie->cookie); db_file_disable_bymatch(path, path, ie->cookie); db_pl_disable_bymatch(path, path, ie->cookie); } if (ie->mask & IN_MOVED_TO) { if (db_watch_cookie_known(ie->cookie)) { db_watch_move_bycookie(ie->cookie, path); db_file_enable_bycookie(ie->cookie, path); db_pl_enable_bycookie(ie->cookie, path); /* We'll rescan the directory tree to update playlists */ flags |= F_SCAN_RESCAN; } ie->mask |= IN_CREATE; } if (ie->mask & IN_CREATE) { process_directories(path, flags); if (dirstack) DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n"); } }