]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Add quota support for DBus
authorAndres Beltran <abeltran@microsoft.com>
Tue, 1 Jul 2025 17:40:47 +0000 (17:40 +0000)
committerAndres Beltran <abeltran@microsoft.com>
Mon, 7 Jul 2025 17:31:03 +0000 (17:31 +0000)
src/core/dbus-execute.c
src/core/dbus-execute.h
src/core/dbus.c
src/core/unit.c
src/core/unit.h
src/shared/bus-unit-util.c
test/fuzz/fuzz-unit-file/directives-all.service

index 1a08c8867ed1991f7074758c4f0809a9368d90ba..d1cb46a1690069620a3276d480de3d118d795a7b 100644 (file)
@@ -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, &current_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)", &quota_absolute, &quota_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;
index c3566bda56121ff5aa7423b7d0c3ad8f8d68d290..38b9c2ab0fd449612e9327ee73eee85d00944521 100644 (file)
@@ -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);
index 223c0b79d217c0049be45460c121f79781fe5ae9..1659089dd94d34754fc589e2f18a21afc6578bd4 100644 (file)
@@ -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);
index 1bacb56801d88fc64d568662b8e8653163b52c7b..aad615383f13544a5d86b680920e3b05c66e7f3f 100644 (file)
@@ -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;
 
index 4c1051472615172732e1f640cbd5b321a52d9403..6e0b58e62e3a4a45ce6e101719ddb444d0fcecc9 100644 (file)
@@ -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);
 
index 41c88b79cf3899168431251247d2b3809789e43b..0e60cd63d40e042cdc87b4e6972a7b89619b0301 100644 (file)
@@ -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, &quota_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                          },
         {}
index a0883d0ebe9f33458f64a23d876001ca7cc0426f..360ddd3dd969e5b74cee14bd2af778158250c29f 100644 (file)
@@ -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=