/*
* 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 */
int type; /* MNT_MONITOR_TYPE_* */
unsigned int enable : 1;
struct list_head ents;
};
struct libmnt_monitor {
int refcount;
struct list_head ents;
};
static int monitor_enable_entry(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;
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
*
* De-increments 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) {
while (!list_empty(&mn->ents)) {
struct monitor_entry *me = list_entry(mn->ents.next,
struct monitor_entry, ents);
free_monitor_entry(me);
}
}
}
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 struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int type)
{
struct list_head *p;
assert(mn);
assert(type);
list_for_each(p, &mn->ents) {
struct monitor_entry *me;
me = list_entry(p, struct monitor_entry, ents);
if (me->type == type)
return me;
}
return NULL;
}
static struct monitor_entry *monitor_get_entry_by_fd(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_enable_userspace:
* @mn: monitor
* @enable: 0 or 1
* @filename: overwrites default
*
* Enables or disables userspace monitor. If the monitor does not exist and
* enable=1 then allocates new resources necessary for the monitor.
*
* If high-level monitor has been already initialized (by mnt_monitor_get_fd()
* or mnt_wait_monitor()) then it's updated according to @enable.
*
* The @filename is used only first time when you enable the monitor. It's
* impossible to have more than one userspace monitor.
*
* Note that the current implementation of the userspace monitor is based on
* inotify. On systems (libc) without inotify_init1() the function return
* -ENOSYS. The dependence on inotify is implemenation specific and may be
* changed later.
*
* 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)
return monitor_enable_entry(mn, me, enable);
if (!enable)
return 0;
DBG(MONITOR, ul_debugobj(mn, "allocate new userspace monitor"));
/* create a new entry */
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;
}
me = monitor_new_entry(mn);
if (!me)
goto err;
me->type = MNT_MONITOR_TYPE_USERSPACE;
me->path = strdup(filename);
if (!me->path)
goto err;
DBG(MONITOR, ul_debugobj(mn, "allocate new userspace monitor: OK"));
return monitor_enable_entry(mn, me, 1);
err:
rc = -errno;
free_monitor_entry(me);
return rc;
}
/**
* mnt_monitor_userspace_get_fd:
* @mn: monitor pointer
*
* Returns: file descriptor to previously enabled userspace monitor or <0 on error.
*/
#ifdef HAVE_INOTIFY_INIT1
int mnt_monitor_userspace_get_fd(struct libmnt_monitor *mn)
{
struct monitor_entry *me;
int wd, rc;
char *dirname, *sep;
assert(mn);
me = monitor_get_entry(mn, MNT_MONITOR_TYPE_USERSPACE);
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));
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 */
DBG(MONITOR, ul_debugobj(mn, "new fd=%d", me->fd));
return me->fd;
err:
return -errno;
}
static int monitor_userspace_is_changed(struct libmnt_monitor *mn,
struct monitor_entry *me)
{
char wanted[NAME_MAX + 1];
char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
struct inotify_event *event;
char *p;
ssize_t r;
int rc = 0;
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;
}
return rc;
}
#else /* HAVE_INOTIFY_INIT1 */
int mnt_monitor_enable_userspace(
struct libmnt_monitor *mn __attribute__((unused)),
int enable __attribute__((unused)),
const char *filename __attribute__((unused)))
{
return -ENOSYS;
}
int mnt_monitor_userspace_get_fd(
struct libmnt_monitor *mn __attribute__((unused)))
{
return -ENOSYS;
}
#endif
static int monitor_enable_entry(struct libmnt_monitor *mn,
struct monitor_entry *me, int enable)
{
assert(mn);
assert(me);
me->enable = enable ? 1 : 0;
/* TODO : remove / add me->fd to high-level*/
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 = monitor_get_entry_by_fd(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 = monitor_get_entry_by_fd(mn, fd);
int rc = 0;
if (!me)
return 0;
switch (me->type) {
case MNT_MONITOR_TYPE_USERSPACE:
rc = monitor_userspace_is_changed(mn, me);
break;
default:
return 0;
}
DBG(MONITOR, ul_debugobj(mn, "fd=%d %s", me->fd, rc ? "changed" : "unchanged"));
return rc;
}
#ifdef TEST_PROGRAM
static int my_epoll(struct libmnt_monitor *mn, int fd)
{
int efd = -1, rc = -1;
struct epoll_event ev = { .events = 0 };
assert(mn);
assert(fd >= 0);
efd = epoll_create1(EPOLL_CLOEXEC);
if (efd < 0) {
warn("failed to create epoll");
goto done;
}
ev.events = EPOLLPRI | 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 {
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:
if (efd >= 0)
close(efd);
return rc;
}
int test_low_user(struct libmnt_test *ts, int argc, char *argv[])
{
struct libmnt_monitor *mn;
int fd, rc = -1;
mn = mnt_new_monitor();
if (!mn) {
warn("failed to allocate monitor");
goto done;
}
if (mnt_monitor_enable_userspace(mn, TRUE, NULL)) {
warn("failed to initialize userspace monitor");
goto done;
}
fd = mnt_monitor_userspace_get_fd(mn);
if (fd < 0) {
warn("failed to initialize userspace monitor fd");
goto done;
}
rc = my_epoll(mn, fd);
done:
printf("done");
mnt_unref_monitor(mn);
return rc;
}
int main(int argc, char *argv[])
{
struct libmnt_test tss[] = {
{ "--low-userspace", test_low_user, "tests low-level userspace monitor" },
{ NULL }
};
return mnt_run_test(tss, argc, argv);
}
#endif /* TEST_PROGRAM */