| /* |
| * Copyright (C) 2014 Karel Zak <kzak@redhat.com> |
| * |
| * This file may be redistributed under the terms of the |
| * GNU Lesser General Public License. |
| */ |
| |
| /** |
| * 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 */ |