homed: move things over to quotactl_fd() (#36902)

Let's use quotactl_fd() wherever we can, it's 2025. quotactl() is such a
mess after all.
This commit is contained in:
Yu Watanabe
2025-03-31 21:15:03 +09:00
committed by GitHub
9 changed files with 81 additions and 60 deletions
+11 -5
View 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);
+8 -7
View 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);
@@ -575,7 +576,7 @@ static int search_quota(uid_t uid, const char *exclude_quota_path) {
if ((FLAGS_SET(req.dqb_valid, QIF_SPACE) && req.dqb_curspace > 0) ||
(FLAGS_SET(req.dqb_valid, QIF_INODES) && req.dqb_curinodes > 0)) {
log_debug_errno(errno, "Quota reports UID " UID_FMT " occupies disk space on %s.", uid, where);
log_debug("Quota reports UID " UID_FMT " occupies disk space on %s.", uid, where);
return 1;
}
}
+3 -3
View 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)
+1 -1
View 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)
+39 -18
View 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"
@@ -11,23 +12,32 @@
#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);
+3 -3
View 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);
+14 -20
View 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));
}
+1 -2
View 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);
+1 -1
View 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}}