diff options
author | Karel Zak | 2015-06-19 12:17:45 +0200 |
---|---|---|
committer | Karel Zak | 2015-06-19 12:30:40 +0200 |
commit | 7dc0f5c90ee9899a1d92de77ad3d8af998198379 (patch) | |
tree | f5dde135d91100e511de6890b6067f23f678d113 /libmount/src/monitor.c | |
parent | libuuid: return correct value for uuid_generate_time_safe (diff) | |
download | kernel-qcow2-util-linux-7dc0f5c90ee9899a1d92de77ad3d8af998198379.tar.gz kernel-qcow2-util-linux-7dc0f5c90ee9899a1d92de77ad3d8af998198379.tar.xz kernel-qcow2-util-linux-7dc0f5c90ee9899a1d92de77ad3d8af998198379.zip |
libmount: improve monitor to be usable for non-root users
The current implementation calls mkdir and open(O_CREATE) to
initialize /run/mount/utab.lock before it starts to monitor the file.
Unfortunately it makes the monitor useless for non-root processes
(e.g. systemd --user).
The new implementation adds inotify watch for the last existing
component in the path (/run/mount/utab.lock) and re-initialize
after a change. It makes the monitor robust enough for mkdir/rmdir
when monitor is already active.
Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'libmount/src/monitor.c')
-rw-r--r-- | libmount/src/monitor.c | 224 |
1 files changed, 142 insertions, 82 deletions
diff --git a/libmount/src/monitor.c b/libmount/src/monitor.c index 05cce5986..c33e7d3c6 100644 --- a/libmount/src/monitor.c +++ b/libmount/src/monitor.c @@ -21,8 +21,6 @@ * * 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); * } @@ -66,7 +64,7 @@ struct libmnt_monitor { struct monitor_opers { int (*op_get_fd)(struct libmnt_monitor *, struct monitor_entry *); int (*op_close_fd)(struct libmnt_monitor *, struct monitor_entry *); - void (*op_event_cleanup)(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, @@ -216,12 +214,68 @@ static int userspace_monitor_close_fd(struct libmnt_monitor *mn, 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/mtab, 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 wd, rc, dummy_fd; - char *dirname, *sep; - char *filename = NULL; + int rc; assert(mn); assert(me); @@ -234,51 +288,17 @@ static int userspace_monitor_get_fd(struct libmnt_monitor *mn, assert(me->path); DBG(MONITOR, ul_debugobj(mn, " open userspace monitor for %s", me->path)); - dirname = me->path; - sep = stripoff_last_component(dirname); /* add \0 between dir/filename */ - - /* make sure the directory exists */ - rc = mkdir(dirname, S_IWUSR| - S_IRUSR|S_IRGRP|S_IROTH| - S_IXUSR|S_IXGRP|S_IXOTH); - - if (sep && sep > dirname) - *(sep - 1) = '/'; /* set '/' back to the path */ - - if (rc && errno != EEXIST) - goto err; - - /* - * libmount uses rename(2) to atomically update utab/mtab, monitor - * rename changes is too tricky. It seems better to monitor utab - * lockfile close. - */ - if (asprintf(&filename, "%s.lock", me->path) <= 0) - goto err; - - /* make sure the file exists */ - dummy_fd = open(filename, O_RDONLY|O_CREAT|O_CLOEXEC, - S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH); - if (dummy_fd < 0) - goto err; - close(dummy_fd); - - /* initialize inotify stuff */ me->fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); if (me->fd < 0) goto err; - - wd = inotify_add_watch(me->fd, filename, IN_CLOSE_NOWRITE); - if (wd < 0) + if (userspace_add_watch(me, NULL, NULL) < 0) goto err; - free(filename); return me->fd; err: rc = -errno; - free(filename); - if (me->fd) + if (me->fd >= 0) close(me->fd); me->fd = -1; DBG(MONITOR, ul_debugobj(mn, "failed to create userspace monitor [rc=%d]", rc)); @@ -286,20 +306,53 @@ err: } /* - * drain inotify buffer + * verify and drain inotify buffer */ -static void userspace_event_cleanup(struct libmnt_monitor *mn, +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; + return 0; - DBG(MONITOR, ul_debugobj(mn, "drain userspace monitor inotify")); + DBG(MONITOR, ul_debugobj(mn, "drain and verify userspace monitor inotify")); /* the 'fd' is non-blocking */ - while (read(me->fd, buf, sizeof(buf)) > 0); + 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; + + 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, status == 1 ? " success" : " nothing")); + return status; } /* @@ -308,7 +361,7 @@ static void userspace_event_cleanup(struct libmnt_monitor *mn, static const struct monitor_opers userspace_opers = { .op_get_fd = userspace_monitor_get_fd, .op_close_fd = userspace_monitor_close_fd, - .op_event_cleanup = userspace_event_cleanup + .op_event_verify = userspace_event_verify }; /** @@ -519,6 +572,9 @@ static int monitor_modify_epoll(struct libmnt_monitor *mn, 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; @@ -640,8 +696,8 @@ err: * @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 or - * at least mnt_monitor_event_cleanup(). + * 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. */ @@ -660,20 +716,24 @@ int mnt_monitor_wait(struct libmnt_monitor *mn, int timeout) return rc; } - 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 */ + 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; - me->changed = 1; + me = (struct monitor_entry *) events[0].data.ptr; + if (!me) + return -EINVAL; - if (me->opers->op_event_cleanup != NULL) - me->opers->op_event_cleanup(mn, me); + if (me->opers->op_event_verify == NULL || + me->opers->op_event_verify(mn, me) == 1) { + me->changed = 1; + break; + } + } while (1); return 1; /* success */ } @@ -699,6 +759,7 @@ static struct monitor_entry *get_changed(struct libmnt_monitor *mn) * @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 */ @@ -719,29 +780,28 @@ int mnt_monitor_next_change(struct libmnt_monitor *mn, * If we get nothing, then ask kernel. */ me = get_changed(mn); - if (!me) { - do { - struct epoll_event events[1]; + while (!me) { + struct epoll_event events[1]; - DBG(MONITOR, ul_debugobj(mn, "asking for next changed")); + 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; - } + 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_cleanup != NULL) - me->opers->op_event_cleanup(mn, me); - break; - } while (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; |