[scan] Use libmount to detect filesystem mount events

Resolves issue #1897
This commit is contained in:
ejurgensen 2025-06-26 23:06:46 +02:00
parent 324b6eb61a
commit e607019a1c
5 changed files with 255 additions and 21 deletions

View File

@ -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])

View File

@ -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 \

View File

@ -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,73 @@ 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);
mntfd = mountwatch_init();
if (mntfd >= 0)
CHECK_NULL(L_SCAN, mntev = event_new(evbase_lib, mntfd, EV_READ, 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 +1696,9 @@ inofd_event_unset(void)
#ifndef __linux__
event_free(deferred_inoev);
#endif
event_free(mntev);
mountwatch_deinit();
event_free(inoev);
close(inofd);
}
@ -1674,6 +1725,7 @@ filescanner_initscan()
{
/* Enable inotify */
event_add(inoev, NULL);
event_add(mntev, NULL);
}
return 0;
}
@ -1692,6 +1744,7 @@ filescanner_rescan()
{
/* Enable inotify */
event_add(inoev, NULL);
event_add(mntev, NULL);
}
return 0;
}
@ -1710,6 +1763,7 @@ filescanner_metarescan()
{
/* Enable inotify */
event_add(inoev, NULL);
event_add(mntev, NULL);
}
return 0;
}
@ -1727,6 +1781,7 @@ filescanner_fullrescan()
{
/* Enable inotify */
event_add(inoev, NULL);
event_add(mntev, NULL);
}
return 0;
}
@ -2210,9 +2265,7 @@ filescanner_init(void)
ret = inofd_event_set();
if (ret < 0)
{
return -1;
}
return -1;
return 0;
}

View File

@ -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__ */

View File

@ -0,0 +1,161 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "filescanner.h"
#include "config.h"
#include "misc.h"
#include "logger.h"
#ifdef HAVE_LIBMOUNT
#include <libmount/libmount.h>
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_changed_get(char **path)
{
return -1;
}
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