]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homed: always use quotactl_fd() if its available
authorLennart Poettering <lennart@poettering.net>
Mon, 31 Mar 2025 09:47:17 +0000 (11:47 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 31 Mar 2025 09:51:15 +0000 (11:51 +0200)
Let's always prefer quotactl_fd() when it's available and use quotactl()
only as as a fallback on old kernels.

This way we can operate on the fds we typically already have open, or if
needed we can open a new one, and use for multiple fs operation.

In the long run we should really focus on operating exclusively by fd
instead of by path, by device nor or otherwise. This gets us a step
closer to that.

src/home/homed-home.c
src/home/homed-manager.c
src/home/homework-directory.c
src/home/homework-fscrypt.c
src/home/homework-quota.c
src/home/homework-quota.h
src/shared/quota-util.c
src/shared/quota-util.h
units/systemd-homed.service.in

index 71ec0ba598c4d51574ed5d0eec976fd15277788c..67854723510f260f4cfd030d25424f1beff7001b 100644 (file)
@@ -25,7 +25,6 @@
 #include "memfd-util.h"
 #include "missing_magic.h"
 #include "missing_mman.h"
-#include "missing_syscall.h"
 #include "mkdir.h"
 #include "path-util.h"
 #include "process-util.h"
@@ -2419,6 +2418,7 @@ static int home_get_disk_status_directory(
         uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX,
                 disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX;
         mode_t access_mode = MODE_INVALID;
+        _cleanup_close_ int fd = -EBADF;
         statfs_f_type_t fstype = 0;
         struct statfs sfs;
         struct dqblk req;
@@ -2440,7 +2440,13 @@ static int home_get_disk_status_directory(
         if (!path)
                 goto finish;
 
-        if (statfs(path, &sfs) < 0)
+        fd = open(path, O_CLOEXEC|O_RDONLY);
+        if (fd < 0) {
+                log_debug_errno(errno, "Failed to open '%s', ignoring: %m", path);
+                goto finish;
+        }
+
+        if (fstatfs(fd, &sfs) < 0)
                 log_debug_errno(errno, "Failed to statfs() %s, ignoring: %m", path);
         else {
                 disk_free = sfs.f_bsize * sfs.f_bavail;
@@ -2454,13 +2460,13 @@ static int home_get_disk_status_directory(
 
         if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME)) {
 
-                r = btrfs_is_subvol(path);
+                r = btrfs_is_subvol_fd(fd);
                 if (r < 0)
                         log_debug_errno(r, "Failed to determine whether %s is a btrfs subvolume: %m", path);
                 else if (r > 0) {
                         BtrfsQuotaInfo qi;
 
-                        r = btrfs_subvol_get_subtree_quota(path, 0, &qi);
+                        r = btrfs_subvol_get_subtree_quota_fd(fd, /* subvol_id= */ 0, &qi);
                         if (r < 0)
                                 log_debug_errno(r, "Failed to query btrfs subtree quota, ignoring: %m");
                         else {
@@ -2493,7 +2499,7 @@ static int home_get_disk_status_directory(
         }
 
         if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_FSCRYPT)) {
-                r = quotactl_path(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), path, h->uid, &req);
+                r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), h->uid, &req);
                 if (r < 0) {
                         if (ERRNO_IS_NOT_SUPPORTED(r)) {
                                 log_debug_errno(r, "No UID quota support on %s.", path);
index 47bcaddfc8a0630f121916f70ee06318dd5170ec..2cf70e78b750ff8be8b17e11e8f93fb7da8c8050 100644 (file)
@@ -55,8 +55,8 @@
 #include "user-record-util.h"
 #include "user-record.h"
 #include "user-util.h"
-#include "varlink-io.systemd.service.h"
 #include "varlink-io.systemd.UserDatabase.h"
+#include "varlink-io.systemd.service.h"
 #include "varlink-util.h"
 
 /* Where to look for private/public keys that are used to sign the user records. We are not using
@@ -533,14 +533,15 @@ static int search_quota(uid_t uid, const char *exclude_quota_path) {
                 struct dqblk req;
                 struct stat st;
 
-                if (stat(where, &st) < 0) {
+                _cleanup_close_ int fd = open(where, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+                if (fd < 0) {
                         log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
-                                       "Failed to stat %s, ignoring: %m", where);
+                                       "Failed to open '%s', ignoring: %m", where);
                         continue;
                 }
 
-                if (major(st.st_dev) == 0) {
-                        log_debug("Directory %s is not on a real block device, not checking quota for UID use.", where);
+                if (fstat(fd, &st) < 0) {
+                        log_error_errno(errno, "Failed to stat '%s', ignoring: %m", where);
                         continue;
                 }
 
@@ -559,7 +560,7 @@ static int search_quota(uid_t uid, const char *exclude_quota_path) {
 
                 previous_devno = st.st_dev;
 
-                r = quotactl_devnum(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), st.st_dev, uid, &req);
+                r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), uid, &req);
                 if (r < 0) {
                         if (ERRNO_IS_NOT_SUPPORTED(r))
                                 log_debug_errno(r, "No UID quota support on %s, ignoring.", where);
index ff88367e43d6b931e7c332628745ef54fd78d5da..37bdba30a3e8668fc9817ff9c43a718b9cbe2fbc 100644 (file)
@@ -153,7 +153,7 @@ int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserReco
                                 /* Actually configure the quota. We also ignore errors here, but we do log
                                  * about them loudly, to keep things discoverable even though we don't
                                  * consider lacking quota support in kernel fatal. */
-                                (void) home_update_quota_btrfs(h, d);
+                                (void) home_update_quota_btrfs(h, /* fd= */ -EBADF, d);
                         }
 
                         break;
@@ -169,7 +169,7 @@ int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserReco
                 if (mkdir(d, 0700) < 0)
                         return log_error_errno(errno, "Failed to create temporary home directory %s: %m", d);
 
-                (void) home_update_quota_classic(h, d);
+                (void) home_update_quota_classic(h, /* fd= */ -EBADF, d);
                 break;
 
         default:
@@ -285,7 +285,7 @@ int home_resize_directory(
         if (r < 0)
                 return r;
 
-        r = home_update_quota_auto(h, NULL);
+        r = home_update_quota_auto(h, setup->root_fd, /* path= */ NULL);
         if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
                 return -ESOCKTNOSUPPORT; /* make recognizable */
         if (r < 0)
index e8864051c204995cd4135bd5c4050064429a4824..7574b5f9421e0cd45b878570861639863188299f 100644 (file)
@@ -628,7 +628,7 @@ int home_create_fscrypt(
                 nr++;
         }
 
-        (void) home_update_quota_classic(h, temporary);
+        (void) home_update_quota_classic(h, setup->root_fd, temporary);
 
         r = home_shift_uid(setup->root_fd, HOME_RUNTIME_WORK_DIR, h->uid, h->uid, &mount_fd);
         if (r > 0)
index c9516829d8811aefc35ee6e6866ec8bcc10477a7..363be87104eec8a1eff82ffc89c1395482d0caf8 100644 (file)
@@ -4,6 +4,7 @@
 #include "blockdev-util.h"
 #include "btrfs-util.h"
 #include "errno-util.h"
+#include "fd-util.h"
 #include "format-util.h"
 #include "homework-quota.h"
 #include "missing_magic.h"
 #include "stat-util.h"
 #include "user-util.h"
 
-int home_update_quota_btrfs(UserRecord *h, const char *path) {
+int home_update_quota_btrfs(UserRecord *h, int fd, const char *path) {
         int r;
 
         assert(h);
         assert(path);
 
+        _cleanup_close_ int _fd = -EBADF;
+        if (fd < 0) {
+                _fd = open(path, O_CLOEXEC|O_RDONLY);
+                if (_fd < 0)
+                        return log_error_errno(errno, "Failed to open '%s': %m", path);
+
+                fd = _fd;
+        }
+
         if (h->disk_size == UINT64_MAX)
                 return 0;
 
         /* If the user wants quota, enable it */
-        r = btrfs_quota_enable(path, true);
+        r = btrfs_quota_enable_fd(fd, true);
         if (r == -ENOTTY)
                 return log_error_errno(r, "No btrfs quota support on subvolume %s.", path);
         if (r < 0)
                 return log_error_errno(r, "Failed to enable btrfs quota support on %s.", path);
 
-        r = btrfs_qgroup_set_limit(path, 0, h->disk_size);
+        r = btrfs_qgroup_set_limit_fd(fd, 0, h->disk_size);
         if (r < 0)
                 return log_error_errno(r, "Failed to set disk quota on subvolume %s: %m", path);
 
@@ -36,25 +46,27 @@ int home_update_quota_btrfs(UserRecord *h, const char *path) {
         return 0;
 }
 
-int home_update_quota_classic(UserRecord *h, const char *path) {
+int home_update_quota_classic(UserRecord *h, int fd, const char *path) {
         struct dqblk req;
-        dev_t devno;
         int r;
 
         assert(h);
         assert(uid_is_valid(h->uid));
         assert(path);
 
+        _cleanup_close_ int _fd = -EBADF;
+        if (fd < 0) {
+                _fd = open(path, O_CLOEXEC|O_RDONLY);
+                if (_fd < 0)
+                        return log_error_errno(errno, "Failed to open '%s': %m", path);
+
+                fd = _fd;
+        }
+
         if (h->disk_size == UINT64_MAX)
                 return 0;
 
-        r = get_block_device(path, &devno);
-        if (r < 0)
-                return log_error_errno(r, "Failed to determine block device of %s: %m", path);
-        if (devno == 0)
-                return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system %s not backed by a block device.", path);
-
-        r = quotactl_devnum(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), devno, h->uid, &req);
+        r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), h->uid, &req);
         if (r == -ESRCH)
                 zero(req);
         else if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
@@ -70,7 +82,7 @@ int home_update_quota_classic(UserRecord *h, const char *path) {
         req.dqb_valid = QIF_BLIMITS;
         req.dqb_bsoftlimit = req.dqb_bhardlimit = h->disk_size / QIF_DQBLKSIZE;
 
-        r = quotactl_devnum(QCMD_FIXED(Q_SETQUOTA, USRQUOTA), devno, h->uid, &req);
+        r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_SETQUOTA, USRQUOTA), h->uid, &req);
         if (r == -ESRCH)
                 return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "UID quota not available on %s.", path);
         if (r < 0)
@@ -81,7 +93,7 @@ int home_update_quota_classic(UserRecord *h, const char *path) {
         return 0;
 }
 
-int home_update_quota_auto(UserRecord *h, const char *path) {
+int home_update_quota_auto(UserRecord *h, int fd, const char *path) {
         struct statfs sfs;
         int r;
 
@@ -96,22 +108,31 @@ int home_update_quota_auto(UserRecord *h, const char *path) {
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home record lacks image path.");
         }
 
-        if (statfs(path, &sfs) < 0)
+        _cleanup_close_ int _fd = -EBADF;
+        if (fd < 0) {
+                _fd = open(path, O_CLOEXEC|O_RDONLY);
+                if (_fd < 0)
+                        return log_error_errno(errno, "Failed to open '%s': %m", path);
+
+                fd = _fd;
+        }
+
+        if (fstatfs(fd, &sfs) < 0)
                 return log_error_errno(errno, "Failed to statfs() file system: %m");
 
         if (is_fs_type(&sfs, XFS_SUPER_MAGIC) ||
             is_fs_type(&sfs, EXT4_SUPER_MAGIC))
-                return home_update_quota_classic(h, path);
+                return home_update_quota_classic(h, fd, path);
 
         if (is_fs_type(&sfs, BTRFS_SUPER_MAGIC)) {
 
-                r = btrfs_is_subvol(path);
+                r = btrfs_is_subvol_fd(fd);
                 if (r < 0)
                         return log_error_errno(r, "Failed to test if %s is a subvolume: %m", path);
                 if (r == 0)
                         return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Directory %s is not a subvolume, cannot apply quota.", path);
 
-                return home_update_quota_btrfs(h, path);
+                return home_update_quota_btrfs(h, fd, path);
         }
 
         return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Type of directory %s not known, cannot apply quota.", path);
index a21c9ba8b15a20a3a090813446f0b28b57e1b862..a03510c75e26a668794e67632321120dbec9d743 100644 (file)
@@ -3,6 +3,6 @@
 
 #include "user-record.h"
 
-int home_update_quota_btrfs(UserRecord *h, const char *path);
-int home_update_quota_classic(UserRecord *h, const char *path);
-int home_update_quota_auto(UserRecord *h, const char *path);
+int home_update_quota_btrfs(UserRecord *h, int fd, const char *path);
+int home_update_quota_classic(UserRecord *h, int fd, const char *path);
+int home_update_quota_auto(UserRecord *h, int fd, const char *path);
index 4d014f847ceb1676c258c86938f71880c04344b2..a698129adfe8b02c1e556de9048b0f298747845d 100644 (file)
@@ -1,42 +1,36 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <stdint.h>
 #include <sys/quota.h>
 #include <sys/stat.h>
 
 #include "alloc-util.h"
 #include "blockdev-util.h"
 #include "device-util.h"
+#include "errno-util.h"
+#include "missing_syscall.h"
 #include "quota-util.h"
 
-int quotactl_devnum(int cmd, dev_t devnum, int id, void *addr) {
-        _cleanup_free_ char *devnode = NULL;
+int quotactl_fd_with_fallback(int fd, int cmd, int id, void *addr) {
         int r;
 
-        /* Like quotactl() but takes a dev_t instead of a path to a device node, and fixes caddr_t → void*,
-         * like we should, today */
+        /* Emulates quotactl_fd() on older kernels that lack it. (i.e. kernels < 5.14) */
 
-        r = devname_from_devnum(S_IFBLK, devnum, &devnode);
-        if (r < 0)
+        r = RET_NERRNO(quotactl_fd(fd, cmd, id, addr));
+        if (!ERRNO_IS_NEG_NOT_SUPPORTED(r))
                 return r;
 
-        if (quotactl(cmd, devnode, id, addr) < 0)
-                return -errno;
-
-        return 0;
-}
-
-int quotactl_path(int cmd, const char *path, int id, void *addr) {
         dev_t devno;
-        int r;
-
-        /* Like quotactl() but takes a path to some fs object, and changes the backing file system. I.e. the
-         * argument shouldn't be a block device but a regular file system object */
-
-        r = get_block_device(path, &devno);
+        r = get_block_device_fd(fd, &devno);
         if (r < 0)
                 return r;
         if (devno == 0) /* Doesn't have a block device */
                 return -ENODEV;
 
-        return quotactl_devnum(cmd, devno, id, addr);
+        _cleanup_free_ char *devnode = NULL;
+        r = devname_from_devnum(S_IFBLK, devno, &devnode);
+        if (r < 0)
+                return r;
+
+        return RET_NERRNO(quotactl(cmd, devnode, id, addr));
 }
index 14a390ebe41e051c330ec392e9d7d80fcb0b3b26..ad97eede0172af39ff396cb902270741c8fc601f 100644 (file)
@@ -15,5 +15,4 @@ static inline int QCMD_FIXED(uint32_t cmd, uint32_t type) {
         return (int) QCMD(cmd, type);
 }
 
-int quotactl_devnum(int cmd, dev_t devnum, int id, void *addr);
-int quotactl_path(int cmd, const char *path, int id, void *addr);
+int quotactl_fd_with_fallback(int fd, int cmd, int id, void *addr);
index 303a346c3ce3438ff541952f07eb1c3689a61605..025b2320c595b32ffbfb9ee43202e856a1e9edca 100644 (file)
@@ -34,7 +34,7 @@ StateDirectory=systemd/home
 CacheDirectory=systemd/home
 SystemCallArchitectures=native
 SystemCallErrorNumber=EPERM
-SystemCallFilter=@system-service @mount quotactl
+SystemCallFilter=@system-service @mount quotactl quotactl_fd
 TimeoutStopSec=3min
 {{SERVICE_WATCHDOG}}