From a89afe194831322a9941dda5189a7c84e7b2cb0e Mon Sep 17 00:00:00 2001 From: Andres Beltran Date: Tue, 1 Jul 2025 17:40:47 +0000 Subject: [PATCH] Add quota support for DBus --- src/core/dbus-execute.c | 128 ++++++++++++++++++ src/core/dbus-execute.h | 1 + src/core/dbus.c | 29 +++- src/core/unit.c | 48 +++++++ src/core/unit.h | 2 + src/shared/bus-unit-util.c | 31 +++++ .../fuzz-unit-file/directives-all.service | 6 + 7 files changed, 243 insertions(+), 2 deletions(-) diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 1a08c8867ed..d1cb46a1690 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -31,7 +31,9 @@ #include "namespace.h" #include "nsflags.h" #include "ordered-set.h" +#include "parse-util.h" #include "path-util.h" +#include "percent-util.h" #include "pcre2-util.h" #include "process-util.h" #include "rlimit-util.h" @@ -1000,6 +1002,22 @@ static int property_get_exec_dir_symlink( return sd_bus_message_close_container(reply); } +static int property_get_exec_quota(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + QuotaLimit *q = ASSERT_PTR(userdata); + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "(tus)", q->quota_absolute, q->quota_scale, yes_no(q->quota_enforce)); +} + static int property_get_image_policy( sd_bus *bus, const char *path, @@ -1266,12 +1284,18 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("RuntimeDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("StateDirectorySymlink", "a(sst)", property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("StateDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE].mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StateDirectoryAccounting", "b", bus_property_get_bool, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE].exec_quota.quota_accounting), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StateDirectoryQuota", "(tus)", property_get_exec_quota, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE].exec_quota), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("StateDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CacheDirectorySymlink", "a(sst)", property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CacheDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE].mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CacheDirectoryAccounting", "b", bus_property_get_bool, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE].exec_quota.quota_accounting), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CacheDirectoryQuota", "(tus)", property_get_exec_quota, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE].exec_quota), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CacheDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LogsDirectorySymlink", "a(sst)", property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LogsDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS].mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LogsDirectoryAccounting", "b", bus_property_get_bool, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS].exec_quota.quota_accounting), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LogsDirectoryQuota", "(tus)", property_get_exec_quota, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS].exec_quota), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LogsDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ConfigurationDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION].mode), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ConfigurationDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION]), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1309,6 +1333,60 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_VTABLE_END }; +static int property_get_quota_usage( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = ASSERT_PTR(userdata); + ExecContext *c = ASSERT_PTR(unit_get_exec_context(u)); + uint64_t current_usage_bytes = UINT64_MAX, limit_bytes = UINT64_MAX; + int r; + + assert(bus); + assert(reply); + + ExecDirectoryType dt; + if (streq(property, "StateDirectoryQuotaUsage")) + dt = EXEC_DIRECTORY_STATE; + else if (streq(property, "CacheDirectoryQuotaUsage")) + dt = EXEC_DIRECTORY_CACHE; + else if (streq(property, "LogsDirectoryQuotaUsage")) + dt = EXEC_DIRECTORY_LOGS; + else + assert_not_reached(); + + const QuotaLimit *q; + q = &c->directories[dt].exec_quota; + + if (q->quota_enforce || q->quota_accounting) { + r = unit_get_exec_quota_stats(u, c, dt, ¤t_usage_bytes, &limit_bytes); + if (r < 0) + return r; + } + + if (!q->quota_enforce) + limit_bytes = UINT64_MAX; + if (!q->quota_accounting) + current_usage_bytes = UINT64_MAX; + + return sd_bus_message_append(reply, "(tt)", current_usage_bytes, limit_bytes); +} + +const sd_bus_vtable bus_unit_exec_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("StateDirectoryQuotaUsage", "(tt)", property_get_quota_usage, 0, 0), + SD_BUS_PROPERTY("CacheDirectoryQuotaUsage", "(tt)", property_get_quota_usage, 0, 0), + SD_BUS_PROPERTY("LogsDirectoryQuotaUsage", "(tt)", property_get_quota_usage, 0, 0), + + SD_BUS_VTABLE_END +}; + static int append_exec_command(sd_bus_message *reply, ExecCommand *c) { int r; @@ -2210,6 +2288,15 @@ int bus_exec_context_set_transient_property( if (streq(name, "RuntimeDirectoryMode")) return bus_set_transient_mode_t(u, name, &c->directories[EXEC_DIRECTORY_RUNTIME].mode, message, flags, error); + if (streq(name, "StateDirectoryAccounting")) + return bus_set_transient_bool(u, name, &c->directories[EXEC_DIRECTORY_STATE].exec_quota.quota_accounting, message, flags, error); + + if (streq(name, "CacheDirectoryAccounting")) + return bus_set_transient_bool(u, name, &c->directories[EXEC_DIRECTORY_CACHE].exec_quota.quota_accounting, message, flags, error); + + if (streq(name, "LogsDirectoryAccounting")) + return bus_set_transient_bool(u, name, &c->directories[EXEC_DIRECTORY_LOGS].exec_quota.quota_accounting, message, flags, error); + if (streq(name, "StateDirectoryMode")) return bus_set_transient_mode_t(u, name, &c->directories[EXEC_DIRECTORY_STATE].mode, message, flags, error); @@ -3603,6 +3690,47 @@ int bus_exec_context_set_transient_property( return 1; + } else if (STR_IN_SET(name, "StateDirectoryQuota", "CacheDirectoryQuota", "LogsDirectoryQuota")) { + uint64_t quota_absolute = UINT64_MAX; + uint32_t quota_scale = UINT32_MAX; + const char *enforce_flag; + int quota_enforce; + + r = sd_bus_message_read(message, "(tus)", "a_absolute, "a_scale, &enforce_flag); + if (r < 0) + return r; + + quota_enforce = parse_boolean(enforce_flag); + if (quota_enforce < 0) + return quota_enforce; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + ExecDirectoryType dt; + if (streq(name, "StateDirectoryQuota")) + dt = EXEC_DIRECTORY_STATE; + else if (streq(name, "CacheDirectoryQuota")) + dt = EXEC_DIRECTORY_CACHE; + else if (streq(name, "LogsDirectoryQuota")) + dt = EXEC_DIRECTORY_LOGS; + else + assert_not_reached(); + + if (quota_enforce) { + c->directories[dt].exec_quota.quota_absolute = quota_absolute; + c->directories[dt].exec_quota.quota_scale = quota_scale; + + if (quota_absolute != UINT64_MAX) + unit_write_settingf(u, flags, name, "%s=%" PRIu64, name, quota_absolute); + else + unit_write_settingf(u, flags, name, "%s=%d%%", name, UINT32_SCALE_TO_PERCENT(quota_scale)); + } else + unit_write_settingf(u, flags, name, "%s=", name); + + c->directories[dt].exec_quota.quota_enforce = quota_enforce; + } + + return 1; + } else if (STR_IN_SET(name, "AppArmorProfile", "SmackProcessLabel")) { int ignore; const char *s; diff --git a/src/core/dbus-execute.h b/src/core/dbus-execute.h index c3566bda561..38b9c2ab0fd 100644 --- a/src/core/dbus-execute.h +++ b/src/core/dbus-execute.h @@ -24,6 +24,7 @@ SD_BUS_PROPERTY(name, "a(sasasttttuii)", bus_property_get_exec_ex_command_list, offset, flags) extern const sd_bus_vtable bus_exec_vtable[]; +extern const sd_bus_vtable bus_unit_exec_vtable[]; int bus_property_get_exec_output(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); int bus_property_get_exec_command(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); diff --git a/src/core/dbus.c b/src/core/dbus.c index 223c0b79d21..1659089dd94 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -338,9 +338,8 @@ static int bus_cgroup_context_find(sd_bus *bus, const char *path, const char *in return 1; } -static int bus_exec_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { +static int bus_unit_exec_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { Manager *m = ASSERT_PTR(userdata); - ExecContext *c; Unit *u; int r; @@ -356,6 +355,27 @@ static int bus_exec_context_find(sd_bus *bus, const char *path, const char *inte if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) return 0; + if (!UNIT_HAS_EXEC_CONTEXT(u)) + return 0; + + *found = u; + return 1; +} + +static int bus_exec_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + ExecContext *c; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + + Unit *u; + r = bus_unit_exec_context_find(bus, path, interface, userdata, (void**) &u, error); + if (r <= 0) + return r; + c = unit_get_exec_context(u); if (!c) return 0; @@ -443,6 +463,7 @@ static const BusObjectImplementation bus_mount_object = { { bus_unit_cgroup_vtable, bus_unit_cgroup_find }, { bus_cgroup_vtable, bus_cgroup_context_find }, { bus_exec_vtable, bus_exec_context_find }, + { bus_unit_exec_vtable, bus_unit_exec_context_find }, { bus_kill_vtable, bus_kill_context_find }), }; @@ -471,6 +492,7 @@ static const BusObjectImplementation bus_service_object = { { bus_unit_cgroup_vtable, bus_unit_cgroup_find }, { bus_cgroup_vtable, bus_cgroup_context_find }, { bus_exec_vtable, bus_exec_context_find }, + { bus_unit_exec_vtable, bus_unit_exec_context_find }, { bus_kill_vtable, bus_kill_context_find }), }; @@ -491,6 +513,7 @@ static const BusObjectImplementation bus_socket_object = { { bus_unit_cgroup_vtable, bus_unit_cgroup_find }, { bus_cgroup_vtable, bus_cgroup_context_find }, { bus_exec_vtable, bus_exec_context_find }, + { bus_unit_exec_vtable, bus_unit_exec_context_find }, { bus_kill_vtable, bus_kill_context_find }), }; @@ -502,6 +525,7 @@ static const BusObjectImplementation bus_swap_object = { { bus_unit_cgroup_vtable, bus_unit_cgroup_find }, { bus_cgroup_vtable, bus_cgroup_context_find }, { bus_exec_vtable, bus_exec_context_find }, + { bus_unit_exec_vtable, bus_unit_exec_context_find }, { bus_kill_vtable, bus_kill_context_find }), }; @@ -1160,6 +1184,7 @@ void dump_bus_properties(FILE *f) { vtable_dump_bus_properties(f, bus_cgroup_vtable); vtable_dump_bus_properties(f, bus_device_vtable); vtable_dump_bus_properties(f, bus_exec_vtable); + vtable_dump_bus_properties(f, bus_unit_exec_vtable); vtable_dump_bus_properties(f, bus_job_vtable); vtable_dump_bus_properties(f, bus_kill_vtable); vtable_dump_bus_properties(f, bus_manager_vtable); diff --git a/src/core/unit.c b/src/core/unit.c index 1bacb56801d..aad615383f1 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -19,6 +19,7 @@ #include "cgroup-setup.h" #include "cgroup-util.h" #include "chase.h" +#include "chattr-util.h" #include "condition.h" #include "dbus-unit.h" #include "dropin.h" @@ -45,6 +46,7 @@ #include "mountpoint-util.h" #include "path-util.h" #include "process-util.h" +#include "quota-util.h" #include "rm-rf.h" #include "serialize.h" #include "set.h" @@ -6722,6 +6724,52 @@ static uint64_t unit_get_cpu_weight(Unit *u) { return cc ? cgroup_context_cpu_weight(cc, manager_state(u->manager)) : CGROUP_WEIGHT_DEFAULT; } +int unit_get_exec_quota_stats(Unit *u, ExecContext *c, ExecDirectoryType dt, uint64_t *ret_usage, uint64_t *ret_limit) { + int r; + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *p = NULL, *pp = NULL; + + assert(u); + assert(c); + + if (c->directories[dt].n_items == 0) { + *ret_usage = UINT64_MAX; + *ret_limit = UINT64_MAX; + return 0; + } + + ExecDirectoryItem *i = &c->directories[dt].items[0]; + p = path_join(u->manager->prefix[dt], i->path); + if (!p) + return log_oom_debug(); + + if (exec_directory_is_private(c, dt)) { + pp = path_join(u->manager->prefix[dt], "private", i->path); + if (!pp) + return log_oom_debug(); + } + + const char *target_dir = pp ?: p; + fd = open(target_dir, O_PATH | O_CLOEXEC | O_DIRECTORY); + if (fd < 0) + return log_unit_debug_errno(u, errno, "Failed to get exec quota stats: %m"); + + uint32_t proj_id; + r = read_fs_xattr_fd(fd, /* ret_xflags = */ NULL, &proj_id); + if (r < 0) + return log_unit_debug_errno(u, r, "Failed to get project ID for exec quota stats: %m"); + + struct dqblk req; + r = quota_query_proj_id(fd, proj_id, &req); + if (r <= 0) + return log_unit_debug_errno(u, r, "Failed to query project ID for exec quota stats: %m"); + + *ret_usage = req.dqb_curspace; + *ret_limit = req.dqb_bhardlimit * QIF_DQBLKSIZE; + + return r; +} + int unit_compare_priority(Unit *a, Unit *b) { int ret; diff --git a/src/core/unit.h b/src/core/unit.h index 4c105147261..6e0b58e62e3 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -938,6 +938,8 @@ int unit_add_default_target_dependency(Unit *u, Unit *target); void unit_start_on_termination_deps(Unit *u, UnitDependencyAtom atom); void unit_trigger_notify(Unit *u); +int unit_get_exec_quota_stats(Unit *u, ExecContext *c, ExecDirectoryType dt, uint64_t *ret_usage, uint64_t *ret_limit); + UnitFileState unit_get_unit_file_state(Unit *u); PresetAction unit_get_unit_file_preset(Unit *u); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 41c88b79cf3..0e60cd63d40 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2059,6 +2059,31 @@ static int bus_append_directory(sd_bus_message *m, const char *field, const char return 1; } +static int bus_append_quota_directory(sd_bus_message *m, const char *field, const char *eq) { + uint64_t quota_absolute = UINT64_MAX; + uint32_t quota_scale = UINT32_MAX; + int quota_enforce = false; + int r; + + if (!isempty(eq) && !streq(eq, "off")) { + r = parse_permyriad(eq); + if (r < 0) { + r = parse_size(eq, 1024, "a_absolute); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse argument: %s=%s", field, eq); + } else + quota_scale = UINT32_SCALE_FROM_PERMYRIAD(r); + + quota_enforce = true; + } + + r = sd_bus_message_append(m, "(sv)", field, "(tus)", quota_absolute, quota_scale, yes_no(quota_enforce)); + if (r < 0) + return bus_log_create_error(r); + + return 1; +} + static int bus_append_protect_hostname(sd_bus_message *m, const char *field, const char *eq) { int r; @@ -2514,6 +2539,12 @@ static const BusProperty execute_properties[] = { { "ProtectControlGroupsEx", bus_append_boolean_or_ex_string }, /* compat */ { "PrivateUsers", bus_append_boolean_or_ex_string }, { "PrivateUsersEx", bus_append_boolean_or_ex_string }, /* compat */ + { "StateDirectoryQuota", bus_append_quota_directory }, + { "CacheDirectoryQuota", bus_append_quota_directory }, + { "LogsDirectoryQuota", bus_append_quota_directory }, + { "StateDirectoryAccounting", bus_append_parse_boolean }, + { "CacheDirectoryAccounting", bus_append_parse_boolean }, + { "LogsDirectoryAccounting", bus_append_parse_boolean }, { NULL, bus_try_append_resource_limit, dump_resource_limits }, {} diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service index a0883d0ebe9..360ddd3dd96 100644 --- a/test/fuzz/fuzz-unit-file/directives-all.service +++ b/test/fuzz/fuzz-unit-file/directives-all.service @@ -784,6 +784,8 @@ CPUSchedulingPriority= CPUSchedulingResetOnFork= CacheDirectory= CacheDirectoryMode= +CacheDirectoryAccounting= +CacheDirectoryQuota= Capability= Compress= ConfigurationDirectory= @@ -861,6 +863,8 @@ LogRateLimitBurst= LogFilterPatterns= LogsDirectory= LogsDirectoryMode= +LogsDirectoryAccounting= +LogsDirectoryQuota= MACVLAN= MachineID= MaxFileSec= @@ -943,6 +947,8 @@ StandardInputText= StandardOutput= StateDirectory= StateDirectoryMode= +StateDirectoryAccounting= +StateDirectoryQuota= Storage= SuspendKeyIgnoreInhibited= SyncIntervalSec= -- 2.47.3