diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:42:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:42:50 +0000 |
commit | 8cb83eee5a58b1fad74c34094ce3afb9e430b5a4 (patch) | |
tree | a9b2e7baeca1be40eb734371e3c8b11b02294497 /libmount/src/monitor.c | |
parent | Initial commit. (diff) | |
download | util-linux-8cb83eee5a58b1fad74c34094ce3afb9e430b5a4.tar.xz util-linux-8cb83eee5a58b1fad74c34094ce3afb9e430b5a4.zip |
Adding upstream version 2.33.1.upstream/2.33.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libmount/src/monitor.c')
-rw-r--r-- | libmount/src/monitor.c | 978 |
1 files changed, 978 insertions, 0 deletions
diff --git a/libmount/src/monitor.c b/libmount/src/monitor.c new file mode 100644 index 0000000..2383a73 --- /dev/null +++ b/libmount/src/monitor.c @@ -0,0 +1,978 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2014-2018 Karel Zak <kzak@redhat.com> + * + * 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. + */ + +/** + * SECTION: monitor + * @title: Monitor + * @short_description: interface to monitor mount tables + * + * For example monitor VFS (/proc/self/mountinfo) for changes: + * + * <informalexample> + * <programlisting> + * const char *filename; + * struct libmount_monitor *mn = mnt_new_monitor(); + * + * mnt_monitor_enable_kernel(mn, TRUE)); + * + * printf("waiting for changes...\n"); + * while (mnt_monitor_wait(mn, -1) > 0) { + * while (mnt_monitor_next_change(mn, &filename, NULL) == 0) + * printf(" %s: change detected\n", filename); + * } + * mnt_unref_monitor(mn); + * </programlisting> + * </informalexample> + * + */ + +#include "fileutils.h" +#include "mountP.h" +#include "pathnames.h" + +#include <sys/inotify.h> +#include <sys/epoll.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; +}; + +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: + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the filesystem. + * + * Returns: newly allocated struct libmnt_monitor. + */ +struct libmnt_monitor *mnt_new_monitor(void) +{ + struct libmnt_monitor *mn = calloc(1, sizeof(*mn)); + if (!mn) + return NULL; + + mn->refcount = 1; + mn->fd = -1; + INIT_LIST_HEAD(&mn->ents); + + DBG(MONITOR, ul_debugobj(mn, "alloc")); + return mn; +} + +/** + * mnt_ref_monitor: + * @mn: monitor pointer + * + * Increments reference counter. + */ +void mnt_ref_monitor(struct libmnt_monitor *mn) +{ + if (mn) + mn->refcount++; +} + +static void free_monitor_entry(struct monitor_entry *me) +{ + if (!me) + return; + list_del(&me->ents); + if (me->fd >= 0) + close(me->fd); + free(me->path); + free(me); +} + +/** + * mnt_unref_monitor: + * @mn: monitor pointer + * + * Decrements the reference counter, on zero the @mn is automatically + * deallocated. + */ +void mnt_unref_monitor(struct libmnt_monitor *mn) +{ + if (!mn) + return; + + mn->refcount--; + if (mn->refcount <= 0) { + mnt_monitor_close_fd(mn); /* destroys all file descriptors */ + + while (!list_empty(&mn->ents)) { + struct monitor_entry *me = list_entry(mn->ents.next, + struct monitor_entry, ents); + free_monitor_entry(me); + } + + free(mn); + } +} + +static struct monitor_entry *monitor_new_entry(struct libmnt_monitor *mn) +{ + struct monitor_entry *me; + + assert(mn); + + me = calloc(1, sizeof(*me)); + if (!me) + return NULL; + INIT_LIST_HEAD(&me->ents); + list_add_tail(&me->ents, &mn->ents); + + me->fd = -1; + + return me; +} + +static int monitor_next_entry(struct libmnt_monitor *mn, + struct libmnt_iter *itr, + struct monitor_entry **me) +{ + int rc = 1; + + assert(mn); + assert(itr); + assert(me); + + *me = NULL; + + if (!itr->head) + MNT_ITER_INIT(itr, &mn->ents); + if (itr->p != itr->head) { + MNT_ITER_ITERATE(itr, *me, struct monitor_entry, ents); + rc = 0; + } + + return rc; +} + +/* returns entry by type */ +static struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int type) +{ + struct libmnt_iter itr; + struct monitor_entry *me; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + while (monitor_next_entry(mn, &itr, &me) == 0) { + if (me->type == type) + return me; + } + return NULL; +} + + +/* + * 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 rename(2) to atomically update utab, monitor + * rename changes is too tricky. It seems better to monitor utab + * lockfile close. + */ + if (asprintf(&filename, "%s.lock", me->path) <= 0) { + rc = -errno; + goto done; + } + + /* try lock file if already exists */ + errno = 0; + wd = inotify_add_watch(me->fd, filename, IN_CLOSE_NOWRITE); + 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 lock 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; + } else 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_NOWRITE) + status = 1; + else { + /* event on lock 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; +} + +/* + * kernel monitor operations + */ +static const struct monitor_opers kernel_opers = { + .op_get_fd = kernel_monitor_get_fd, + .op_close_fd = kernel_monitor_close_fd, +}; + +/** + * 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; +} + +/* + * Add/Remove monitor entry to/from monitor epoll. + */ +static int monitor_modify_epoll(struct libmnt_monitor *mn, + struct monitor_entry *me, int enable) +{ + assert(mn); + assert(me); + + me->enable = enable ? 1 : 0; + me->changed = 0; + + if (mn->fd < 0) + return 0; /* no epoll, ignore request */ + + if (enable) { + struct epoll_event ev = { .events = me->events }; + int fd = me->opers->op_get_fd(mn, me); + + if (fd < 0) + goto err; + + DBG(MONITOR, ul_debugobj(mn, " add fd=%d (for %s)", fd, me->path)); + + ev.data.ptr = (void *) me; + + if (epoll_ctl(mn->fd, EPOLL_CTL_ADD, fd, &ev) < 0) { + if (errno != EEXIST) + goto err; + } + if (me->events & (EPOLLIN | EPOLLET)) { + /* Drain initial events generated for /proc/self/mountinfo */ + struct epoll_event events[1]; + while (epoll_wait(mn->fd, events, 1, 0) > 0); + } + } else if (me->fd) { + DBG(MONITOR, ul_debugobj(mn, " remove fd=%d (for %s)", me->fd, me->path)); + if (epoll_ctl(mn->fd, EPOLL_CTL_DEL, me->fd, NULL) < 0) { + if (errno != ENOENT) + goto err; + } + } + + return 0; +err: + return -errno; +} + +/** + * mnt_monitor_close_fd: + * @mn: monitor + * + * Close monitor file descriptor. This is usually unnecessary, because + * mnt_unref_monitor() cleanups all. + * + * The function is necessary only if you want to reset monitor setting. The + * next mnt_monitor_get_fd() or mnt_monitor_wait() will use newly initialized + * monitor. This restart is unnecessary for mnt_monitor_enable_*() functions. + * + * Returns: 0 on success, <0 on error. + */ +int mnt_monitor_close_fd(struct libmnt_monitor *mn) +{ + struct libmnt_iter itr; + struct monitor_entry *me; + + if (!mn) + return -EINVAL; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + /* disable all monitor entries */ + while (monitor_next_entry(mn, &itr, &me) == 0) { + + /* remove entry from epoll */ + if (mn->fd >= 0) + monitor_modify_epoll(mn, me, FALSE); + + /* close entry FD */ + me->opers->op_close_fd(mn, me); + } + + if (mn->fd >= 0) { + DBG(MONITOR, ul_debugobj(mn, "closing top-level monitor fd")); + close(mn->fd); + } + mn->fd = -1; + return 0; +} + +/** + * mnt_monitor_get_fd: + * @mn: monitor + * + * The file descriptor is associated with all monitored files and it's usable + * for example for epoll. You have to call mnt_monitor_event_cleanup() or + * mnt_monitor_next_change() after each event. + * + * Returns: >=0 (fd) on success, <0 on error + */ +int mnt_monitor_get_fd(struct libmnt_monitor *mn) +{ + struct libmnt_iter itr; + struct monitor_entry *me; + int rc = 0; + + if (!mn) + return -EINVAL; + if (mn->fd >= 0) + return mn->fd; + + DBG(MONITOR, ul_debugobj(mn, "create top-level monitor fd")); + mn->fd = epoll_create1(EPOLL_CLOEXEC); + if (mn->fd < 0) + return -errno; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + DBG(MONITOR, ul_debugobj(mn, "adding monitor entries to epoll (fd=%d)", mn->fd)); + while (monitor_next_entry(mn, &itr, &me) == 0) { + if (!me->enable) + continue; + rc = monitor_modify_epoll(mn, me, TRUE); + if (rc) + goto err; + } + + DBG(MONITOR, ul_debugobj(mn, "successfully created monitor")); + return mn->fd; +err: + rc = errno ? -errno : -EINVAL; + close(mn->fd); + mn->fd = -1; + DBG(MONITOR, ul_debugobj(mn, "failed to create monitor [rc=%d]", rc)); + return rc; +} + +/** + * mnt_monitor_wait: + * @mn: monitor + * @timeout: number of milliseconds, -1 block indefinitely, 0 return immediately + * + * Waits for the next change, after the event it's recommended to use + * mnt_monitor_next_change() to get more details about the change and to + * avoid false positive events. + * + * Returns: 1 success (something changed), 0 timeout, <0 error. + */ +int mnt_monitor_wait(struct libmnt_monitor *mn, int timeout) +{ + int rc; + struct monitor_entry *me; + struct epoll_event events[1]; + + if (!mn) + return -EINVAL; + + if (mn->fd < 0) { + rc = mnt_monitor_get_fd(mn); + if (rc < 0) + return rc; + } + + do { + DBG(MONITOR, ul_debugobj(mn, "calling epoll_wait(), timeout=%d", timeout)); + rc = epoll_wait(mn->fd, events, 1, timeout); + if (rc < 0) + return -errno; /* error */ + if (rc == 0) + return 0; /* timeout */ + + me = (struct monitor_entry *) events[0].data.ptr; + if (!me) + return -EINVAL; + + if (me->opers->op_event_verify == NULL || + me->opers->op_event_verify(mn, me) == 1) { + me->changed = 1; + break; + } + } while (1); + + return 1; /* success */ +} + + +static struct monitor_entry *get_changed(struct libmnt_monitor *mn) +{ + struct libmnt_iter itr; + struct monitor_entry *me; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + while (monitor_next_entry(mn, &itr, &me) == 0) { + if (me->changed) + return me; + } + return NULL; +} + +/** + * mnt_monitor_next_change: + * @mn: monitor + * @filename: returns changed file (optional argument) + * @type: returns MNT_MONITOR_TYPE_* (optional argument) + * + * The function does not wait and it's designed to provide details about changes. + * It's always recommended to use this function to avoid false positives. + * + * Returns: 0 on success, 1 no change, <0 on error + */ +int mnt_monitor_next_change(struct libmnt_monitor *mn, + const char **filename, + int *type) +{ + int rc; + struct monitor_entry *me; + + if (!mn || mn->fd < 0) + return -EINVAL; + + /* + * if we previously called epoll_wait() (e.g. mnt_monitor_wait()) then + * info about unread change is already stored in monitor_entry. + * + * If we get nothing, then ask kernel. + */ + me = get_changed(mn); + while (!me) { + struct epoll_event events[1]; + + DBG(MONITOR, ul_debugobj(mn, "asking for next changed")); + + rc = epoll_wait(mn->fd, events, 1, 0); /* no timeout! */ + if (rc < 0) { + DBG(MONITOR, ul_debugobj(mn, " *** error")); + return -errno; + } + if (rc == 0) { + DBG(MONITOR, ul_debugobj(mn, " *** nothing")); + return 1; + } + + me = (struct monitor_entry *) events[0].data.ptr; + if (!me) + return -EINVAL; + + if (me->opers->op_event_verify != NULL && + me->opers->op_event_verify(mn, me) != 1) + me = NULL; + } + + me->changed = 0; + + if (filename) + *filename = me->path; + if (type) + *type = me->type; + + DBG(MONITOR, ul_debugobj(mn, " *** success [changed: %s]", me->path)); + return 0; /* success */ +} + +/** + * mnt_monitor_event_cleanup: + * @mn: monitor + * + * This function cleanups (drain) internal buffers. It's necessary to call + * this function after event if you do not call mnt_monitor_next_change(). + * + * Returns: 0 on success, <0 on error + */ +int mnt_monitor_event_cleanup(struct libmnt_monitor *mn) +{ + int rc; + + if (!mn || mn->fd < 0) + return -EINVAL; + + while ((rc = mnt_monitor_next_change(mn, NULL, NULL)) == 0); + return rc < 0 ? rc : 0; +} + +#ifdef TEST_PROGRAM + +static struct libmnt_monitor *create_test_monitor(int argc, char *argv[]) +{ + struct libmnt_monitor *mn; + int i; + + mn = mnt_new_monitor(); + if (!mn) { + warn("failed to allocate monitor"); + goto err; + } + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "userspace") == 0) { + if (mnt_monitor_enable_userspace(mn, TRUE, NULL)) { + warn("failed to initialize userspace monitor"); + goto err; + } + + } else if (strcmp(argv[i], "kernel") == 0) { + if (mnt_monitor_enable_kernel(mn, TRUE)) { + warn("failed to initialize kernel monitor"); + goto err; + } + } + } + if (i == 1) { + warnx("No monitor type specified"); + goto err; + } + + return mn; +err: + mnt_unref_monitor(mn); + return NULL; +} + +/* + * create a monitor and add the monitor fd to epoll + */ +static int __test_epoll(struct libmnt_test *ts, int argc, char *argv[], int cleanup) +{ + int fd, efd = -1, rc = -1; + struct epoll_event ev; + struct libmnt_monitor *mn = create_test_monitor(argc, argv); + + if (!mn) + return -1; + + fd = mnt_monitor_get_fd(mn); + if (fd < 0) { + warn("failed to initialize monitor fd"); + goto done; + } + + efd = epoll_create1(EPOLL_CLOEXEC); + if (efd < 0) { + warn("failed to create epoll"); + goto done; + } + + ev.events = EPOLLIN; + ev.data.fd = fd; + + rc = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev); + if (rc < 0) { + warn("failed to add fd to epoll"); + goto done; + } + + printf("waiting for changes...\n"); + do { + const char *filename = NULL; + struct epoll_event events[1]; + int n = epoll_wait(efd, events, 1, -1); + + if (n < 0) { + rc = -errno; + warn("polling error"); + goto done; + } + if (n == 0 || events[0].data.fd != fd) + continue; + + printf(" top-level FD active\n"); + if (cleanup) + mnt_monitor_event_cleanup(mn); + else { + while (mnt_monitor_next_change(mn, &filename, NULL) == 0) + printf(" %s: change detected\n", filename); + } + } while (1); + + rc = 0; +done: + if (efd >= 0) + close(efd); + mnt_unref_monitor(mn); + return rc; +} + +/* + * create a monitor and add the monitor fd to epoll + */ +static int test_epoll(struct libmnt_test *ts, int argc, char *argv[]) +{ + return __test_epoll(ts, argc, argv, 0); +} + +static int test_epoll_cleanup(struct libmnt_test *ts, int argc, char *argv[]) +{ + return __test_epoll(ts, argc, argv, 1); +} + +/* + * create a monitor and wait for a change + */ +static int test_wait(struct libmnt_test *ts, int argc, char *argv[]) +{ + const char *filename; + struct libmnt_monitor *mn = create_test_monitor(argc, argv); + + if (!mn) + return -1; + + printf("waiting for changes...\n"); + while (mnt_monitor_wait(mn, -1) > 0) { + printf("notification detected\n"); + + while (mnt_monitor_next_change(mn, &filename, NULL) == 0) + printf(" %s: change detected\n", filename); + + } + mnt_unref_monitor(mn); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--epoll", test_epoll, "<userspace kernel ...> monitor in epoll" }, + { "--epoll-clean", test_epoll_cleanup, "<userspace kernel ...> monitor in epoll and clean events" }, + { "--wait", test_wait, "<userspace kernel ...> monitor wait function" }, + { NULL } + }; + + return mnt_run_test(tss, argc, argv); +} + +#endif /* TEST_PROGRAM */ |