diff --git a/configure.ac b/configure.ac index e2c25e04..c0887ec5 100644 --- a/configure.ac +++ b/configure.ac @@ -103,6 +103,9 @@ AC_SEARCH_LIBS([copy_file_range], [c], AC_SEARCH_LIBS([fcopyfile], [c], [AC_DEFINE([HAVE_FCOPYFILE], 1, [Define to 1 if you have fcopyfile])]) +AC_SEARCH_LIBS([mnt_new_monitor], [mount], + [AC_DEFINE([HAVE_LIBMOUNT], 1, + [Define to 1 if you have libmount])]) AC_SEARCH_LIBS([log10], [m]) AC_SEARCH_LIBS([lrint], [m]) diff --git a/src/Makefile.am b/src/Makefile.am index 9dd91b70..ee93e0a7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -88,6 +88,7 @@ owntone_SOURCES = main.c \ library/filescanner.c library/filescanner.h \ library/filescanner_ffmpeg.c library/filescanner_playlist.c \ library/filescanner_smartpl.c library/filescanner_itunes.c \ + library/filescanner_mountwatch.c \ library/rssscanner.c \ library.c library.h \ $(MDNS_SRC) mdns.h \ diff --git a/src/library/filescanner.c b/src/library/filescanner.c index ffcf9e59..de3169a5 100644 --- a/src/library/filescanner.c +++ b/src/library/filescanner.c @@ -115,6 +115,7 @@ struct stacked_dir { static int inofd; static struct event *inoev; +static struct event *mntev; static struct deferred_pl *playlists; static struct stacked_dir *dirstack; @@ -1130,13 +1131,6 @@ process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie) DPRINTF(E_DBG, L_SCAN, "Directory event: 0x%08x, cookie 0x%08x, wd %d\n", ie->mask, ie->cookie, wi->wd); - if (ie->mask & IN_UNMOUNT) - { - db_file_disable_bymatch(path, STRIP_NONE, 0); - db_pl_disable_bymatch(path, STRIP_NONE, 0); - db_directory_disable_bymatch(path, STRIP_NONE, 0); - } - if (ie->mask & IN_MOVE_SELF) { /* A directory we know about, that got moved from a place @@ -1508,6 +1502,7 @@ inotify_cb(int fd, short event, void *arg) { struct inotify_event *ie; struct watch_info wi; + struct stat sb; uint8_t *buf; uint8_t *ptr; char path[PATH_MAX]; @@ -1565,8 +1560,16 @@ inotify_cb(int fd, short event, void *arg) DPRINTF(E_DBG, L_SCAN, "%s deleted or backing filesystem unmounted!\n", wi.path); db_watch_delete_bywd(ie->wd); - free_wi(&wi, 1); - continue; + + // Is the directory gone? + if (! (lstat(wi.path, &sb) == 0 && S_ISDIR(sb.st_mode))) + { + free_wi(&wi, 1); + continue; + } + + // After an unmount event the mount point is a regular dir that we must process + ie->mask |= IN_CREATE; } path[0] = '\0'; @@ -1614,28 +1617,77 @@ inotify_cb(int fd, short event, void *arg) event_add(inoev, NULL); } +static void +mount_cb(int fd, short event, void *arg) +{ + struct watch_info wi = { 0 }; + char *path = NULL; + enum mountwatch_event mev; + int parent_id; + int ret; + + mev = mountwatch_event_get(&path); + if (mev == MOUNTWATCH_ERR || mev == MOUNTWATCH_NONE) + goto readd; + + // Check if this is a location we are watching + ret = db_watch_get_bypath(&wi, path); + if (ret < 0) + goto readd; + + if (mev == MOUNTWATCH_MOUNT) + { + DPRINTF(E_DBG, L_SCAN, "MNT_MOUNT path %s\n", path); + + // After a mount, the inotify watch we had on the mount point will no + // longer be working, so remove. + inotify_rm_watch(inofd, wi.wd); + db_watch_delete_bywd(wi.wd); + + // Adds watches, scans and sets disabled = 0 + parent_id = get_parent_dir_id(path); + process_directories(path, parent_id, 0); + } + else if (mev == MOUNTWATCH_UNMOUNT) + { + DPRINTF(E_DBG, L_SCAN, "MNT_UNMOUNT path %s\n", path); + + db_file_disable_bymatch(path, STRIP_NONE, 0); + db_pl_disable_bymatch(path, STRIP_NONE, 0); + db_directory_disable_bymatch(path, STRIP_NONE, 0); + } + + readd: + free_wi(&wi, 1); + free(path); + event_add(mntev, NULL); +} + /* Thread: main & scan */ static int inofd_event_set(void) { + int mntfd; + inofd = inotify_init1(IN_CLOEXEC); if (inofd < 0) { DPRINTF(E_FATAL, L_SCAN, "Could not create inotify fd: %s\n", strerror(errno)); - return -1; } + else + CHECK_NULL(L_SCAN, inoev = event_new(evbase_lib, inofd, EV_READ, inotify_cb, NULL)); - inoev = event_new(evbase_lib, inofd, EV_READ, inotify_cb, NULL); + // If mountwatch cannot be initialized we create mntev as a timer (that will + // never trigger), so that all the event_add() don't need a "if (mntev)" test. + mntfd = mountwatch_init(); + if (mntfd >= 0) + CHECK_NULL(L_SCAN, mntev = event_new(evbase_lib, mntfd, EV_READ, mount_cb, NULL)); + else + CHECK_NULL(L_SCAN, mntev = evtimer_new(evbase_lib, mount_cb, NULL)); #ifndef __linux__ - deferred_inoev = evtimer_new(evbase_lib, inotify_deferred_cb, NULL); - if (!deferred_inoev) - { - DPRINTF(E_LOG, L_SCAN, "Could not create deferred inotify event\n"); - - return -1; - } + CHECK_NULL(L_SCAN, deferred_inoev = evtimer_new(evbase_lib, inotify_deferred_cb, NULL)); #endif return 0; @@ -1648,6 +1700,9 @@ inofd_event_unset(void) #ifndef __linux__ event_free(deferred_inoev); #endif + event_free(mntev); + mountwatch_deinit(); + event_free(inoev); close(inofd); } @@ -1674,6 +1729,7 @@ filescanner_initscan() { /* Enable inotify */ event_add(inoev, NULL); + event_add(mntev, NULL); } return 0; } @@ -1692,6 +1748,7 @@ filescanner_rescan() { /* Enable inotify */ event_add(inoev, NULL); + event_add(mntev, NULL); } return 0; } @@ -1710,6 +1767,7 @@ filescanner_metarescan() { /* Enable inotify */ event_add(inoev, NULL); + event_add(mntev, NULL); } return 0; } @@ -1727,6 +1785,7 @@ filescanner_fullrescan() { /* Enable inotify */ event_add(inoev, NULL); + event_add(mntev, NULL); } return 0; } @@ -2210,9 +2269,7 @@ filescanner_init(void) ret = inofd_event_set(); if (ret < 0) - { - return -1; - } + return -1; return 0; } diff --git a/src/library/filescanner.h b/src/library/filescanner.h index 52370c1e..ac749516 100644 --- a/src/library/filescanner.h +++ b/src/library/filescanner.h @@ -4,6 +4,13 @@ #include "db.h" +enum mountwatch_event +{ + MOUNTWATCH_ERR = -1, + MOUNTWATCH_NONE = 0, + MOUNTWATCH_MOUNT = 1, + MOUNTWATCH_UNMOUNT = 2, +}; /* --------------------------- Actual scanners ---------------------------- */ @@ -84,4 +91,13 @@ playlist_add(const char *path); int write_metadata_ffmpeg(const struct media_file_info *mfi); +int +mountwatch_event_get(char **path); + +void +mountwatch_deinit(void); + +int +mountwatch_init(void); + #endif /* !__FILESCANNER_H__ */ diff --git a/src/library/filescanner_mountwatch.c b/src/library/filescanner_mountwatch.c new file mode 100644 index 00000000..e9b55e82 --- /dev/null +++ b/src/library/filescanner_mountwatch.c @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "filescanner.h" +#include "config.h" +#include "misc.h" +#include "logger.h" + +#ifdef HAVE_LIBMOUNT +#include + +static struct libmnt_monitor *mountwatch_monitor; +static struct libmnt_table *mountwatch_table; + +// path is allocated if a change is found +static int +compare_tables(char **path, struct libmnt_table *old_tab, struct libmnt_table *new_tab) +{ + struct libmnt_iter *iter; + struct libmnt_fs *fs; + const char *target; + + *path = NULL; + + CHECK_NULL(L_SCAN, iter = mnt_new_iter(MNT_ITER_FORWARD)); + + // Find new mounts (in new_tab but not in old_tab) + mnt_reset_iter(iter, MNT_ITER_FORWARD); + while (mnt_table_next_fs(new_tab, iter, &fs) == 0) + { + target = mnt_fs_get_target(fs); + if (!target || mnt_table_find_target(old_tab, target, MNT_ITER_FORWARD)) + continue; + + *path = strdup(target); + return MOUNTWATCH_MOUNT; + } + + // Find removed mounts (in old_tab but not in new_tab) + mnt_reset_iter(iter, MNT_ITER_FORWARD); + while (mnt_table_next_fs(old_tab, iter, &fs) == 0) + { + target = mnt_fs_get_target(fs); + if (!target || mnt_table_find_target(new_tab, target, MNT_ITER_FORWARD)) + continue; + + *path = strdup(target); + return MOUNTWATCH_UNMOUNT; + } + + mnt_free_iter(iter); + return MOUNTWATCH_NONE; +} + +int +mountwatch_event_get(char **path) +{ + struct libmnt_table *newtable = NULL; + enum mountwatch_event event; + int ret; + + *path = NULL; + + ret = mnt_monitor_event_cleanup(mountwatch_monitor); + if (ret < 0) + { + DPRINTF(E_WARN, L_SCAN, "Monitor cleanup failed: %s\n", strerror(-ret)); + goto error; + } + + CHECK_NULL(L_SCAN, newtable = mnt_new_table()); + + ret = mnt_table_parse_mtab(newtable, NULL); + if (ret < 0) + { + DPRINTF(E_WARN, L_SCAN, "Failed to reload mount table: %s\n", strerror(-ret)); + goto error; + } + + event = compare_tables(path, mountwatch_table, newtable); + + mnt_unref_table(mountwatch_table); + mountwatch_table = newtable; + + return event; + + error: + if (newtable) + mnt_unref_table(newtable); + + return MOUNTWATCH_ERR; +} + +void +mountwatch_deinit(void) +{ + mnt_unref_table(mountwatch_table); + mountwatch_table = NULL; + + mnt_unref_monitor(mountwatch_monitor); + mountwatch_monitor = NULL; +} + +int +mountwatch_init(void) +{ + int ret, fd; + + CHECK_NULL(L_SCAN, mountwatch_monitor = mnt_new_monitor()); + + ret = mnt_monitor_enable_kernel(mountwatch_monitor, 1); + if (ret < 0) + goto error; + + fd = mnt_monitor_get_fd(mountwatch_monitor); + if (fd < 0) + goto error; + + mountwatch_table = mnt_new_table(); + if (!mountwatch_table) + goto error; + + ret = mnt_table_parse_mtab(mountwatch_table, NULL); + if (ret < 0) + goto error; + + return fd; + + error: + DPRINTF(E_LOG, L_SCAN, "Error initializing libmount, mount/unmount events won't be detected\n"); + mountwatch_deinit(); + errno = -ret; //TODO + return -1; +} +#else +int +mountwatch_event_get(char **path) +{ + *path = NULL; + return MOUNTWATCH_NONE; +} + +void +mountwatch_deinit(void) +{ + return; +} + +int +mountwatch_init(void) +{ + DPRINTF(E_LOG, L_SCAN, "No libmount on this platform, mount/unmount events won't be detected\n"); + return -1; +} +#endif