mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-30 07:56:02 -05:00
Implement directory rescanning on top of kqueue/kevent for FreeBSD
This commit is contained in:
parent
69bae139bb
commit
364b22c030
4
src/db.h
4
src/db.h
@ -221,10 +221,6 @@ struct watch_info {
|
|||||||
char *path;
|
char *path;
|
||||||
uint32_t cookie;
|
uint32_t cookie;
|
||||||
int libidx;
|
int libidx;
|
||||||
|
|
||||||
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
|
||||||
void *priv;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#define wi_offsetof(field) offsetof(struct watch_info, field)
|
#define wi_offsetof(field) offsetof(struct watch_info, field)
|
||||||
|
@ -413,11 +413,9 @@ process_directory(int libidx, char *path, int flags)
|
|||||||
char entry[PATH_MAX];
|
char entry[PATH_MAX];
|
||||||
char *deref;
|
char *deref;
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
#if defined(__linux__)
|
|
||||||
struct watch_info wi;
|
struct watch_info wi;
|
||||||
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
||||||
struct watch_info *wi;
|
struct kevent kev;
|
||||||
struct kevent *kev;
|
|
||||||
#endif
|
#endif
|
||||||
int compilation;
|
int compilation;
|
||||||
int ret;
|
int ret;
|
||||||
@ -526,9 +524,9 @@ process_directory(int libidx, char *path, int flags)
|
|||||||
|
|
||||||
closedir(dirp);
|
closedir(dirp);
|
||||||
|
|
||||||
#if defined(__linux__)
|
|
||||||
memset(&wi, 0, sizeof(struct watch_info));
|
memset(&wi, 0, sizeof(struct watch_info));
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
/* Add inotify watch */
|
/* Add inotify watch */
|
||||||
wi.wd = inotify_add_watch(inofd, path, IN_CREATE | IN_DELETE | IN_MODIFY | IN_CLOSE_WRITE | IN_MOVE | IN_DELETE | IN_MOVE_SELF);
|
wi.wd = inotify_add_watch(inofd, path, IN_CREATE | IN_DELETE | IN_MODIFY | IN_CLOSE_WRITE | IN_MOVE | IN_DELETE | IN_MOVE_SELF);
|
||||||
if (wi.wd < 0)
|
if (wi.wd < 0)
|
||||||
@ -546,71 +544,33 @@ process_directory(int libidx, char *path, int flags)
|
|||||||
db_watch_add(&wi);
|
db_watch_add(&wi);
|
||||||
|
|
||||||
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
||||||
|
memset(&kev, 0, sizeof(struct kevent));
|
||||||
|
|
||||||
wi = (struct watch_info *)malloc(sizeof(struct watch_info));
|
wi.wd = open(path, O_RDONLY | O_NONBLOCK);
|
||||||
if (!wi)
|
if (wi.wd < 0)
|
||||||
{
|
|
||||||
DPRINTF(E_WARN, L_SCAN, "Could not allocate watch_info for %s: %s\n", path, strerror(errno));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
kev = (struct kevent *)malloc(sizeof(struct kevent));
|
|
||||||
if (!kev)
|
|
||||||
{
|
|
||||||
DPRINTF(E_WARN, L_SCAN, "Could not allocate kevent for %s: %s\n", path, strerror(errno));
|
|
||||||
|
|
||||||
free(wi);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(wi, 0, sizeof(struct watch_info));
|
|
||||||
memset(kev, 0, sizeof(struct kevent));
|
|
||||||
|
|
||||||
wi->priv = kev;
|
|
||||||
|
|
||||||
/* TODO: we probably want to keep track of the watch_info structs
|
|
||||||
* somewhere in addition to the kevent struct
|
|
||||||
*/
|
|
||||||
|
|
||||||
wi->wd = open(path, O_RDONLY | O_NONBLOCK);
|
|
||||||
if (wi->wd < 0)
|
|
||||||
{
|
{
|
||||||
DPRINTF(E_WARN, L_SCAN, "Could not open directory %s for watching: %s\n", path, strerror(errno));
|
DPRINTF(E_WARN, L_SCAN, "Could not open directory %s for watching: %s\n", path, strerror(errno));
|
||||||
|
|
||||||
free(wi);
|
|
||||||
free(kev);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
wi->libidx = libidx;
|
|
||||||
wi->path = strdup(path);
|
|
||||||
if (!wi->path)
|
|
||||||
{
|
|
||||||
DPRINTF(E_WARN, L_SCAN, "Out of memory for watch path\n");
|
|
||||||
|
|
||||||
close(wi->wd);
|
|
||||||
free(wi);
|
|
||||||
free(kev);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add kevent */
|
/* Add kevent */
|
||||||
EV_SET(kev, wi->wd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_WRITE | NOTE_RENAME, 0, wi);
|
EV_SET(&kev, wi.wd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_WRITE | NOTE_RENAME, 0, NULL);
|
||||||
|
|
||||||
ret = kevent(inofd, kev, 1, NULL, 0, NULL);
|
ret = kevent(inofd, &kev, 1, NULL, 0, NULL);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_WARN, L_SCAN, "Could not add kevent for %s: %s\n", path, strerror(errno));
|
DPRINTF(E_WARN, L_SCAN, "Could not add kevent for %s: %s\n", path, strerror(errno));
|
||||||
|
|
||||||
close(wi->wd);
|
close(wi.wd);
|
||||||
free(wi->path);
|
|
||||||
free(wi);
|
|
||||||
free(kev);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: we'll probably want to add the watch to the DB */
|
wi.libidx = libidx;
|
||||||
|
wi.cookie = 0;
|
||||||
|
wi.path = path;
|
||||||
|
|
||||||
|
db_watch_add(&wi);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1047,55 +1007,176 @@ kqueue_cb(int fd, short event, void *arg)
|
|||||||
{
|
{
|
||||||
struct kevent kev;
|
struct kevent kev;
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
struct watch_info *wi;
|
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_sec = 0;
|
||||||
ts.tv_nsec = 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
|
/* 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.
|
* 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,
|
* Unfortunately, that means we only know when directories get renamed,
|
||||||
* deleted or changed. We don't get directory/file names when directories/files
|
* deleted or changed. We don't get directory/file names when directories/files
|
||||||
* are created/deleted in the directory, so we have to rescan.
|
* are created/deleted/renamed in the directory, so we have to rescan.
|
||||||
*/
|
*/
|
||||||
while (kevent(fd, NULL, 0, &kev, 1, &ts) > 0)
|
while (kevent(fd, NULL, 0, &kev, 1, &ts) > 0)
|
||||||
{
|
{
|
||||||
wi = (struct watch_info *)kev.udata;
|
/* This should not happen, and if it does, we'll end up in
|
||||||
|
* an infinite loop.
|
||||||
if (kev.flags & EV_ERROR)
|
*/
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_SCAN, "kevent reports EV_ERROR: %s\n", strerror(kev.data));
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kev.filter != EVFILT_VNODE)
|
if (kev.filter != EVFILT_VNODE)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* TODO: well ... everything. */
|
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)
|
if (kev.fflags & NOTE_WRITE)
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_SCAN, "Got NOTE_WRITE (%s)\n", wi->path);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kev.fflags & NOTE_DELETE)
|
free(wi.path);
|
||||||
{
|
}
|
||||||
DPRINTF(E_DBG, L_SCAN, "Got NOTE_DELETE (%s)\n", wi->path);
|
|
||||||
|
|
||||||
close(wi->wd);
|
while ((path = pop_dir(&rescan)))
|
||||||
free(wi->priv);
|
{
|
||||||
free(wi->path);
|
/* FIXME: libidx for multi-library support */
|
||||||
free(wi);
|
process_directories(0, path, 0);
|
||||||
}
|
|
||||||
|
|
||||||
if (kev.fflags & NOTE_RENAME)
|
free(path);
|
||||||
{
|
|
||||||
DPRINTF(E_DBG, L_SCAN, "Got NOTE_RENAME (%s)\n", wi->path);
|
|
||||||
|
|
||||||
/* TODO: might need to free watch_info & friends just like in
|
if (rescan)
|
||||||
* the NOTE_DELETE case above. Depends on how this will be handled.
|
DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n");
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event_add(&inoev, NULL);
|
event_add(&inoev, NULL);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user