libmount: add new libmnt_monitor API
It's usually enough to us [e]poll() to monitor kernel mount table, but there is no way how to monitor changes in userspace mount options (e.g. _netdev). The management of these mount options is completely hidden in libmount and /rub/mount/utab is private libmount file. This patch introduces new libmnt_mount API to monitor also userspace mount table. Signed-off-by: Karel Zak <kzak@redhat.com>
This commit is contained in:
parent
109df14fad
commit
372112e908
|
@ -1,6 +1,12 @@
|
|||
#ifndef UTIL_LINUX_FILEUTILS
|
||||
#define UTIL_LINUX_FILEUTILS
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "c.h"
|
||||
|
||||
extern int xmkstemp(char **tmpname, char *dir);
|
||||
|
||||
static inline FILE *xfmkstemp(char **tmpname, char *dir)
|
||||
|
|
|
@ -43,6 +43,7 @@ available from ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
|
|||
<title>Tables management</title>
|
||||
<xi:include href="xml/lock.xml"/>
|
||||
<xi:include href="xml/update.xml"/>
|
||||
<xi:include href="xml/monitor.xml"/>
|
||||
<xi:include href="xml/tabdiff.xml"/>
|
||||
</part>
|
||||
<part>
|
||||
|
|
|
@ -382,3 +382,15 @@ mnt_get_library_version
|
|||
mnt_get_library_features
|
||||
LIBMOUNT_VERSION
|
||||
</SECTION>
|
||||
|
||||
<SECTION>
|
||||
<FILE>monitor</FILE>
|
||||
libmnt_monitor
|
||||
mnt_new_monitor
|
||||
mnt_ref_monitor
|
||||
mnt_unref_monitor
|
||||
mnt_monitor_userspace_get_fd
|
||||
mnt_monitor_get_events
|
||||
mnt_monitor_get_filename
|
||||
mnt_monitor_is_changed
|
||||
</SECTION>
|
||||
|
|
|
@ -30,7 +30,8 @@ libmount_la_SOURCES += \
|
|||
libmount/src/context.c \
|
||||
libmount/src/context_loopdev.c \
|
||||
libmount/src/context_mount.c \
|
||||
libmount/src/context_umount.c
|
||||
libmount/src/context_umount.c \
|
||||
libmount/src/monitor.c
|
||||
endif
|
||||
|
||||
nodist_libmount_la_SOURCES = libmount/src/mountP.h
|
||||
|
@ -73,6 +74,7 @@ check_PROGRAMS += \
|
|||
test_mount_tab_update \
|
||||
test_mount_utils \
|
||||
test_mount_version \
|
||||
test_mount_monitor \
|
||||
test_mount_debug
|
||||
|
||||
libmount_tests_cflags = -DTEST_PROGRAM $(libmount_la_CFLAGS)
|
||||
|
@ -113,6 +115,11 @@ 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_CFLAGS = $(libmount_tests_cflags)
|
||||
test_mount_monitor_LDFLAGS = $(libmount_tests_ldflags)
|
||||
test_mount_monitor_LDADD = $(libmount_tests_ldadd)
|
||||
|
||||
test_mount_tab_update_SOURCES = libmount/src/tab_update.c
|
||||
test_mount_tab_update_CFLAGS = $(libmount_tests_cflags)
|
||||
test_mount_tab_update_LDFLAGS = $(libmount_tests_ldflags)
|
||||
|
|
|
@ -29,6 +29,8 @@ UL_DEBUG_DEFINE_MASKNAMES(libmount) =
|
|||
{ "tab", MNT_DEBUG_TAB, "fstab, mtab, moutninfo routines" },
|
||||
{ "update", MNT_DEBUG_UPDATE, "mtab, utab updates" },
|
||||
{ "utils", MNT_DEBUG_UTILS, "misc library utils" },
|
||||
{ "monitor", MNT_DEBUG_MONITOR, "mount tables monitor" },
|
||||
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
|
|
|
@ -103,6 +103,13 @@ struct libmnt_update;
|
|||
*/
|
||||
struct libmnt_context;
|
||||
|
||||
/**
|
||||
* libmnt_monitor
|
||||
*
|
||||
* Mount tables monitor
|
||||
*/
|
||||
struct libmnt_monitor;
|
||||
|
||||
/**
|
||||
* libmnt_tabdiff:
|
||||
*
|
||||
|
@ -529,6 +536,17 @@ extern int mnt_tabdiff_next_change(struct libmnt_tabdiff *df,
|
|||
struct libmnt_fs **new_fs,
|
||||
int *oper);
|
||||
|
||||
/* minitor.c */
|
||||
extern struct libmnt_monitor *mnt_new_monitor(void);
|
||||
extern void mnt_ref_monitor(struct libmnt_monitor *mn);
|
||||
extern void mnt_unref_monitor(struct libmnt_monitor *mn);
|
||||
|
||||
extern int mnt_monitor_userspace_get_fd(struct libmnt_monitor *mn, const char *filename);
|
||||
extern int mnt_monitor_get_events(struct libmnt_monitor *mn, int fd, unsigned int *event);
|
||||
extern const char *mnt_monitor_get_filename(struct libmnt_monitor *mn, int fd);
|
||||
extern int mnt_monitor_is_changed(struct libmnt_monitor *mn, int fd);
|
||||
|
||||
|
||||
/* context.c */
|
||||
|
||||
/*
|
||||
|
|
|
@ -297,3 +297,13 @@ MOUNT_2.25 {
|
|||
mnt_table_uniq_fs;
|
||||
mnt_tag_is_valid;
|
||||
} MOUNT_2.24;
|
||||
|
||||
MOUNT_2.26 {
|
||||
mnt_monitor_get_events;
|
||||
mnt_monitor_get_filename;
|
||||
mnt_monitor_is_changed;
|
||||
mnt_monitor_userspace_get_fd;
|
||||
mnt_new_monitor;
|
||||
mnt_ref_monitor;
|
||||
mnt_unref_monitor;
|
||||
} MOUNT_2.25;
|
||||
|
|
|
@ -0,0 +1,421 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#include "fileutils.h"
|
||||
#include "mountP.h"
|
||||
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
|
||||
enum {
|
||||
MNT_MONITOR_TYPE_NONE = 0,
|
||||
MNT_MONITOR_TYPE_USERSPACE
|
||||
};
|
||||
|
||||
struct monitor_entry {
|
||||
int fd; /* public file descriptor */
|
||||
char *path; /* path to the monitored file */
|
||||
unsigned int events; /* epoll events or zero */
|
||||
int type; /* MNT_MONITOR_TYPE_* */
|
||||
|
||||
struct list_head ents;
|
||||
};
|
||||
|
||||
struct libmnt_monitor {
|
||||
int refcount;
|
||||
|
||||
struct list_head ents;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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);
|
||||
}
|
||||
|
||||
static void free_monitor(struct libmnt_monitor *mn)
|
||||
{
|
||||
|
||||
while (!list_empty(&mn->ents)) {
|
||||
struct monitor_entry *me = list_entry(mn->ents.next,
|
||||
struct monitor_entry, ents);
|
||||
free_monitor_entry(me);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mnt_unref_monitor:
|
||||
* @mn: monitor pointer
|
||||
*
|
||||
* De-increments reference counter, on zero the @mn is automatically
|
||||
* deallocated.
|
||||
*/
|
||||
void mnt_unref_monitor(struct libmnt_monitor *mn)
|
||||
{
|
||||
if (mn) {
|
||||
mn->refcount--;
|
||||
if (mn->refcount <= 0)
|
||||
free_monitor(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);
|
||||
|
||||
return me;
|
||||
}
|
||||
|
||||
/**
|
||||
* mnt_monitor_userspace_get_fd:
|
||||
* @mn: monitor pointer
|
||||
* @filename: overwrites default
|
||||
*
|
||||
* The kernel mount tables (/proc/mounts and /proc/self/mountinfo) are possible
|
||||
* to monitor by [e]poll(). This function provides the same for userspace mount
|
||||
* table.
|
||||
*
|
||||
* The userspace mount table is originaly /etc/mtab or on systems without mtab
|
||||
* it's private libmount utab file.
|
||||
*
|
||||
* The userspace mount tables are updated by rename(2), this requires that all
|
||||
* dictionary with the mount table is monitored. Be careful on systems with
|
||||
* regular /etc (see mnt_has_regular_mtab()).
|
||||
*
|
||||
* Use mnt_monitor_userspace_get_events() to get epoll events mask (e.g
|
||||
* EPOLLIN, ...).
|
||||
*
|
||||
* Use mnt_monitor_is_changed() to verify that events on the @fd are really
|
||||
* relevant for userspace moutn table.
|
||||
*
|
||||
* If the change is detected then you can use mnt_table_parse_mtab() to parse
|
||||
* the file and mnt_diff_tables() to compare old and new version of the file.
|
||||
*
|
||||
* Returns: <0 on error or file descriptor.
|
||||
*/
|
||||
#ifdef HAVE_INOTIFY_INIT1
|
||||
int mnt_monitor_userspace_get_fd(struct libmnt_monitor *mn, const char *filename)
|
||||
{
|
||||
struct monitor_entry *me;
|
||||
int rc = 0, wd;
|
||||
char *dirname, *sep;
|
||||
|
||||
assert(mn);
|
||||
|
||||
if (!filename) {
|
||||
if (!mnt_has_regular_mtab(&filename, NULL)) /* /etc/mtab */
|
||||
filename = mnt_get_utab_path(); /* /run/mount/utab */
|
||||
if (!filename) {
|
||||
DBG(MONITOR, ul_debugobj(mn, "failed to get userspace mount table path"));
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
DBG(MONITOR, ul_debugobj(mn, "new userspace monitor for %s requested", filename));
|
||||
|
||||
me = monitor_new_entry(mn);
|
||||
if (!me)
|
||||
goto err;
|
||||
|
||||
me->type = MNT_MONITOR_TYPE_USERSPACE;
|
||||
me->path = strdup(filename);
|
||||
if (!me->path)
|
||||
goto err;
|
||||
|
||||
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 (rc && errno != EEXIST)
|
||||
goto err;
|
||||
|
||||
/* initialize inotify stuff */
|
||||
me->fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
||||
if (me->fd < 0)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* libmount uses rename(2) to atomically update utab/mtab, the finame
|
||||
* change is possible to detect by IN_MOVE_TO inotify event.
|
||||
*/
|
||||
wd = inotify_add_watch(me->fd, dirname, IN_MOVED_TO);
|
||||
if (wd < 0)
|
||||
goto err;
|
||||
|
||||
if (sep && sep > dirname)
|
||||
*(sep - 1) = '/'; /* set '/' back to the path */
|
||||
|
||||
me->events = EPOLLIN | EPOLLPRI;
|
||||
|
||||
DBG(MONITOR, ul_debugobj(mn, "new fd=%d", me->fd));
|
||||
return me->fd;
|
||||
err:
|
||||
rc = -errno;
|
||||
free_monitor_entry(me);
|
||||
return rc;
|
||||
}
|
||||
#else /* HAVE_INOTIFY_INIT1 */
|
||||
int mnt_monitor_userspace_get_fd(struct libmnt_monitor *mn __attribute__((unused)),
|
||||
const char *filename __attribute__((unused)))
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct monitor_entry *get_monitor_entry(struct libmnt_monitor *mn, int fd)
|
||||
{
|
||||
struct list_head *p;
|
||||
|
||||
assert(mn);
|
||||
|
||||
if (fd < 0)
|
||||
return NULL;
|
||||
|
||||
list_for_each(p, &mn->ents) {
|
||||
struct monitor_entry *me;
|
||||
|
||||
me = list_entry(p, struct monitor_entry, ents);
|
||||
if (me->fd == fd)
|
||||
return me;
|
||||
}
|
||||
|
||||
DBG(MONITOR, ul_debugobj(mn, "failed to get entry for fd=%d", fd));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* mnt_monitor_get_events:
|
||||
* @mn: monitor
|
||||
* @fd: event file descriptor
|
||||
* @event: returns epoll event mask
|
||||
*
|
||||
* Returns: on on success, <0 on error.
|
||||
*/
|
||||
int mnt_monitor_get_events(struct libmnt_monitor *mn, int fd, unsigned int *event)
|
||||
{
|
||||
struct monitor_entry *me = get_monitor_entry(mn, fd);
|
||||
|
||||
if (!me || !event)
|
||||
return -EINVAL;
|
||||
*event = me->events;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mnt_monitor_get_filename:
|
||||
* @mn: monitor
|
||||
* @fd: event file descriptor
|
||||
*
|
||||
* Returns: filename monitored by @fd or NULL on error.
|
||||
*/
|
||||
const char *mnt_monitor_get_filename(struct libmnt_monitor *mn, int fd)
|
||||
{
|
||||
struct monitor_entry *me = get_monitor_entry(mn, fd);
|
||||
|
||||
if (!me)
|
||||
return NULL;
|
||||
return me->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* mnt_monitor_is_changed:
|
||||
* @mn: monitor
|
||||
* @fd: event file descriptor
|
||||
*
|
||||
* Returns: 1 of the file monitored by @fd has been changed
|
||||
*/
|
||||
int mnt_monitor_is_changed(struct libmnt_monitor *mn, int fd)
|
||||
{
|
||||
struct monitor_entry *me = get_monitor_entry(mn, fd);
|
||||
int rc = 0;
|
||||
|
||||
if (!me)
|
||||
return 0;
|
||||
|
||||
|
||||
switch (me->type) {
|
||||
#ifdef HAVE_INOTIFY_INIT1
|
||||
case MNT_MONITOR_TYPE_USERSPACE:
|
||||
{
|
||||
char wanted[NAME_MAX + 1];
|
||||
char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
|
||||
struct inotify_event *event;
|
||||
char *p;
|
||||
ssize_t r;
|
||||
|
||||
DBG(MONITOR, ul_debugobj(mn, "checking fd=%d for userspace changes", me->fd));
|
||||
|
||||
p = strrchr(me->path, '/');
|
||||
if (!p)
|
||||
p = me->path;
|
||||
else
|
||||
p++;
|
||||
strncpy(wanted, p, sizeof(wanted) - 1);
|
||||
wanted[sizeof(wanted) - 1] = '\0';
|
||||
rc = 0;
|
||||
|
||||
DBG(MONITOR, ul_debugobj(mn, "wanted file: '%s'", wanted));
|
||||
|
||||
while ((r = read(me->fd, buf, sizeof(buf))) > 0) {
|
||||
for (p = buf; p < buf + r; ) {
|
||||
event = (struct inotify_event *) p;
|
||||
|
||||
if (strcmp(event->name, wanted) == 0)
|
||||
rc = 1;
|
||||
p += sizeof(struct inotify_event) + event->len;
|
||||
}
|
||||
if (rc)
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
DBG(MONITOR, ul_debugobj(mn, "fd=%d %s", me->fd, rc ? "changed" : "unchanged"));
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
#ifdef TEST_PROGRAM
|
||||
|
||||
int test_monitor(struct libmnt_test *ts, int argc, char *argv[])
|
||||
{
|
||||
struct libmnt_monitor *mn;
|
||||
int fd, efd = -1, rc = -1;
|
||||
struct epoll_event ev = { .events = 0 };
|
||||
|
||||
mn = mnt_new_monitor();
|
||||
if (!mn) {
|
||||
warn("failed to allocate monitor");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* monitor userspace mount table changes */
|
||||
fd = mnt_monitor_userspace_get_fd(mn, NULL);
|
||||
if (fd < 0) {
|
||||
warn("failed to initialize userspace mount table fd");
|
||||
goto done;
|
||||
}
|
||||
|
||||
efd = epoll_create1(EPOLL_CLOEXEC);
|
||||
if (efd < 0) {
|
||||
warn("failed to create epoll");
|
||||
goto done;
|
||||
}
|
||||
|
||||
mnt_monitor_get_events(mn, fd, &ev.events);
|
||||
|
||||
/* set data is necessary only if you want to use epoll for more file
|
||||
* descriptors, then epoll_wait() returns data associated with the file
|
||||
* descriptor. */
|
||||
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 {
|
||||
struct epoll_event events[1];
|
||||
int n, nfds = epoll_wait(efd, events, 1, -1);
|
||||
|
||||
if (nfds < 0) {
|
||||
rc = -errno;
|
||||
warn("polling error");
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (n = 0; n < nfds; n++) {
|
||||
if (events[n].data.fd == fd &&
|
||||
mnt_monitor_is_changed(mn, fd) == 1)
|
||||
printf("%s: change detected\n",
|
||||
mnt_monitor_get_filename(mn, fd));
|
||||
}
|
||||
} while (1);
|
||||
|
||||
rc = 0;
|
||||
done:
|
||||
printf("done");
|
||||
mnt_unref_monitor(mn);
|
||||
if (efd >= 0)
|
||||
close(efd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct libmnt_test tss[] = {
|
||||
{ "--monitor", test_monitor, "print change" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
return mnt_run_test(tss, argc, argv);
|
||||
}
|
||||
|
||||
#endif /* TEST_PROGRAM */
|
|
@ -48,6 +48,8 @@
|
|||
#define MNT_DEBUG_UTILS (1 << 8)
|
||||
#define MNT_DEBUG_CXT (1 << 9)
|
||||
#define MNT_DEBUG_DIFF (1 << 10)
|
||||
#define MNT_DEBUG_MONITOR (1 << 11)
|
||||
|
||||
#define MNT_DEBUG_ALL 0xFFFF
|
||||
|
||||
UL_DEBUG_DECLARE_MASK(libmount);
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
/**
|
||||
* SECTION: tabdiff
|
||||
* @title: Monitor mountinfo changes
|
||||
* @short_description: monitor changes in the list of the mounted filesystems
|
||||
* @title: Compare changes in mount tables
|
||||
* @short_description: compare changes in the list of the mounted filesystems
|
||||
*/
|
||||
#include "mountP.h"
|
||||
|
||||
|
|
Loading…
Reference in New Issue