diff --git a/src/basic/meson.build b/src/basic/meson.build index f70d1b8bf8..ccb22e1595 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -169,6 +169,8 @@ basic_sources = files(''' process-util.h procfs-util.c procfs-util.h + quota-util.c + quota-util.h random-util.c random-util.h ratelimit.c diff --git a/src/basic/quota-util.c b/src/basic/quota-util.c new file mode 100644 index 0000000000..e048f5571f --- /dev/null +++ b/src/basic/quota-util.c @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include + +#include "alloc-util.h" +#include "blockdev-util.h" +#include "quota-util.h" +#include "stat-util.h" + +int quotactl_devno(int cmd, dev_t devno, int id, void *addr) { + _cleanup_free_ char *devnode = NULL; + 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 */ + + r = device_path_make_major_minor(S_IFBLK, devno, &devnode); + if (r < 0) + 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); + if (r < 0) + return r; + if (devno == 0) + return -ENODEV; + + return quotactl_devno(cmd, devno, id, addr); +} diff --git a/src/basic/quota-util.h b/src/basic/quota-util.h new file mode 100644 index 0000000000..ff99e342f9 --- /dev/null +++ b/src/basic/quota-util.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include +#include +#include + +/* Wrapper around the QCMD() macro of linux/quota.h that removes some undefined behaviour. A typical quota + * command such as QCMD(Q_GETQUOTA, USRQUOTA) cannot be resolved on platforms where "int" is 32bit, as it is + * larger than INT_MAX. Yikes, because that are basically all platforms Linux supports. Let's add a wrapper + * that explicitly takes its arguments as unsigned 32bit, and then converts the shift result explicitly to + * int, acknowledging the undefined behaviour of the kernel headers. This doesn't remove the undefined + * behaviour, but it stops ubsan from complaining about it. */ +static inline int QCMD_FIXED(uint32_t cmd, uint32_t type) { + return (int) QCMD(cmd, type); +} + +int quotactl_devno(int cmd, dev_t devno, int id, void *addr); +int quotactl_path(int cmd, const char *path, int id, void *addr); diff --git a/src/core/meson.build b/src/core/meson.build index df3aa5c6c1..f6e1f66177 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -22,8 +22,6 @@ libcore_sources = ''' bpf-firewall.h cgroup.c cgroup.h - chown-recursive.c - chown-recursive.h dbus-automount.c dbus-automount.h dbus-cgroup.c diff --git a/src/core/chown-recursive.c b/src/shared/chown-recursive.c similarity index 76% rename from src/core/chown-recursive.c rename to src/shared/chown-recursive.c index 24cdf25b83..1aebac307a 100644 --- a/src/core/chown-recursive.c +++ b/src/shared/chown-recursive.c @@ -139,3 +139,40 @@ int path_chown_recursive( return chown_recursive_internal(TAKE_FD(fd), &st, uid, gid, mask); /* we donate the fd to the call, regardless if it succeeded or failed */ } + +int fd_chown_recursive( + int fd, + uid_t uid, + gid_t gid, + mode_t mask) { + + int duplicated_fd = -1; + struct stat st; + + /* Note that the slightly different order of fstat() and the checks here and in + * path_chown_recursive(). That's because when we open the dirctory ourselves we can specify + * O_DIRECTORY and we always want to ensure we are operating on a directory before deciding whether + * the operation is otherwise redundant. */ + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode)) + return -ENOTDIR; + + if (!uid_is_valid(uid) && !gid_is_valid(gid) && (mask & 07777) == 07777) + return 0; /* nothing to do */ + + /* Shortcut, as above */ + if ((!uid_is_valid(uid) || st.st_uid == uid) && + (!gid_is_valid(gid) || st.st_gid == gid) && + ((st.st_mode & ~mask & 07777) == 0)) + return 0; + + /* Let's duplicate the fd here, as opendir() wants to take possession of it and close it afterwards */ + duplicated_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (duplicated_fd < 0) + return -errno; + + return chown_recursive_internal(duplicated_fd, &st, uid, gid, mask); /* fd donated even on failure */ +} diff --git a/src/core/chown-recursive.h b/src/shared/chown-recursive.h similarity index 69% rename from src/core/chown-recursive.h rename to src/shared/chown-recursive.h index bfee05f3be..14a79733f5 100644 --- a/src/core/chown-recursive.h +++ b/src/shared/chown-recursive.h @@ -4,3 +4,5 @@ #include int path_chown_recursive(const char *path, uid_t uid, gid_t gid, mode_t mask); + +int fd_chown_recursive(int fd, uid_t uid, gid_t gid, mode_t mask); diff --git a/src/shared/meson.build b/src/shared/meson.build index feaeffeb26..8cea90e9d6 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -37,6 +37,8 @@ shared_sources = files(''' cgroup-setup.h cgroup-show.c cgroup-show.h + chown-recursive.c + chown-recursive.h clean-ipc.c clean-ipc.h clock-util.c diff --git a/src/shared/varlink.c b/src/shared/varlink.c index e1d956e649..77cea00cb9 100644 --- a/src/shared/varlink.c +++ b/src/shared/varlink.c @@ -1193,6 +1193,15 @@ int varlink_close(Varlink *v) { return 1; } +Varlink* varlink_close_unref(Varlink *v) { + + if (!v) + return NULL; + + (void) varlink_close(v); + return varlink_unref(v); +} + Varlink* varlink_flush_close_unref(Varlink *v) { if (!v) @@ -1200,7 +1209,6 @@ Varlink* varlink_flush_close_unref(Varlink *v) { (void) varlink_flush(v); (void) varlink_close(v); - return varlink_unref(v); } diff --git a/src/shared/varlink.h b/src/shared/varlink.h index 0208802010..0d9617d403 100644 --- a/src/shared/varlink.h +++ b/src/shared/varlink.h @@ -73,6 +73,7 @@ int varlink_flush(Varlink *v); int varlink_close(Varlink *v); Varlink* varlink_flush_close_unref(Varlink *v); +Varlink* varlink_close_unref(Varlink *v); /* Enqueue method call, not expecting a reply */ int varlink_send(Varlink *v, const char *method, JsonVariant *parameters); @@ -152,6 +153,7 @@ int varlink_server_set_connections_max(VarlinkServer *s, unsigned m); int varlink_server_set_description(VarlinkServer *s, const char *description); DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_close_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_flush_close_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkServer *, varlink_server_unref);