]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
mount-util: introduce umountat_detach_verbose()
authorLennart Poettering <lennart@poettering.net>
Thu, 16 Jan 2025 09:17:44 +0000 (10:17 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 20 Jan 2025 10:31:55 +0000 (11:31 +0100)
This new helper allows unmounting paths by fd.

src/shared/mount-util.c
src/shared/mount-util.h
src/test/test-mount-util.c

index 39aa3d3ec825621e2ab1b91d42708fd3ad7c792a..a9ce8d7e5aead1f3b9742cc6f5c96f41fd93de92 100644 (file)
@@ -736,16 +736,62 @@ int mount_verbose_full(
 
 int umount_verbose(
                 int error_log_level,
-                const char *what,
+                const char *where,
                 int flags) {
 
-        assert(what);
+        assert(where);
+
+        log_debug("Unmounting '%s'...", where);
+
+        if (umount2(where, flags) < 0)
+                return log_full_errno(error_log_level, errno, "Failed to unmount '%s': %m", where);
+
+        return 0;
+}
+
+int umountat_detach_verbose(
+                int error_log_level,
+                int fd,
+                const char *where) {
 
-        log_debug("Umounting %s...", what);
+        /* Similar to umountat_verbose(), but goes by fd + path. This implies MNT_DETACH, since to do this we
+         * must pin the inode in question via an fd. */
+
+        assert(fd >= 0 || fd == AT_FDCWD);
+
+        /* If neither fd nor path are specified take this as reference to the cwd */
+        if (fd == AT_FDCWD && isempty(where))
+                return umount_verbose(error_log_level, ".", MNT_DETACH|UMOUNT_NOFOLLOW);
+
+        /* If we don't actually take the fd into consideration for this operation shortcut things, so that we
+         * don't have to open the inode */
+        if (fd == AT_FDCWD || path_is_absolute(where))
+                return umount_verbose(error_log_level, where, MNT_DETACH|UMOUNT_NOFOLLOW);
+
+        _cleanup_free_ char *prefix = NULL;
+        const char *p;
+        if (fd_get_path(fd, &prefix) < 0)
+                p = "<fd>"; /* if we can't get the path, return something vaguely useful */
+        else
+                p = prefix;
+        _cleanup_free_ char *joined = isempty(where) ? strdup(p) : path_join(p, where);
+
+        log_debug("Unmounting '%s'...", strna(joined));
+
+        _cleanup_close_ int inode_fd = -EBADF;
+        int mnt_fd;
+        if (isempty(where))
+                mnt_fd = fd;
+        else {
+                inode_fd = openat(fd, where, O_PATH|O_CLOEXEC|O_NOFOLLOW);
+                if (inode_fd < 0)
+                        return log_full_errno(error_log_level, errno, "Failed to pin '%s': %m", strna(joined));
+
+                mnt_fd = inode_fd;
+        }
 
-        if (umount2(what, flags) < 0)
-                return log_full_errno(error_log_level, errno,
-                                      "Failed to unmount %s: %m", what);
+        if (umount2(FORMAT_PROC_FD_PATH(mnt_fd), MNT_DETACH) < 0)
+                return log_full_errno(error_log_level, errno, "Failed to unmount '%s': %m", strna(joined));
 
         return 0;
 }
index 496a95ab050eeee7b9a498a7d0ad3f335628cbdc..3235a3cc81f35688733e9e5fd9cc15bdf1d4e50a 100644 (file)
@@ -79,6 +79,11 @@ int umount_verbose(
                 const char *where,
                 int flags);
 
+int umountat_detach_verbose(
+                int error_log_level,
+                int fd,
+                const char *where);
+
 int mount_option_mangle(
                 const char *options,
                 unsigned long mount_flags,
index 4ac8f869d6614d259c7a6f3e86966e09ee808100..0d7e803b5437b706c80c1e3ca0e5c201107830b2 100644 (file)
@@ -587,4 +587,24 @@ TEST(path_is_network_fs_harder) {
         }
 }
 
+TEST(umountat) {
+        int r;
+
+        _cleanup_(rm_rf_physical_and_freep) char *p = NULL;
+        _cleanup_close_ int dfd = mkdtemp_open(NULL, O_CLOEXEC, &p);
+        ASSERT_OK(dfd);
+
+        ASSERT_OK(mkdirat(dfd, "foo", 0777));
+
+        _cleanup_free_ char *q = ASSERT_PTR(path_join(p, "foo"));
+
+        r = mount_nofollow_verbose(LOG_ERR, "tmpfs", q, "tmpfs", 0, NULL);
+        if (ERRNO_IS_NEG_PRIVILEGE(r))
+                return (void) log_tests_skipped("not running privileged");
+
+        ASSERT_OK(r);
+        ASSERT_OK(umountat_detach_verbose(LOG_ERR, dfd, "foo"));
+        ASSERT_ERROR(umountat_detach_verbose(LOG_ERR, dfd, "foo"), EINVAL);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);