From: Karel Zak Date: Wed, 21 May 2025 09:43:57 +0000 (+0200) Subject: libmount: split monitor code to more files X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=defd0a7eb534df5d4e3c5d72e228bcf8c18b8b72;p=thirdparty%2Futil-linux.git libmount: split monitor code to more files Signed-off-by: Karel Zak --- diff --git a/libmount/meson.build b/libmount/meson.build index 29a43be02..f30a7fdd8 100644 --- a/libmount/meson.build +++ b/libmount/meson.build @@ -47,6 +47,9 @@ if LINUX lib_mount_sources += ''' src/hooks.c src/monitor.c + src/monitor.h + src/monitor_utab.c + src/monitor_mountinfo.c src/optlist.c src/hook_veritydev.c src/hook_subdir.c diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am index 5a5c787a4..5875062f2 100644 --- a/libmount/src/Makemodule.am +++ b/libmount/src/Makemodule.am @@ -42,7 +42,10 @@ libmount_la_SOURCES += \ libmount/src/hook_idmap.c \ libmount/src/hook_loopdev.c \ libmount/src/hook_veritydev.c \ - libmount/src/monitor.c + libmount/src/monitor.c \ + libmount/src/monitor.h \ + libmount/src/monitor_utab.c \ + libmount/src/monitor_mountinfo.c if HAVE_BTRFS libmount_la_SOURCES += libmount/src/btrfs.c @@ -159,7 +162,10 @@ test_mount_tab_diff_CFLAGS = $(libmount_tests_cflags) test_mount_tab_diff_LDFLAGS = $(libmount_tests_ldflags) test_mount_tab_diff_LDADD = $(libmount_tests_ldadd) -test_mount_monitor_SOURCES = libmount/src/monitor.c +test_mount_monitor_SOURCES = libmount/src/monitor.c \ + libmount/src/monitor.h \ + libmount/src/monitor_utab.c \ + libmount/src/monitor_mountinfo.c test_mount_monitor_CFLAGS = $(libmount_tests_cflags) test_mount_monitor_LDFLAGS = $(libmount_tests_ldflags) test_mount_monitor_LDADD = $(libmount_tests_ldadd) diff --git a/libmount/src/monitor.c b/libmount/src/monitor.c index 941f5d5b8..fb68d0d67 100644 --- a/libmount/src/monitor.c +++ b/libmount/src/monitor.c @@ -35,48 +35,11 @@ * */ -#include "fileutils.h" #include "mountP.h" -#include "pathnames.h" +#include "monitor.h" -#include #include - -struct monitor_opers; - -struct monitor_entry { - int fd; /* private entry file descriptor */ - char *path; /* path to the monitored file */ - int type; /* MNT_MONITOR_TYPE_* */ - uint32_t events; /* wanted epoll events */ - - const struct monitor_opers *opers; - - unsigned int enable : 1, - changed : 1; - - struct list_head ents; -}; - -struct libmnt_monitor { - int refcount; - int fd; /* public monitor file descriptor */ - - struct list_head ents; - - unsigned int kernel_veiled: 1; -}; - -struct monitor_opers { - int (*op_get_fd)(struct libmnt_monitor *, struct monitor_entry *); - int (*op_close_fd)(struct libmnt_monitor *, struct monitor_entry *); - int (*op_event_verify)(struct libmnt_monitor *, struct monitor_entry *); -}; - -static int monitor_modify_epoll(struct libmnt_monitor *mn, - struct monitor_entry *me, int enable); - /** * mnt_new_monitor: * @@ -111,7 +74,7 @@ void mnt_ref_monitor(struct libmnt_monitor *mn) mn->refcount++; } -static void free_monitor_entry(struct monitor_entry *me) +void free_monitor_entry(struct monitor_entry *me) { if (!me) return; @@ -148,7 +111,7 @@ void mnt_unref_monitor(struct libmnt_monitor *mn) } } -static struct monitor_entry *monitor_new_entry(struct libmnt_monitor *mn) +struct monitor_entry *monitor_new_entry(struct libmnt_monitor *mn) { struct monitor_entry *me; @@ -190,7 +153,7 @@ static int monitor_next_entry(struct libmnt_monitor *mn, } /* returns entry by type */ -static struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int type) +struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int type) { struct libmnt_iter itr; struct monitor_entry *me; @@ -204,391 +167,10 @@ static struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int ty } -/* - * Userspace monitor - */ - -static int userspace_monitor_close_fd(struct libmnt_monitor *mn __attribute__((__unused__)), - struct monitor_entry *me) -{ - assert(me); - - if (me->fd >= 0) - close(me->fd); - me->fd = -1; - return 0; -} - -static int userspace_add_watch(struct monitor_entry *me, int *final, int *fd) -{ - char *filename = NULL; - int wd, rc = -EINVAL; - - assert(me); - assert(me->path); - - /* - * libmount uses utab.event file to monitor and control utab updates - */ - if (asprintf(&filename, "%s.event", me->path) <= 0) { - rc = -ENOMEM; - goto done; - } - - /* try event file if already exists */ - errno = 0; - wd = inotify_add_watch(me->fd, filename, IN_CLOSE_WRITE); - if (wd >= 0) { - DBG(MONITOR, ul_debug(" added inotify watch for %s [fd=%d]", filename, wd)); - rc = 0; - if (final) - *final = 1; - if (fd) - *fd = wd; - goto done; - } else if (errno != ENOENT) { - rc = -errno; - goto done; - } - - while (strchr(filename, '/')) { - stripoff_last_component(filename); - if (!*filename) - break; - - /* try directory where is the event file */ - errno = 0; - wd = inotify_add_watch(me->fd, filename, IN_CREATE|IN_ISDIR); - if (wd >= 0) { - DBG(MONITOR, ul_debug(" added inotify watch for %s [fd=%d]", filename, wd)); - rc = 0; - if (fd) - *fd = wd; - break; - } - - if (errno != ENOENT) { - rc = -errno; - break; - } - } -done: - free(filename); - return rc; -} - -static int userspace_monitor_get_fd(struct libmnt_monitor *mn, - struct monitor_entry *me) -{ - int rc; - - if (!me || me->enable == 0) /* not-initialized or disabled */ - return -EINVAL; - if (me->fd >= 0) - return me->fd; /* already initialized */ - - assert(me->path); - DBG(MONITOR, ul_debugobj(mn, " open userspace monitor for %s", me->path)); - - me->fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); - if (me->fd < 0) - goto err; - - if (userspace_add_watch(me, NULL, NULL) < 0) - goto err; - - return me->fd; -err: - rc = -errno; - if (me->fd >= 0) - close(me->fd); - me->fd = -1; - DBG(MONITOR, ul_debugobj(mn, "failed to create userspace monitor [rc=%d]", rc)); - return rc; -} - -/* - * verify and drain inotify buffer - */ -static int userspace_event_verify(struct libmnt_monitor *mn, - struct monitor_entry *me) -{ - char buf[sizeof(struct inotify_event) + NAME_MAX + 1]; - int status = 0; - - if (!me || me->fd < 0) - return 0; - - DBG(MONITOR, ul_debugobj(mn, "drain and verify userspace monitor inotify")); - - /* the me->fd is non-blocking */ - do { - ssize_t len; - char *p; - const struct inotify_event *e; - - len = read(me->fd, buf, sizeof(buf)); - if (len < 0) - break; - - for (p = buf; p < buf + len; - p += sizeof(struct inotify_event) + e->len) { - - int fd = -1; - - e = (const struct inotify_event *) p; - DBG(MONITOR, ul_debugobj(mn, " inotify event 0x%x [%s]\n", e->mask, e->len ? e->name : "")); - - if (e->mask & IN_CLOSE_WRITE) - status = 1; - else { - /* add watch for the event file */ - userspace_add_watch(me, &status, &fd); - - if (fd != e->wd) { - DBG(MONITOR, ul_debugobj(mn, " removing watch [fd=%d]", e->wd)); - inotify_rm_watch(me->fd, e->wd); - } - } - } - } while (1); - - DBG(MONITOR, ul_debugobj(mn, "%s", status == 1 ? " success" : " nothing")); - return status; -} - -/* - * userspace monitor operations - */ -static const struct monitor_opers userspace_opers = { - .op_get_fd = userspace_monitor_get_fd, - .op_close_fd = userspace_monitor_close_fd, - .op_event_verify = userspace_event_verify -}; - -/** - * mnt_monitor_enable_userspace: - * @mn: monitor - * @enable: 0 or 1 - * @filename: overwrites default - * - * Enables or disables userspace monitoring. If the userspace monitor does not - * exist and enable=1 then allocates new resources necessary for the monitor. - * - * If the top-level monitor has been already created (by mnt_monitor_get_fd() - * or mnt_monitor_wait()) then it's updated according to @enable. - * - * The @filename is used only the first time when you enable the monitor. It's - * impossible to have more than one userspace monitor. The recommended is to - * use NULL as filename. - * - * The userspace monitor is unsupported for systems with classic regular - * /etc/mtab file. - * - * Return: 0 on success and <0 on error - */ -int mnt_monitor_enable_userspace(struct libmnt_monitor *mn, int enable, const char *filename) -{ - struct monitor_entry *me; - int rc = 0; - - if (!mn) - return -EINVAL; - - me = monitor_get_entry(mn, MNT_MONITOR_TYPE_USERSPACE); - if (me) { - rc = monitor_modify_epoll(mn, me, enable); - if (!enable) - userspace_monitor_close_fd(mn, me); - return rc; - } - if (!enable) - return 0; - - DBG(MONITOR, ul_debugobj(mn, "allocate new userspace monitor")); - - if (!filename) - filename = mnt_get_utab_path(); /* /run/mount/utab */ - if (!filename) { - DBG(MONITOR, ul_debugobj(mn, "failed to get userspace mount table path")); - return -EINVAL; - } - - me = monitor_new_entry(mn); - if (!me) - goto err; - - me->type = MNT_MONITOR_TYPE_USERSPACE; - me->opers = &userspace_opers; - me->events = EPOLLIN; - me->path = strdup(filename); - if (!me->path) - goto err; - - return monitor_modify_epoll(mn, me, TRUE); -err: - rc = -errno; - free_monitor_entry(me); - DBG(MONITOR, ul_debugobj(mn, "failed to allocate userspace monitor [rc=%d]", rc)); - return rc; -} - - -/* - * Kernel monitor - */ - -static int kernel_monitor_close_fd(struct libmnt_monitor *mn __attribute__((__unused__)), - struct monitor_entry *me) -{ - assert(me); - - if (me->fd >= 0) - close(me->fd); - me->fd = -1; - return 0; -} - -static int kernel_monitor_get_fd(struct libmnt_monitor *mn, - struct monitor_entry *me) -{ - int rc; - - if (!me || me->enable == 0) /* not-initialized or disabled */ - return -EINVAL; - if (me->fd >= 0) - return me->fd; /* already initialized */ - - assert(me->path); - DBG(MONITOR, ul_debugobj(mn, " open kernel monitor for %s", me->path)); - - me->fd = open(me->path, O_RDONLY|O_CLOEXEC); - if (me->fd < 0) - goto err; - - return me->fd; -err: - rc = -errno; - DBG(MONITOR, ul_debugobj(mn, "failed to create kernel monitor [rc=%d]", rc)); - return rc; -} - -static int kernel_event_verify(struct libmnt_monitor *mn, - struct monitor_entry *me) -{ - int status = 1; - - if (!mn || !me || me->fd < 0) - return 0; - - if (mn->kernel_veiled && access(MNT_PATH_UTAB ".act", F_OK) == 0) { - status = 0; - DBG(MONITOR, ul_debugobj(mn, "kernel event veiled")); - } - return status; -} - -/* - * kernel monitor operations - */ -static const struct monitor_opers kernel_opers = { - .op_get_fd = kernel_monitor_get_fd, - .op_close_fd = kernel_monitor_close_fd, - .op_event_verify = kernel_event_verify -}; - -/** - * mnt_monitor_enable_kernel: - * @mn: monitor - * @enable: 0 or 1 - * - * Enables or disables kernel VFS monitoring. If the monitor does not exist and - * enable=1 then allocates new resources necessary for the monitor. - * - * If the top-level monitor has been already created (by mnt_monitor_get_fd() - * or mnt_monitor_wait()) then it's updated according to @enable. - * - * Return: 0 on success and <0 on error - */ -int mnt_monitor_enable_kernel(struct libmnt_monitor *mn, int enable) -{ - struct monitor_entry *me; - int rc = 0; - - if (!mn) - return -EINVAL; - - me = monitor_get_entry(mn, MNT_MONITOR_TYPE_KERNEL); - if (me) { - rc = monitor_modify_epoll(mn, me, enable); - if (!enable) - kernel_monitor_close_fd(mn, me); - return rc; - } - if (!enable) - return 0; - - DBG(MONITOR, ul_debugobj(mn, "allocate new kernel monitor")); - - /* create a new entry */ - me = monitor_new_entry(mn); - if (!me) - goto err; - - /* If you want to use epoll FD in another epoll then top level - * epoll_wait() will drain all events from low-level FD if the - * low-level FD is not added with EPOLLIN. It means without EPOLLIN it - * it's impossible to detect which low-level FD has been active. - * - * Unfortunately, use EPOLLIN for mountinfo is tricky because in this - * case kernel returns events all time (we don't read from the FD). - * The solution is to use also edge-triggered (EPOLLET) flag, then - * kernel generate events on mountinfo changes only. The disadvantage is - * that we have to drain initial event generated by EPOLLIN after - * epoll_ctl(ADD). See monitor_modify_epoll(). - */ - me->events = EPOLLIN | EPOLLET; - - me->type = MNT_MONITOR_TYPE_KERNEL; - me->opers = &kernel_opers; - me->path = strdup(_PATH_PROC_MOUNTINFO); - if (!me->path) - goto err; - - return monitor_modify_epoll(mn, me, TRUE); -err: - rc = -errno; - free_monitor_entry(me); - DBG(MONITOR, ul_debugobj(mn, "failed to allocate kernel monitor [rc=%d]", rc)); - return rc; -} - -/** - * mnt_monitor_veil_kernel: - * @mn: monitor instance - * @enable: 1 or 0 - * - * Force monitor to ignore kernel events if the same mount/umount operation - * will generate an userspace event later. The kernel-only mount operation will - * be not affected. - * - * Return: 0 on success and <0 on error. - * - * Since: 2.40 - */ -int mnt_monitor_veil_kernel(struct libmnt_monitor *mn, int enable) -{ - if (!mn) - return -EINVAL; - - mn->kernel_veiled = enable ? 1 : 0; - return 0; -} - /* * Add/Remove monitor entry to/from monitor epoll. */ -static int monitor_modify_epoll(struct libmnt_monitor *mn, +int monitor_modify_epoll(struct libmnt_monitor *mn, struct monitor_entry *me, int enable) { assert(mn); diff --git a/libmount/src/monitor.h b/libmount/src/monitor.h new file mode 100644 index 000000000..a72658e95 --- /dev/null +++ b/libmount/src/monitor.h @@ -0,0 +1,43 @@ + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef _MNTMONITOR_PRIVATE_H +#define _MNTMONITOR_PRIVATE_H + +struct monitor_opers; + +struct monitor_entry { + int fd; /* private entry file descriptor */ + char *path; /* path to the monitored file */ + int type; /* MNT_MONITOR_TYPE_* */ + uint32_t events; /* wanted epoll events */ + + const struct monitor_opers *opers; + + unsigned int enable : 1, + changed : 1; + + struct list_head ents; +}; + +struct libmnt_monitor { + int refcount; + int fd; /* public monitor file descriptor */ + + struct list_head ents; + + unsigned int kernel_veiled: 1; +}; + +struct monitor_opers { + int (*op_get_fd)(struct libmnt_monitor *, struct monitor_entry *); + int (*op_close_fd)(struct libmnt_monitor *, struct monitor_entry *); + int (*op_event_verify)(struct libmnt_monitor *, struct monitor_entry *); +}; + +int monitor_modify_epoll(struct libmnt_monitor *mn, struct monitor_entry *me, int enable); +struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int type); +struct monitor_entry *monitor_new_entry(struct libmnt_monitor *mn); +void free_monitor_entry(struct monitor_entry *me); + +#endif /* _MNTMONITOR_PRIVATE_H */ diff --git a/libmount/src/monitor_mountinfo.c b/libmount/src/monitor_mountinfo.c new file mode 100644 index 000000000..56171fd91 --- /dev/null +++ b/libmount/src/monitor_mountinfo.c @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2014-2025 Karel Zak + * + * libmount is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * libmount kernel mount table monitor (based on /proc/self/mountinfo epoll) + */ + +#include "mountP.h" +#include "monitor.h" + +#include "pathnames.h" + +#include + +static int kernel_monitor_close_fd(struct libmnt_monitor *mn __attribute__((__unused__)), + struct monitor_entry *me) +{ + assert(me); + + if (me->fd >= 0) + close(me->fd); + me->fd = -1; + return 0; +} + +static int kernel_monitor_get_fd(struct libmnt_monitor *mn, + struct monitor_entry *me) +{ + int rc; + + if (!me || me->enable == 0) /* not-initialized or disabled */ + return -EINVAL; + if (me->fd >= 0) + return me->fd; /* already initialized */ + + assert(me->path); + DBG(MONITOR, ul_debugobj(mn, " open kernel monitor for %s", me->path)); + + me->fd = open(me->path, O_RDONLY|O_CLOEXEC); + if (me->fd < 0) + goto err; + + return me->fd; +err: + rc = -errno; + DBG(MONITOR, ul_debugobj(mn, "failed to create kernel monitor [rc=%d]", rc)); + return rc; +} + +static int kernel_event_verify(struct libmnt_monitor *mn, + struct monitor_entry *me) +{ + int status = 1; + + if (!mn || !me || me->fd < 0) + return 0; + + if (mn->kernel_veiled && access(MNT_PATH_UTAB ".act", F_OK) == 0) { + status = 0; + DBG(MONITOR, ul_debugobj(mn, "kernel event veiled")); + } + return status; +} + +/* + * kernel monitor operations + */ +static const struct monitor_opers kernel_opers = { + .op_get_fd = kernel_monitor_get_fd, + .op_close_fd = kernel_monitor_close_fd, + .op_event_verify = kernel_event_verify +}; + +/** + * mnt_monitor_enable_kernel: + * @mn: monitor + * @enable: 0 or 1 + * + * Enables or disables kernel VFS monitoring. If the monitor does not exist and + * enable=1 then allocates new resources necessary for the monitor. + * + * If the top-level monitor has been already created (by mnt_monitor_get_fd() + * or mnt_monitor_wait()) then it's updated according to @enable. + * + * Return: 0 on success and <0 on error + */ +int mnt_monitor_enable_kernel(struct libmnt_monitor *mn, int enable) +{ + struct monitor_entry *me; + int rc = 0; + + if (!mn) + return -EINVAL; + + me = monitor_get_entry(mn, MNT_MONITOR_TYPE_KERNEL); + if (me) { + rc = monitor_modify_epoll(mn, me, enable); + if (!enable) + kernel_monitor_close_fd(mn, me); + return rc; + } + if (!enable) + return 0; + + DBG(MONITOR, ul_debugobj(mn, "allocate new kernel monitor")); + + /* create a new entry */ + me = monitor_new_entry(mn); + if (!me) + goto err; + + /* If you want to use epoll FD in another epoll then top level + * epoll_wait() will drain all events from low-level FD if the + * low-level FD is not added with EPOLLIN. It means without EPOLLIN it + * it's impossible to detect which low-level FD has been active. + * + * Unfortunately, use EPOLLIN for mountinfo is tricky because in this + * case kernel returns events all time (we don't read from the FD). + * The solution is to use also edge-triggered (EPOLLET) flag, then + * kernel generate events on mountinfo changes only. The disadvantage is + * that we have to drain initial event generated by EPOLLIN after + * epoll_ctl(ADD). See monitor_modify_epoll(). + */ + me->events = EPOLLIN | EPOLLET; + + me->type = MNT_MONITOR_TYPE_KERNEL; + me->opers = &kernel_opers; + me->path = strdup(_PATH_PROC_MOUNTINFO); + if (!me->path) + goto err; + + return monitor_modify_epoll(mn, me, TRUE); +err: + rc = -errno; + free_monitor_entry(me); + DBG(MONITOR, ul_debugobj(mn, "failed to allocate kernel monitor [rc=%d]", rc)); + return rc; +} + +/** + * mnt_monitor_veil_kernel: + * @mn: monitor instance + * @enable: 1 or 0 + * + * Force monitor to ignore kernel events if the same mount/umount operation + * will generate an userspace event later. The kernel-only mount operation will + * be not affected. + * + * Return: 0 on success and <0 on error. + * + * Since: 2.40 + */ +int mnt_monitor_veil_kernel(struct libmnt_monitor *mn, int enable) +{ + if (!mn) + return -EINVAL; + + mn->kernel_veiled = enable ? 1 : 0; + return 0; +} diff --git a/libmount/src/monitor_utab.c b/libmount/src/monitor_utab.c new file mode 100644 index 000000000..d7261a947 --- /dev/null +++ b/libmount/src/monitor_utab.c @@ -0,0 +1,248 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2014-2025 Karel Zak + * + * libmount is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * + * libmount userspace monitor (based on /run/mount/utab inotify) + */ + +#include "mountP.h" +#include "monitor.h" + +#include "fileutils.h" + +#include +#include + +static int userspace_monitor_close_fd( + struct libmnt_monitor *mn __attribute__((__unused__)), + struct monitor_entry *me) +{ + assert(me); + + if (me->fd >= 0) + close(me->fd); + me->fd = -1; + return 0; +} + +static int userspace_add_watch(struct monitor_entry *me, int *final, int *fd) +{ + char *filename = NULL; + int wd, rc = -EINVAL; + + assert(me); + assert(me->path); + + /* + * libmount uses utab.event file to monitor and control utab updates + */ + if (asprintf(&filename, "%s.event", me->path) <= 0) { + rc = -ENOMEM; + goto done; + } + + /* try event file if already exists */ + errno = 0; + wd = inotify_add_watch(me->fd, filename, IN_CLOSE_WRITE); + if (wd >= 0) { + DBG(MONITOR, ul_debug(" added inotify watch for %s [fd=%d]", filename, wd)); + rc = 0; + if (final) + *final = 1; + if (fd) + *fd = wd; + goto done; + } else if (errno != ENOENT) { + rc = -errno; + goto done; + } + + while (strchr(filename, '/')) { + stripoff_last_component(filename); + if (!*filename) + break; + + /* try directory where is the event file */ + errno = 0; + wd = inotify_add_watch(me->fd, filename, IN_CREATE|IN_ISDIR); + if (wd >= 0) { + DBG(MONITOR, ul_debug(" added inotify watch for %s [fd=%d]", filename, wd)); + rc = 0; + if (fd) + *fd = wd; + break; + } + + if (errno != ENOENT) { + rc = -errno; + break; + } + } +done: + free(filename); + return rc; +} + +static int userspace_monitor_get_fd(struct libmnt_monitor *mn, + struct monitor_entry *me) +{ + int rc; + + if (!me || me->enable == 0) /* not-initialized or disabled */ + return -EINVAL; + if (me->fd >= 0) + return me->fd; /* already initialized */ + + assert(me->path); + DBG(MONITOR, ul_debugobj(mn, " open userspace monitor for %s", me->path)); + + me->fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + if (me->fd < 0) + goto err; + + if (userspace_add_watch(me, NULL, NULL) < 0) + goto err; + + return me->fd; +err: + rc = -errno; + if (me->fd >= 0) + close(me->fd); + me->fd = -1; + DBG(MONITOR, ul_debugobj(mn, "failed to create userspace monitor [rc=%d]", rc)); + return rc; +} + +/* + * verify and drain inotify buffer + */ +static int userspace_event_verify(struct libmnt_monitor *mn, + struct monitor_entry *me) +{ + char buf[sizeof(struct inotify_event) + NAME_MAX + 1]; + int status = 0; + + if (!me || me->fd < 0) + return 0; + + DBG(MONITOR, ul_debugobj(mn, "drain and verify userspace monitor inotify")); + + /* the me->fd is non-blocking */ + do { + ssize_t len; + char *p; + const struct inotify_event *e; + + len = read(me->fd, buf, sizeof(buf)); + if (len < 0) + break; + + for (p = buf; p < buf + len; + p += sizeof(struct inotify_event) + e->len) { + + int fd = -1; + + e = (const struct inotify_event *) p; + DBG(MONITOR, ul_debugobj(mn, " inotify event 0x%x [%s]\n", e->mask, e->len ? e->name : "")); + + if (e->mask & IN_CLOSE_WRITE) + status = 1; + else { + /* add watch for the event file */ + userspace_add_watch(me, &status, &fd); + + if (fd != e->wd) { + DBG(MONITOR, ul_debugobj(mn, " removing watch [fd=%d]", e->wd)); + inotify_rm_watch(me->fd, e->wd); + } + } + } + } while (1); + + DBG(MONITOR, ul_debugobj(mn, "%s", status == 1 ? " success" : " nothing")); + return status; +} + +/* + * userspace monitor operations + */ +static const struct monitor_opers userspace_opers = { + .op_get_fd = userspace_monitor_get_fd, + .op_close_fd = userspace_monitor_close_fd, + .op_event_verify = userspace_event_verify +}; + +/** + * mnt_monitor_enable_userspace: + * @mn: monitor + * @enable: 0 or 1 + * @filename: overwrites default + * + * Enables or disables userspace monitoring. If the userspace monitor does not + * exist and enable=1 then allocates new resources necessary for the monitor. + * + * If the top-level monitor has been already created (by mnt_monitor_get_fd() + * or mnt_monitor_wait()) then it's updated according to @enable. + * + * The @filename is used only the first time when you enable the monitor. It's + * impossible to have more than one userspace monitor. The recommended is to + * use NULL as filename. + * + * The userspace monitor is unsupported for systems with classic regular + * /etc/mtab file. + * + * Return: 0 on success and <0 on error + */ +int mnt_monitor_enable_userspace(struct libmnt_monitor *mn, int enable, const char *filename) +{ + struct monitor_entry *me; + int rc = 0; + + if (!mn) + return -EINVAL; + + me = monitor_get_entry(mn, MNT_MONITOR_TYPE_USERSPACE); + if (me) { + rc = monitor_modify_epoll(mn, me, enable); + if (!enable) + userspace_monitor_close_fd(mn, me); + return rc; + } + if (!enable) + return 0; + + DBG(MONITOR, ul_debugobj(mn, "allocate new userspace monitor")); + + if (!filename) + filename = mnt_get_utab_path(); /* /run/mount/utab */ + if (!filename) { + DBG(MONITOR, ul_debugobj(mn, "failed to get userspace mount table path")); + return -EINVAL; + } + + me = monitor_new_entry(mn); + if (!me) + goto err; + + me->type = MNT_MONITOR_TYPE_USERSPACE; + me->opers = &userspace_opers; + me->events = EPOLLIN; + me->path = strdup(filename); + if (!me->path) + goto err; + + return monitor_modify_epoll(mn, me, TRUE); +err: + rc = -errno; + free_monitor_entry(me); + DBG(MONITOR, ul_debugobj(mn, "failed to allocate userspace monitor [rc=%d]", rc)); + return rc; +}