]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libmount: split monitor code to more files
authorKarel Zak <kzak@redhat.com>
Wed, 21 May 2025 09:43:57 +0000 (11:43 +0200)
committerKarel Zak <kzak@redhat.com>
Wed, 6 Aug 2025 12:50:55 +0000 (14:50 +0200)
Signed-off-by: Karel Zak <kzak@redhat.com>
libmount/meson.build
libmount/src/Makemodule.am
libmount/src/monitor.c
libmount/src/monitor.h [new file with mode: 0644]
libmount/src/monitor_mountinfo.c [new file with mode: 0644]
libmount/src/monitor_utab.c [new file with mode: 0644]

index 29a43be029f6c453489688a04ce30c455fb9832d..f30a7fdd8a2bc439ee19b11864be5afe96e55a5c 100644 (file)
@@ -47,6 +47,9 @@ if LINUX
   lib_mount_sources += '''
     src/hooks.c
     src/monitor.c
+    src/monitor.h
+    src/monitor_utab.c
+    src/monitor_mountinfo.c
     src/optlist.c
     src/hook_veritydev.c
     src/hook_subdir.c
index 5a5c787a4c22a745a60121e8b9a49ee20394d6c7..5875062f2430260b50a8138e4dcb16120e0c42c6 100644 (file)
@@ -42,7 +42,10 @@ libmount_la_SOURCES += \
        libmount/src/hook_idmap.c \
        libmount/src/hook_loopdev.c \
        libmount/src/hook_veritydev.c \
-       libmount/src/monitor.c
+       libmount/src/monitor.c \
+       libmount/src/monitor.h \
+       libmount/src/monitor_utab.c \
+       libmount/src/monitor_mountinfo.c
 
 if HAVE_BTRFS
 libmount_la_SOURCES += libmount/src/btrfs.c
@@ -159,7 +162,10 @@ 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_SOURCES = libmount/src/monitor.c \
+                            libmount/src/monitor.h \
+                            libmount/src/monitor_utab.c \
+                            libmount/src/monitor_mountinfo.c
 test_mount_monitor_CFLAGS = $(libmount_tests_cflags)
 test_mount_monitor_LDFLAGS = $(libmount_tests_ldflags)
 test_mount_monitor_LDADD = $(libmount_tests_ldadd)
index 941f5d5b8ef41196705334c5d2705bf431a8c199..fb68d0d67aaccac7c30b73562fbd9dd76be1ed49 100644 (file)
  *
  */
 
-#include "fileutils.h"
 #include "mountP.h"
-#include "pathnames.h"
+#include "monitor.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;
-
-       unsigned int            kernel_veiled: 1;
-};
-
-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:
  *
@@ -111,7 +74,7 @@ void mnt_ref_monitor(struct libmnt_monitor *mn)
                mn->refcount++;
 }
 
-static void free_monitor_entry(struct monitor_entry *me)
+void free_monitor_entry(struct monitor_entry *me)
 {
        if (!me)
                return;
@@ -148,7 +111,7 @@ void mnt_unref_monitor(struct libmnt_monitor *mn)
        }
 }
 
-static struct monitor_entry *monitor_new_entry(struct libmnt_monitor *mn)
+struct monitor_entry *monitor_new_entry(struct libmnt_monitor *mn)
 {
        struct monitor_entry *me;
 
@@ -190,7 +153,7 @@ static int monitor_next_entry(struct libmnt_monitor *mn,
 }
 
 /* returns entry by type */
-static struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int type)
+struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int type)
 {
        struct libmnt_iter itr;
        struct monitor_entry *me;
@@ -204,391 +167,10 @@ static struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int ty
 }
 
 
-/*
- * 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 utab.event file to monitor and control utab updates
-        */
-       if (asprintf(&filename, "%s.event", me->path) <= 0) {
-               rc = -ENOMEM;
-               goto done;
-       }
-
-       /* try event file if already exists */
-       errno = 0;
-       wd = inotify_add_watch(me->fd, filename, IN_CLOSE_WRITE);
-       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 event 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;
-               }
-
-               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_WRITE)
-                               status = 1;
-                       else {
-                               /* add watch for the event 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;
-}
-
-static int kernel_event_verify(struct libmnt_monitor *mn,
-                              struct monitor_entry *me)
-{
-       int status = 1;
-
-       if (!mn || !me || me->fd < 0)
-               return 0;
-
-       if (mn->kernel_veiled && access(MNT_PATH_UTAB ".act", F_OK) == 0) {
-               status = 0;
-               DBG(MONITOR, ul_debugobj(mn, "kernel event veiled"));
-       }
-       return status;
-}
-
-/*
- * kernel monitor operations
- */
-static const struct monitor_opers kernel_opers = {
-       .op_get_fd              = kernel_monitor_get_fd,
-       .op_close_fd            = kernel_monitor_close_fd,
-       .op_event_verify        = kernel_event_verify
-};
-
-/**
- * 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;
-}
-
-/**
- * mnt_monitor_veil_kernel:
- * @mn: monitor instance
- * @enable: 1 or 0
- *
- * Force monitor to ignore kernel events if the same mount/umount operation
- * will generate an userspace event later. The kernel-only mount operation will
- * be not affected.
- *
- * Return: 0 on success and <0 on error.
- *
- * Since: 2.40
- */
-int mnt_monitor_veil_kernel(struct libmnt_monitor *mn, int enable)
-{
-       if (!mn)
-               return -EINVAL;
-
-       mn->kernel_veiled = enable ? 1 : 0;
-       return 0;
-}
-
 /*
  * Add/Remove monitor entry to/from monitor epoll.
  */
-static int monitor_modify_epoll(struct libmnt_monitor *mn,
+int monitor_modify_epoll(struct libmnt_monitor *mn,
                                struct monitor_entry *me, int enable)
 {
        assert(mn);
diff --git a/libmount/src/monitor.h b/libmount/src/monitor.h
new file mode 100644 (file)
index 0000000..a72658e
--- /dev/null
@@ -0,0 +1,43 @@
+
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef _MNTMONITOR_PRIVATE_H
+#define _MNTMONITOR_PRIVATE_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;
+
+       unsigned int            kernel_veiled: 1;
+};
+
+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 *);
+};
+
+int monitor_modify_epoll(struct libmnt_monitor *mn, struct monitor_entry *me, int enable);
+struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int type);
+struct monitor_entry *monitor_new_entry(struct libmnt_monitor *mn);
+void free_monitor_entry(struct monitor_entry *me);
+
+#endif /* _MNTMONITOR_PRIVATE_H */
diff --git a/libmount/src/monitor_mountinfo.c b/libmount/src/monitor_mountinfo.c
new file mode 100644 (file)
index 0000000..56171fd
--- /dev/null
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2014-2025 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.
+ *
+ * libmount kernel mount table monitor (based on /proc/self/mountinfo epoll)
+ */
+
+#include "mountP.h"
+#include "monitor.h"
+
+#include "pathnames.h"
+
+#include <sys/epoll.h>
+
+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;
+}
+
+static int kernel_event_verify(struct libmnt_monitor *mn,
+                              struct monitor_entry *me)
+{
+       int status = 1;
+
+       if (!mn || !me || me->fd < 0)
+               return 0;
+
+       if (mn->kernel_veiled && access(MNT_PATH_UTAB ".act", F_OK) == 0) {
+               status = 0;
+               DBG(MONITOR, ul_debugobj(mn, "kernel event veiled"));
+       }
+       return status;
+}
+
+/*
+ * kernel monitor operations
+ */
+static const struct monitor_opers kernel_opers = {
+       .op_get_fd              = kernel_monitor_get_fd,
+       .op_close_fd            = kernel_monitor_close_fd,
+       .op_event_verify        = kernel_event_verify
+};
+
+/**
+ * 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;
+}
+
+/**
+ * mnt_monitor_veil_kernel:
+ * @mn: monitor instance
+ * @enable: 1 or 0
+ *
+ * Force monitor to ignore kernel events if the same mount/umount operation
+ * will generate an userspace event later. The kernel-only mount operation will
+ * be not affected.
+ *
+ * Return: 0 on success and <0 on error.
+ *
+ * Since: 2.40
+ */
+int mnt_monitor_veil_kernel(struct libmnt_monitor *mn, int enable)
+{
+       if (!mn)
+               return -EINVAL;
+
+       mn->kernel_veiled = enable ? 1 : 0;
+       return 0;
+}
diff --git a/libmount/src/monitor_utab.c b/libmount/src/monitor_utab.c
new file mode 100644 (file)
index 0000000..d7261a9
--- /dev/null
@@ -0,0 +1,248 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2014-2025 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.
+ *
+ *
+ * libmount userspace monitor (based on /run/mount/utab inotify)
+ */
+
+#include "mountP.h"
+#include "monitor.h"
+
+#include "fileutils.h"
+
+#include <sys/inotify.h>
+#include <sys/epoll.h>
+
+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 utab.event file to monitor and control utab updates
+        */
+       if (asprintf(&filename, "%s.event", me->path) <= 0) {
+               rc = -ENOMEM;
+               goto done;
+       }
+
+       /* try event file if already exists */
+       errno = 0;
+       wd = inotify_add_watch(me->fd, filename, IN_CLOSE_WRITE);
+       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 event 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;
+               }
+
+               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_WRITE)
+                               status = 1;
+                       else {
+                               /* add watch for the event 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;
+}