core: add quota support for State, Cache, and Log exec directories (#35892)
Based on https://github.com/systemd/systemd/issues/7820, this adds support for quota enforcement to State, Cache, and Log exec directories. * Add new directives, StateDirectoryQuota=, CacheDirectoryQuota=, and LogDirectoryQuota=, to define quotas as percentages (hard limits for blocks and inodes) or absolute values (hard limits for blocks only). * Add new directives, StateDirectoryQuotaAccounting=, CacheDirectoryQuotaAccounting= and LogDirectoryQuotaAccounting= to keep track of storage quotas but not enforce them (effectively just assigning a project ID to defined exec directories). Example: ``` StateDirectory=quotadir StateDirectoryQuota=1% Jan 06 22:55:46 abeltran: Storage quotas set for /var/lib/private/quotadir. Block limit = 2639404, inode limit = 671088 root@abeltran:/var/lib/private# lsattr -pR 3153000189 --------------e----P-- ./quotadir root@abeltran:/var/lib/private# repquota -P /datadrive *** Report for project quotas on device /dev/sdc1 Block grace time: 7days; Inode grace time: 7days Block limits File limits Project used soft hard grace used soft hard grace ---------------------------------------------------------------------- #0 -- 213200 0 0 4086 0 0 #3153000189 -- 2639404 0 2639404 2 0 671088 ```
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Project IDs for Disk Quotas on Exec Directories
|
||||
category: Exec directories
|
||||
layout: default
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
---
|
||||
|
||||
# Project IDs on systemd Systems
|
||||
|
||||
Project IDs are needed to enforce disk quotas for Exec Directories.
|
||||
Project IDs are unsigned, 32-bit integers. For disk quota enforcement,
|
||||
the range used is 2147483648 - 4294967294, which is the highest range
|
||||
inspired from `UIDS-GUID.md`. The range is defined through `PROJ_ID_MIN`
|
||||
and `PROJ_ID_MAX` in `exec-invoke.c`.
|
||||
@@ -257,10 +257,16 @@ All execution-related settings are available for transient units.
|
||||
✓ RuntimeDirectoryMode=
|
||||
✓ RuntimeDirectory=
|
||||
✓ StateDirectoryMode=
|
||||
✓ StateDirectoryAccounting=
|
||||
✓ StateDirectoryQuota=
|
||||
✓ StateDirectory=
|
||||
✓ CacheDirectoryMode=
|
||||
✓ CacheDirectoryAccounting=
|
||||
✓ CacheDirectoryQuota=
|
||||
✓ CacheDirectory=
|
||||
✓ LogsDirectoryMode=
|
||||
✓ LogsDirectoryAccounting=
|
||||
✓ LogsDirectoryQuota=
|
||||
✓ LogsDirectory=
|
||||
✓ ConfigurationDirectoryMode=
|
||||
✓ ConfigurationDirectory=
|
||||
|
||||
@@ -3288,18 +3288,30 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u StateDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b StateDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) StateDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as StateDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) CacheDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u CacheDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b CacheDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) CacheDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as CacheDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) LogsDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u LogsDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b LogsDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) LogsDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as LogsDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u ConfigurationDirectoryMode = ...;
|
||||
@@ -3351,6 +3363,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
readonly s MountImagePolicy = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s ExtensionImagePolicy = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) StateDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) CacheDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) LogsDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s KillMode = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
@@ -3885,10 +3903,22 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<!--property StateDirectoryMode is not documented!-->
|
||||
|
||||
<!--property StateDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property StateDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryMode is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryMode is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectoryMode is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectory is not documented!-->
|
||||
@@ -3935,6 +3965,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<!--property ExtensionImagePolicy is not documented!-->
|
||||
|
||||
<!--property StateDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property KillMode is not documented!-->
|
||||
|
||||
<!--property KillSignal is not documented!-->
|
||||
@@ -4579,18 +4615,30 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectoryMode"/>
|
||||
@@ -4643,6 +4691,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
|
||||
@@ -5443,18 +5497,30 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u StateDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b StateDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) StateDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as StateDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) CacheDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u CacheDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b CacheDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) CacheDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as CacheDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) LogsDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u LogsDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b LogsDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) LogsDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as LogsDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u ConfigurationDirectoryMode = ...;
|
||||
@@ -5506,6 +5572,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
readonly s MountImagePolicy = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s ExtensionImagePolicy = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) StateDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) CacheDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) LogsDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s KillMode = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
@@ -6060,10 +6132,22 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
|
||||
<!--property StateDirectoryMode is not documented!-->
|
||||
|
||||
<!--property StateDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property StateDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryMode is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryMode is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectoryMode is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectory is not documented!-->
|
||||
@@ -6110,6 +6194,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
|
||||
<!--property ExtensionImagePolicy is not documented!-->
|
||||
|
||||
<!--property StateDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property KillMode is not documented!-->
|
||||
|
||||
<!--property KillSignal is not documented!-->
|
||||
@@ -6734,18 +6824,30 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectoryMode"/>
|
||||
@@ -6798,6 +6900,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
|
||||
@@ -7422,18 +7530,30 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u StateDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b StateDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) StateDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as StateDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) CacheDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u CacheDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b CacheDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) CacheDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as CacheDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) LogsDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u LogsDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b LogsDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) LogsDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as LogsDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u ConfigurationDirectoryMode = ...;
|
||||
@@ -7485,6 +7605,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
readonly s MountImagePolicy = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s ExtensionImagePolicy = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) StateDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) CacheDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) LogsDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s KillMode = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
@@ -7961,10 +8087,22 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
|
||||
<!--property StateDirectoryMode is not documented!-->
|
||||
|
||||
<!--property StateDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property StateDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryMode is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryMode is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectoryMode is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectory is not documented!-->
|
||||
@@ -8011,6 +8149,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
|
||||
<!--property ExtensionImagePolicy is not documented!-->
|
||||
|
||||
<!--property StateDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property KillMode is not documented!-->
|
||||
|
||||
<!--property KillSignal is not documented!-->
|
||||
@@ -8543,18 +8687,30 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectoryMode"/>
|
||||
@@ -8607,6 +8763,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
|
||||
@@ -9364,18 +9526,30 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u StateDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b StateDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) StateDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as StateDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) CacheDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u CacheDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b CacheDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) CacheDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as CacheDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) LogsDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u LogsDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly b LogsDirectoryAccounting = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly (tus) LogsDirectoryQuota = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as LogsDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u ConfigurationDirectoryMode = ...;
|
||||
@@ -9427,6 +9601,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
readonly s MountImagePolicy = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s ExtensionImagePolicy = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) StateDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) CacheDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly (tt) LogsDirectoryQuotaUsage = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s KillMode = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
@@ -9885,10 +10065,22 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
|
||||
<!--property StateDirectoryMode is not documented!-->
|
||||
|
||||
<!--property StateDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property StateDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryMode is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryMode is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryAccounting is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryQuota is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectoryMode is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectory is not documented!-->
|
||||
@@ -9935,6 +10127,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
|
||||
<!--property ExtensionImagePolicy is not documented!-->
|
||||
|
||||
<!--property StateDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property CacheDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property LogsDirectoryQuotaUsage is not documented!-->
|
||||
|
||||
<!--property KillMode is not documented!-->
|
||||
|
||||
<!--property KillSignal is not documented!-->
|
||||
@@ -10449,18 +10647,30 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryAccounting"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryQuota"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectoryMode"/>
|
||||
@@ -10513,6 +10723,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryQuotaUsage"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
|
||||
@@ -12076,8 +12292,17 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>PrivateUsersEx</varname>, and
|
||||
<varname>PrivatePIDs</varname> were added in version 257.</para>
|
||||
<para><varname>ProtectHostnameEx</varname>,
|
||||
<varname>DelegateNamespaces</varname>, and
|
||||
<function>RemoveSubGroup()</function> were added in version 258.</para>
|
||||
<varname>DelegateNamespaces</varname>,
|
||||
<function>RemoveSubGroup()</function>,
|
||||
<varname>StateDirectoryQuota</varname>,
|
||||
<varname>StateDirectoryQuotaUsage</varname>,
|
||||
<varname>StateDirectoryAccounting</varname>,
|
||||
<varname>CacheDirectoryQuota</varname>,
|
||||
<varname>CacheDirectoryQuotaUsage</varname>,
|
||||
<varname>CacheDirectoryAccounting</varname>,
|
||||
<varname>LogsDirectoryQuota</varname>,
|
||||
<varname>LogsDirectoryQuotaUsage</varname>, and
|
||||
<varname>LogsDirectoryAccounting</varname>, were added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Socket Unit Objects</title>
|
||||
@@ -12126,8 +12351,17 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>AcceptFileDescriptors</varname>,
|
||||
<varname>DelegateNamespaces</varname>,
|
||||
<function>RemoveSubgroup()</function>,
|
||||
<varname>DeferTrigger</varname>, and
|
||||
<varname>DeferTriggerMaxUSec</varname> were added in version 258.</para>
|
||||
<varname>DeferTrigger</varname>,
|
||||
<varname>DeferTriggerMaxUSec</varname>,
|
||||
<varname>StateDirectoryQuota</varname>,
|
||||
<varname>StateDirectoryQuotaUsage</varname>,
|
||||
<varname>StateDirectoryAccounting</varname>,
|
||||
<varname>CacheDirectoryQuota</varname>,
|
||||
<varname>CacheDirectoryQuotaUsage</varname>,
|
||||
<varname>CacheDirectoryAccounting</varname>,
|
||||
<varname>LogsDirectoryQuota</varname>,
|
||||
<varname>LogsDirectoryQuotaUsage</varname>, and
|
||||
<varname>LogsDirectoryAccounting</varname>, were added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Mount Unit Objects</title>
|
||||
@@ -12171,8 +12405,17 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<para><varname>ProtectHostnameEx</varname>,
|
||||
<varname>DelegateNamespaces</varname>,
|
||||
<function>RemoveSubgroup()</function>,
|
||||
<varname>ReloadResult</varname>, and
|
||||
<varname>CleanResult</varname> were added in version 258.</para>
|
||||
<varname>ReloadResult</varname>,
|
||||
<varname>CleanResult</varname>,
|
||||
<varname>StateDirectoryQuota</varname>,
|
||||
<varname>StateDirectoryQuotaUsage</varname>,
|
||||
<varname>StateDirectoryAccounting</varname>,
|
||||
<varname>CacheDirectoryQuota</varname>,
|
||||
<varname>CacheDirectoryQuotaUsage</varname>,
|
||||
<varname>CacheDirectoryAccounting</varname>,
|
||||
<varname>LogsDirectoryQuota</varname>,
|
||||
<varname>LogsDirectoryQuotaUsage</varname>, and
|
||||
<varname>LogsDirectoryAccounting</varname>, were added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Swap Unit Objects</title>
|
||||
@@ -12214,8 +12457,17 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>ProtectControlGroupsEx</varname>, and
|
||||
<varname>PrivatePIDs</varname> were added in version 257.</para>
|
||||
<para><varname>ProtectHostnameEx</varname>,
|
||||
<varname>DelegateNamespaces</varname>, and
|
||||
<function>RemoveSubgroup()</function> were added in version 258.</para>
|
||||
<varname>DelegateNamespaces</varname>,
|
||||
<function>RemoveSubgroup()</function>,
|
||||
<varname>StateDirectoryQuota</varname>,
|
||||
<varname>StateDirectoryQuotaUsage</varname>,
|
||||
<varname>StateDirectoryAccounting</varname>,
|
||||
<varname>CacheDirectoryQuota</varname>,
|
||||
<varname>CacheDirectoryQuotaUsage</varname>,
|
||||
<varname>CacheDirectoryAccounting</varname>,
|
||||
<varname>LogsDirectoryQuota</varname>,
|
||||
<varname>LogsDirectoryQuotaUsage</varname>, and
|
||||
<varname>LogsDirectoryAccounting</varname>, were added in version 258.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Slice Unit Objects</title>
|
||||
|
||||
@@ -1680,6 +1680,46 @@ StateDirectory=aaa/bbb ccc</programlisting>
|
||||
<xi:include href="version-info.xml" xpointer="v234"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>StateDirectoryQuota=</varname></term>
|
||||
<term><varname>CacheDirectoryQuota=</varname></term>
|
||||
<term><varname>LogsDirectoryQuota=</varname></term>
|
||||
|
||||
<listitem><para>Specifies the storage limits for the directories specified in <varname>StateDirectory=</varname>,
|
||||
<varname>CacheDirectory=</varname>, or <varname>LogsDirectory=</varname> respectively.</para>
|
||||
|
||||
<para>The storage quota is defined in terms of disk blocks and inodes, as per
|
||||
<ulink url="https://man7.org/linux/man-pages/man2/quotactl.2.html">quotactl</ulink>. Takes an absolute size limit
|
||||
in bytes. If the value is suffixed with K, M, G or T, the specified size is parsed as Kilobytes, Megabytes, Gigabytes,
|
||||
or Terabytes (with the base 1024), respectively. If an absolute size limit is specified, only the block quota is set
|
||||
(rounded up to the nearest block). Alternatively, a percentage value may be specified, which applies the same percent
|
||||
quota to both blocks and inodes. Defaults to <constant>off</constant>, in which case no storage limits will be set.</para>
|
||||
|
||||
<para>Only hard limits are set, not soft limits. If the underlying filesystem for the specified directories does not
|
||||
support project quotas, the specified storage limits will not be set. In addition to enabling per-unit quotas with
|
||||
these settings, it is necessary to enable <constant>prjquota</constant> on the file system level as well
|
||||
(i.e. <command>tune2fs -Q prjquota</command>). Quotas must also be turned on with
|
||||
<ulink url="https://linux.die.net/man/8/quotaon">quotaon.</ulink></para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>StateDirectoryAccounting=</varname></term>
|
||||
<term><varname>CacheDirectoryAccounting=</varname></term>
|
||||
<term><varname>LogsDirectoryAccounting=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. If true, a project ID is assigned to the directories specified in
|
||||
<varname>StateDirectory=</varname>, <varname>CacheDirectory=</varname>, or <varname>LogsDirectory=</varname>
|
||||
respectively, which is used for tracking disk usage when disk quotas are turned on
|
||||
(see <ulink url="https://man7.org/linux/man-pages/man8/repquota.8.html">repquota</ulink>). Defaults to false.</para>
|
||||
|
||||
<para>To set and enforce disk quotas, <varname>StateDirectoryQuota=</varname>, <varname>CacheDirectoryQuota=</varname>,
|
||||
or <varname>LogsDirectoryQuota=</varname> must be specified.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>RuntimeDirectoryPreserve=</varname></term>
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[Partition]
|
||||
Type=linux-generic
|
||||
Format=ext4
|
||||
SizeMinBytes=498M
|
||||
SizeMaxBytes=498M
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "log.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "string-util.h"
|
||||
|
||||
int chattr_full(
|
||||
@@ -169,3 +170,81 @@ int read_attr_at(int dir_fd, const char *path, unsigned *ret) {
|
||||
|
||||
return read_attr_fd(fd, ret);
|
||||
}
|
||||
|
||||
int read_fs_xattr_fd(int fd, uint32_t *ret_xflags, uint32_t *ret_projid) {
|
||||
struct fsxattr attrs;
|
||||
_cleanup_close_ int fd_reopened = -EBADF;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
fd = fd_reopen_condition(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY, O_PATH, &fd_reopened);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
if (ioctl(fd, FS_IOC_FSGETXATTR, &attrs) < 0)
|
||||
return -errno;
|
||||
|
||||
if (ret_xflags)
|
||||
*ret_xflags = attrs.fsx_xflags;
|
||||
|
||||
if (ret_projid)
|
||||
*ret_projid = attrs.fsx_projid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int set_proj_id(int fd, uint32_t proj_id) {
|
||||
struct fsxattr attrs;
|
||||
_cleanup_close_ int fd_reopened = -EBADF;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
fd = fd_reopen_condition(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY, O_PATH, &fd_reopened);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
if (ioctl(fd, FS_IOC_FSGETXATTR, &attrs) < 0)
|
||||
return -errno;
|
||||
|
||||
struct stat statbuf;
|
||||
if (fstat(fd, &statbuf) < 0)
|
||||
return -errno;
|
||||
|
||||
if (attrs.fsx_projid == proj_id && (!S_ISDIR(statbuf.st_mode) || FLAGS_SET(attrs.fsx_xflags, FS_XFLAG_PROJINHERIT)))
|
||||
return 0;
|
||||
|
||||
attrs.fsx_projid = proj_id;
|
||||
if (S_ISDIR(statbuf.st_mode))
|
||||
attrs.fsx_xflags |= FS_XFLAG_PROJINHERIT;
|
||||
|
||||
return RET_NERRNO(ioctl(fd, FS_IOC_FSSETXATTR, &attrs));
|
||||
}
|
||||
|
||||
static int set_proj_id_cb(
|
||||
RecurseDirEvent event,
|
||||
const char *path,
|
||||
int dir_fd,
|
||||
int inode_fd,
|
||||
const struct dirent *de,
|
||||
const struct statx *sx,
|
||||
void *userdata) {
|
||||
|
||||
if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
|
||||
return RECURSE_DIR_CONTINUE;
|
||||
|
||||
if (de && !IN_SET(de->d_type, DT_DIR, DT_REG))
|
||||
return RECURSE_DIR_CONTINUE;
|
||||
|
||||
return set_proj_id(inode_fd, PTR_TO_UINT32(userdata));
|
||||
}
|
||||
|
||||
int set_proj_id_recursive(int fd, uint32_t proj_id) {
|
||||
return recurse_dir_at(
|
||||
fd,
|
||||
/* path = */ NULL,
|
||||
/* statx_mask = */ 0,
|
||||
/* n_depth_max = */ UINT_MAX,
|
||||
RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_TOPLEVEL|RECURSE_DIR_INODE_FD,
|
||||
set_proj_id_cb,
|
||||
UINT32_TO_PTR(proj_id));
|
||||
}
|
||||
|
||||
@@ -50,6 +50,10 @@ static inline int chattr_path(const char *path, unsigned value, unsigned mask) {
|
||||
|
||||
int read_attr_fd(int fd, unsigned *ret);
|
||||
int read_attr_at(int dir_fd, const char *path, unsigned *ret);
|
||||
int read_fs_xattr_fd(int fd, uint32_t *ret_xflags, uint32_t *ret_projid);
|
||||
|
||||
int set_proj_id(int fd, uint32_t proj_id);
|
||||
int set_proj_id_recursive(int fd, uint32_t proj_id);
|
||||
|
||||
/* Combination of chattr flags, that should be appropriate for secrets stored on disk: Secure Remove +
|
||||
* Exclusion from Dumping + Synchronous Writing (i.e. not caching in memory) + In-Place Updating (i.e. not
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+27
-2
@@ -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);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if HAVE_PAM
|
||||
@@ -30,6 +31,7 @@
|
||||
#include "cgroup-setup.h"
|
||||
#include "cgroup.h"
|
||||
#include "chase.h"
|
||||
#include "chattr-util.h"
|
||||
#include "chown-recursive.h"
|
||||
#include "constants.h"
|
||||
#include "copy.h"
|
||||
@@ -60,15 +62,19 @@
|
||||
#include "open-file.h"
|
||||
#include "osc-context.h"
|
||||
#include "path-util.h"
|
||||
#include "percent-util.h"
|
||||
#include "pidref.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "process-util.h"
|
||||
#include "psi-util.h"
|
||||
#include "quota-util.h"
|
||||
#include "random-util.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "seccomp-util.h"
|
||||
#include "selinux-util.h"
|
||||
#include "set.h"
|
||||
#include "signal-util.h"
|
||||
#include "siphash24.h"
|
||||
#include "smack-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "stat-util.h"
|
||||
@@ -84,6 +90,11 @@
|
||||
|
||||
#define SNDBUF_SIZE (8*1024*1024)
|
||||
|
||||
/* Project id range for disk quotas */
|
||||
#define PROJ_ID_MIN UINT32_C(2147483648)
|
||||
#define PROJ_ID_MAX UINT32_C(4294967294)
|
||||
#define PROJ_ID_CLAMP_INTO_QUOTA_RANGE(id) ((uint32_t) ((id) % (PROJ_ID_MAX - PROJ_ID_MIN + 1)) + PROJ_ID_MIN)
|
||||
|
||||
static int flag_fds(
|
||||
const int fds[],
|
||||
size_t n_socket_fds,
|
||||
@@ -2575,6 +2586,217 @@ static int create_many_symlinks(const char *root, const char *source, char **sym
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_exec_storage_quota(int fd, uint32_t proj_id, const QuotaLimit *ql) {
|
||||
int r;
|
||||
uint64_t block_limit = 0, inode_limit = 0;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ql);
|
||||
|
||||
if (ql->quota_absolute == 0 || ql->quota_scale == 0)
|
||||
/* Limit of 0 means no usage is allowed. For quotactl, use 1 as the limit, since 0 means that
|
||||
* hard limits are disabled */
|
||||
block_limit = inode_limit = 1;
|
||||
else if (ql->quota_absolute == UINT64_MAX) {
|
||||
_cleanup_close_ int fd_parent = -EBADF;
|
||||
|
||||
/* Use target_dir's parent when setting quotas. If a FD for target_dir has been previously
|
||||
* used for quotactl_fd(SET) and is passed again for fstatvfs(), the total number of blocks is not
|
||||
* reported accurately (instead, the block limit is reported as total blocks). Thus, use the FD
|
||||
* associated with the parent, so that total blocks is accurate */
|
||||
fd_parent = openat(fd, "..", O_PATH|O_CLOEXEC|O_DIRECTORY);
|
||||
if (fd_parent < 0)
|
||||
return -errno;
|
||||
|
||||
uint32_t xattr_flags = 0;
|
||||
r = read_fs_xattr_fd(fd_parent, &xattr_flags, /* ret_projid = */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
/* Refuse if parent has FS_XFLAG_PROJINHERIT since this will mean the total number of blocks will not
|
||||
* be reported accurately */
|
||||
if (FLAGS_SET(xattr_flags, FS_XFLAG_PROJINHERIT))
|
||||
return -ENOMEDIUM;
|
||||
|
||||
struct statvfs disk_st;
|
||||
if (fstatvfs(fd_parent, &disk_st) < 0)
|
||||
return -errno;
|
||||
|
||||
block_limit = (uint64_t) DIV_ROUND_UP((uint64_t)((double) (disk_st.f_frsize * disk_st.f_blocks) / UINT32_MAX * ql->quota_scale), QIF_DQBLKSIZE);
|
||||
inode_limit = (uint64_t) ((double) disk_st.f_files / UINT32_MAX * ql->quota_scale);
|
||||
} else
|
||||
block_limit = (uint64_t) DIV_ROUND_UP(ql->quota_absolute, QIF_DQBLKSIZE);
|
||||
|
||||
struct dqblk req = {
|
||||
.dqb_bhardlimit = block_limit,
|
||||
.dqb_ihardlimit = inode_limit,
|
||||
.dqb_valid = QIF_LIMITS,
|
||||
};
|
||||
|
||||
r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_SETQUOTA, PRJQUOTA), proj_id, &req);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_debug("Storage quotas set for project id %" PRIu32 ". Block limit = %" PRIu64 ", inode limit = %" PRIu64, proj_id, block_limit, inode_limit);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unset_exec_storage_quota(int fd, uint32_t proj_id, bool quota_accounting) {
|
||||
int r, quota_supported;
|
||||
struct dqblk req;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
quota_supported = quota_query_proj_id(fd, proj_id, &req);
|
||||
if (quota_supported < 0)
|
||||
return log_debug_errno(quota_supported, "Failed to query disk quota for project ID %" PRIu32 ": %m", proj_id);
|
||||
|
||||
/* Do not enforce quotas anymore */
|
||||
if (quota_supported && FLAGS_SET(req.dqb_valid, QIF_BLIMITS) && (req.dqb_bhardlimit > 0 || req.dqb_ihardlimit > 0)) {
|
||||
req.dqb_bhardlimit = 0, req.dqb_ihardlimit = 0;
|
||||
|
||||
r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_SETQUOTA, PRJQUOTA), proj_id, &req);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to disable project quotas for project ID %" PRIu32 ": %m", proj_id);
|
||||
|
||||
log_debug("Storage quotas for project ID %" PRIu32 " were disabled", proj_id);
|
||||
}
|
||||
|
||||
/* Release project ID if no accounting needed */
|
||||
if (!quota_accounting) {
|
||||
r = set_proj_id_recursive(fd, 0);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to release project ID %" PRIu32 ", ignoring: %m", proj_id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apply_exec_quotas(
|
||||
const char *target_dir,
|
||||
const char *cgroup_path,
|
||||
ExecDirectoryType type,
|
||||
const QuotaLimit *ql,
|
||||
uint32_t *exec_dt_proj_id, /* in/out */
|
||||
bool *already_enforced) { /* in/out */
|
||||
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
int r, quota_supported = 0;
|
||||
|
||||
assert(target_dir);
|
||||
assert(cgroup_path);
|
||||
assert(ql);
|
||||
assert(exec_dt_proj_id);
|
||||
assert(already_enforced);
|
||||
|
||||
/* Do not apply to the Runtime directory since tmpfs does not support project IDs yet */
|
||||
if (!IN_SET(type, EXEC_DIRECTORY_STATE, EXEC_DIRECTORY_CACHE, EXEC_DIRECTORY_LOGS))
|
||||
return 0;
|
||||
|
||||
fd = open(target_dir, O_PATH|O_CLOEXEC|O_DIRECTORY);
|
||||
if (fd < 0)
|
||||
return log_debug_errno(errno, "Failed to open %s: %m", target_dir);
|
||||
|
||||
/* Get the project ID of the current directory */
|
||||
uint32_t proj_id;
|
||||
r = read_fs_xattr_fd(fd, /* ret_xflags = */ NULL, &proj_id);
|
||||
if (ERRNO_IS_NEG_IOCTL_NOT_SUPPORTED(r)) {
|
||||
log_debug_errno(r, "Not applying storage quotas. FS_IOC_FSGETXATTR not supported for %s: %m", target_dir);
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to retrieve project ID for %s: %m", target_dir);
|
||||
|
||||
/* If the first directory of this ExecType already has a project ID, adopt it as the project ID for all dirs of this ExecType */
|
||||
bool proj_id_exists = PROJ_ID_MIN <= proj_id && proj_id <= PROJ_ID_MAX;
|
||||
if (proj_id_exists && *exec_dt_proj_id == 0)
|
||||
*exec_dt_proj_id = proj_id;
|
||||
|
||||
/* Check if enforcement should be disabled. Do not release project ID if accounting is enabled */
|
||||
if (!ql->quota_enforce) {
|
||||
if (proj_id_exists) {
|
||||
r = unset_exec_storage_quota(fd, proj_id, ql->quota_accounting);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to unset project quotas for %s: %m", target_dir);
|
||||
}
|
||||
|
||||
if (!ql->quota_accounting)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (*exec_dt_proj_id > 0 && *exec_dt_proj_id != proj_id) {
|
||||
/* Set the existing project ID only if the current directory's ID does not exist or does not match */
|
||||
proj_id = *exec_dt_proj_id;
|
||||
r = quota_proj_id_set_recursive(fd, proj_id, false);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to set project ID for %s: %m", target_dir);
|
||||
} else if (*exec_dt_proj_id == 0) {
|
||||
/* Only generate a new project ID if it's the first directory of this ExecType to be processed and does not have an existing ID */
|
||||
static const sd_id128_t k = SD_ID128_ARRAY(e1,4a,79,9b,64,40,41,4a,a8,46,c2,f3,f9,19,4f,01);
|
||||
_cleanup_free_ char *proj_id_plain = NULL;
|
||||
|
||||
/* Generate candidate project id */
|
||||
proj_id_plain = strjoin(cgroup_path, "|", exec_directory_type_to_string(type));
|
||||
if (!proj_id_plain)
|
||||
return log_oom_debug();
|
||||
|
||||
struct siphash state;
|
||||
siphash24_init(&state, k.bytes);
|
||||
siphash24_compress_string(proj_id_plain, &state);
|
||||
proj_id = PROJ_ID_CLAMP_INTO_QUOTA_RANGE(siphash24_finalize(&state));
|
||||
|
||||
#define MAX_PROJ_ID_RETRIES 10
|
||||
for (unsigned attempt = 0;; attempt++) {
|
||||
if (attempt >= MAX_PROJ_ID_RETRIES)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Failed to generate unique project ID for %s: %m", target_dir);
|
||||
|
||||
/* Check if project quotas are supported */
|
||||
struct dqblk req;
|
||||
quota_supported = quota_query_proj_id(fd, proj_id, &req);
|
||||
if (quota_supported < 0)
|
||||
return log_debug_errno(quota_supported, "Failed to query disk quota for project ID %" PRIu32 ": %m", proj_id);
|
||||
if (!quota_supported) {
|
||||
log_debug("Not applying storage quotas. Project quotas are not supported for %s", target_dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!quota_dqblk_is_populated(&req)) {
|
||||
int proj_id_was_set = quota_proj_id_set_recursive(fd, proj_id, true);
|
||||
if (proj_id_was_set < 0)
|
||||
return log_debug_errno(proj_id_was_set, "Failed to set project ID for %s: %m", target_dir);
|
||||
if (proj_id_was_set) {
|
||||
*exec_dt_proj_id = proj_id;
|
||||
log_debug("Project ID %u generated for %s", proj_id, target_dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
proj_id = (uint32_t) (random_u64_range(PROJ_ID_MAX - PROJ_ID_MIN + 1) + PROJ_ID_MIN);
|
||||
}
|
||||
}
|
||||
|
||||
if (ql->quota_enforce && !*already_enforced) {
|
||||
if (!quota_supported) {
|
||||
struct dqblk req;
|
||||
quota_supported = quota_query_proj_id(fd, proj_id, &req);
|
||||
if (quota_supported < 0)
|
||||
return log_debug_errno(quota_supported, "Failed to query disk quota for project ID %" PRIu32 ": %m", proj_id);
|
||||
if (!quota_supported) {
|
||||
log_debug("Not applying storage quotas. Project quotas are not supported for %s", target_dir);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
r = set_exec_storage_quota(fd, proj_id, ql);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to set storage quotas for %s: %m", target_dir);
|
||||
|
||||
*already_enforced = true;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int setup_exec_directory(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
@@ -2608,6 +2830,9 @@ static int setup_exec_directory(
|
||||
gid = 0;
|
||||
}
|
||||
|
||||
uint32_t exec_dt_proj_id = 0;
|
||||
bool quota_already_enforced = false;
|
||||
|
||||
FOREACH_ARRAY(i, context->directories[type].items, context->directories[type].n_items) {
|
||||
_cleanup_free_ char *p = NULL, *pp = NULL;
|
||||
|
||||
@@ -2898,6 +3123,11 @@ static int setup_exec_directory(
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Apply storage quotas and accounting */
|
||||
r = apply_exec_quotas(target_dir, params->cgroup_path, type, &context->directories[type].exec_quota, &exec_dt_proj_id, "a_already_enforced);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* If we are not going to run in a namespace, set up the symlinks - otherwise
|
||||
|
||||
@@ -1848,6 +1848,28 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) {
|
||||
r = serialize_item(f, key, value);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (c->directories[dt].exec_quota.quota_enforce) {
|
||||
_cleanup_free_ char *key_quota = NULL;
|
||||
key_quota = strjoin("exec-context-quota-directories-", exec_directory_type_to_string(dt));
|
||||
if (!key_quota)
|
||||
return log_oom_debug();
|
||||
|
||||
r = serialize_item_format(f, key_quota, "%" PRIu64 " %" PRIu32, c->directories[dt].exec_quota.quota_absolute,
|
||||
c->directories[dt].exec_quota.quota_scale);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
} else if (c->directories[dt].exec_quota.quota_accounting) {
|
||||
_cleanup_free_ char *key_quota = NULL;
|
||||
key_quota = strjoin("exec-context-quota-accounting-directories-", exec_directory_type_to_string(dt));
|
||||
if (!key_quota)
|
||||
return log_oom_debug();
|
||||
|
||||
r = serialize_bool(f, key_quota, c->directories[dt].exec_quota.quota_accounting);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
r = serialize_usec(f, "exec-context-timeout-clean-usec", c->timeout_clean_usec);
|
||||
@@ -2735,7 +2757,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) {
|
||||
|
||||
dt = exec_directory_type_from_string(type);
|
||||
if (dt < 0)
|
||||
return -EINVAL;
|
||||
return dt;
|
||||
|
||||
r = parse_mode(mode, &c->directories[dt].mode);
|
||||
if (r < 0)
|
||||
@@ -2793,6 +2815,48 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
} else if ((val = startswith(l, "exec-context-quota-accounting-directories-"))) {
|
||||
_cleanup_free_ char *type = NULL, *quota_accounting = NULL;
|
||||
ExecDirectoryType dt;
|
||||
|
||||
r = split_pair(val, "=", &type, "a_accounting);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
dt = exec_directory_type_from_string(type);
|
||||
if (dt < 0)
|
||||
return dt;
|
||||
|
||||
r = parse_boolean(quota_accounting);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
c->directories[dt].exec_quota.quota_accounting = r;
|
||||
} else if ((val = startswith(l, "exec-context-quota-directories-"))) {
|
||||
_cleanup_free_ char *type = NULL, *quota_info = NULL, *quota_absolute = NULL, *quota_scale = NULL;
|
||||
ExecDirectoryType dt;
|
||||
|
||||
r = split_pair(val, "=", &type, "a_info);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = split_pair(quota_info, " ", "a_absolute, "a_scale);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
dt = exec_directory_type_from_string(type);
|
||||
if (dt < 0)
|
||||
return dt;
|
||||
|
||||
r = safe_atou64(quota_absolute, &c->directories[dt].exec_quota.quota_absolute);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = safe_atou32(quota_scale, &c->directories[dt].exec_quota.quota_scale);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
c->directories[dt].exec_quota.quota_enforce = true;
|
||||
} else if ((val = startswith(l, "exec-context-timeout-clean-usec="))) {
|
||||
r = deserialize_usec(val, &c->timeout_clean_usec);
|
||||
if (r < 0)
|
||||
|
||||
@@ -3000,17 +3000,6 @@ static const char* const exec_preserve_mode_table[_EXEC_PRESERVE_MODE_MAX] = {
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(exec_preserve_mode, ExecPreserveMode, EXEC_PRESERVE_YES);
|
||||
|
||||
/* This table maps ExecDirectoryType to the setting it is configured with in the unit */
|
||||
static const char* const exec_directory_type_table[_EXEC_DIRECTORY_TYPE_MAX] = {
|
||||
[EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectory",
|
||||
[EXEC_DIRECTORY_STATE] = "StateDirectory",
|
||||
[EXEC_DIRECTORY_CACHE] = "CacheDirectory",
|
||||
[EXEC_DIRECTORY_LOGS] = "LogsDirectory",
|
||||
[EXEC_DIRECTORY_CONFIGURATION] = "ConfigurationDirectory",
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(exec_directory_type, ExecDirectoryType);
|
||||
|
||||
/* This table maps ExecDirectoryType to the symlink setting it is configured with in the unit */
|
||||
static const char* const exec_directory_type_symlink_table[_EXEC_DIRECTORY_TYPE_MAX] = {
|
||||
[EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectorySymlink",
|
||||
|
||||
+9
-13
@@ -7,6 +7,7 @@
|
||||
#include "cgroup-util.h"
|
||||
#include "core-forward.h"
|
||||
#include "cpu-set-util.h"
|
||||
#include "exec-directory-util.h"
|
||||
#include "exec-util.h"
|
||||
#include "list.h"
|
||||
#include "log-context.h"
|
||||
@@ -126,22 +127,19 @@ typedef struct ExecRuntime {
|
||||
int ephemeral_storage_socket[2];
|
||||
} ExecRuntime;
|
||||
|
||||
typedef enum ExecDirectoryType {
|
||||
EXEC_DIRECTORY_RUNTIME,
|
||||
EXEC_DIRECTORY_STATE,
|
||||
EXEC_DIRECTORY_CACHE,
|
||||
EXEC_DIRECTORY_LOGS,
|
||||
EXEC_DIRECTORY_CONFIGURATION,
|
||||
_EXEC_DIRECTORY_TYPE_MAX,
|
||||
_EXEC_DIRECTORY_TYPE_INVALID = -EINVAL,
|
||||
} ExecDirectoryType;
|
||||
|
||||
static inline bool EXEC_DIRECTORY_TYPE_SHALL_CHOWN(ExecDirectoryType t) {
|
||||
/* Returns true for the ExecDirectoryTypes that we shall chown()ing for the user to. We do this for
|
||||
* all of them, except for configuration */
|
||||
return t >= 0 && t < _EXEC_DIRECTORY_TYPE_MAX && t != EXEC_DIRECTORY_CONFIGURATION;
|
||||
}
|
||||
|
||||
typedef struct QuotaLimit {
|
||||
uint64_t quota_absolute; /* absolute quota in bytes; if UINT64_MAX relative quota configured, see below */
|
||||
uint32_t quota_scale; /* relative quota to backend size, scaled to 0…UINT32_MAX */
|
||||
bool quota_enforce;
|
||||
bool quota_accounting;
|
||||
} QuotaLimit;
|
||||
|
||||
typedef struct ExecDirectoryItem {
|
||||
char *path;
|
||||
char **symlinks;
|
||||
@@ -153,6 +151,7 @@ typedef struct ExecDirectory {
|
||||
mode_t mode;
|
||||
size_t n_items;
|
||||
ExecDirectoryItem *items;
|
||||
QuotaLimit exec_quota;
|
||||
} ExecDirectory;
|
||||
|
||||
typedef enum ExecCleanMask {
|
||||
@@ -581,9 +580,6 @@ ExecPreserveMode exec_preserve_mode_from_string(const char *s) _pure_;
|
||||
const char* exec_keyring_mode_to_string(ExecKeyringMode i) _const_;
|
||||
ExecKeyringMode exec_keyring_mode_from_string(const char *s) _pure_;
|
||||
|
||||
const char* exec_directory_type_to_string(ExecDirectoryType i) _const_;
|
||||
ExecDirectoryType exec_directory_type_from_string(const char *s) _pure_;
|
||||
|
||||
const char* exec_directory_type_symlink_to_string(ExecDirectoryType i) _const_;
|
||||
ExecDirectoryType exec_directory_type_symlink_from_string(const char *s) _pure_;
|
||||
|
||||
|
||||
@@ -145,10 +145,16 @@
|
||||
{{type}}.RuntimeDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME].mode)
|
||||
{{type}}.RuntimeDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME])
|
||||
{{type}}.StateDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE].mode)
|
||||
{{type}}.StateDirectoryAccounting, config_parse_bool, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE].exec_quota.quota_accounting)
|
||||
{{type}}.StateDirectoryQuota, config_parse_exec_quota, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE].exec_quota)
|
||||
{{type}}.StateDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE])
|
||||
{{type}}.CacheDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CACHE].mode)
|
||||
{{type}}.CacheDirectoryAccounting, config_parse_bool, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CACHE].exec_quota.quota_accounting)
|
||||
{{type}}.CacheDirectoryQuota, config_parse_exec_quota, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CACHE].exec_quota)
|
||||
{{type}}.CacheDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CACHE])
|
||||
{{type}}.LogsDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_LOGS].mode)
|
||||
{{type}}.LogsDirectoryAccounting, config_parse_bool, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_LOGS].exec_quota.quota_accounting)
|
||||
{{type}}.LogsDirectoryQuota, config_parse_exec_quota, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_LOGS].exec_quota)
|
||||
{{type}}.LogsDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_LOGS])
|
||||
{{type}}.ConfigurationDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].mode)
|
||||
{{type}}.ConfigurationDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION])
|
||||
|
||||
@@ -4602,6 +4602,48 @@ int config_parse_exec_directories(
|
||||
}
|
||||
}
|
||||
|
||||
int config_parse_exec_quota(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
unsigned section_line,
|
||||
const char *lvalue,
|
||||
int ltype,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
QuotaLimit *quota_limit = ASSERT_PTR(data);
|
||||
uint64_t quota_absolute = UINT64_MAX;
|
||||
uint32_t quota_scale = UINT32_MAX;
|
||||
int r;
|
||||
|
||||
if (isempty(rvalue) || streq(rvalue, "off")) {
|
||||
quota_limit->quota_enforce = false;
|
||||
quota_limit->quota_absolute = UINT64_MAX;
|
||||
quota_limit->quota_scale = UINT32_MAX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = parse_permyriad(rvalue);
|
||||
if (r < 0) {
|
||||
r = parse_size(rvalue, 1024, "a_absolute);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse disk quota value, ignoring: %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
} else
|
||||
/* Normalize to 2^32-1 == 100% */
|
||||
quota_scale = UINT32_SCALE_FROM_PERMYRIAD(r);
|
||||
|
||||
quota_limit->quota_absolute = quota_absolute;
|
||||
quota_limit->quota_scale = quota_scale;
|
||||
quota_limit->quota_enforce = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_set_credential(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
|
||||
@@ -101,6 +101,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_smack_process_label);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_address_families);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_exec_preserve_mode);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_exec_quota);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_import_credential);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 },
|
||||
{}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "exec-directory-util.h"
|
||||
#include "string-table.h"
|
||||
|
||||
/* This table maps ExecDirectoryType to the setting it is configured with in the unit */
|
||||
static const char* const exec_directory_type_table[_EXEC_DIRECTORY_TYPE_MAX] = {
|
||||
[EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectory",
|
||||
[EXEC_DIRECTORY_STATE] = "StateDirectory",
|
||||
[EXEC_DIRECTORY_CACHE] = "CacheDirectory",
|
||||
[EXEC_DIRECTORY_LOGS] = "LogsDirectory",
|
||||
[EXEC_DIRECTORY_CONFIGURATION] = "ConfigurationDirectory",
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(exec_directory_type, ExecDirectoryType);
|
||||
@@ -0,0 +1,19 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "macro-fundamental.h"
|
||||
|
||||
typedef enum ExecDirectoryType {
|
||||
EXEC_DIRECTORY_RUNTIME,
|
||||
EXEC_DIRECTORY_STATE,
|
||||
EXEC_DIRECTORY_CACHE,
|
||||
EXEC_DIRECTORY_LOGS,
|
||||
EXEC_DIRECTORY_CONFIGURATION,
|
||||
_EXEC_DIRECTORY_TYPE_MAX,
|
||||
_EXEC_DIRECTORY_TYPE_INVALID = -EINVAL,
|
||||
} ExecDirectoryType;
|
||||
|
||||
const char* exec_directory_type_to_string(ExecDirectoryType i) _const_;
|
||||
ExecDirectoryType exec_directory_type_from_string(const char *s) _pure_;
|
||||
@@ -69,6 +69,7 @@ shared_sources = files(
|
||||
'elf-util.c',
|
||||
'enable-mempool.c',
|
||||
'ethtool-util.c',
|
||||
'exec-directory-util.c',
|
||||
'exec-util.c',
|
||||
'exit-status.c',
|
||||
'extension-util.c',
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "blockdev-util.h"
|
||||
#include "chattr-util.h"
|
||||
#include "device-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "missing_syscall.h"
|
||||
@@ -33,3 +34,66 @@ int quotactl_fd_with_fallback(int fd, int cmd, int id, void *addr) {
|
||||
|
||||
return RET_NERRNO(quotactl(cmd, devnode, id, addr));
|
||||
}
|
||||
|
||||
int quota_query_proj_id(int fd, uint32_t proj_id, struct dqblk *ret_req) {
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ret_req);
|
||||
|
||||
r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, PRJQUOTA), proj_id, ret_req);
|
||||
if (r == -ESRCH || ERRNO_IS_NEG_NOT_SUPPORTED(r) || ERRNO_IS_NEG_PRIVILEGE(r)) {
|
||||
zero(ret_req);
|
||||
return false;
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int quota_proj_id_set_recursive(int fd, uint32_t proj_id, bool verify_exclusive) {
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
/* Confirm only the current inode has the project id (in case of race conditions) */
|
||||
if (verify_exclusive) {
|
||||
/* Set to top level first because of the case where directories already exist with multiple subdirectories,
|
||||
* in which case, number of inodes will be > 1 if applied recursively only */
|
||||
r = set_proj_id(fd, proj_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
struct dqblk req;
|
||||
r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, PRJQUOTA), proj_id, &req);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (req.dqb_curinodes == 0)
|
||||
return -ENOTRECOVERABLE;
|
||||
|
||||
if (req.dqb_curinodes != 1)
|
||||
return false;
|
||||
}
|
||||
|
||||
r = set_proj_id_recursive(fd, proj_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool quota_dqblk_is_populated(const struct dqblk *req) {
|
||||
assert(req);
|
||||
|
||||
return FLAGS_SET(req->dqb_valid, QIF_BLIMITS|QIF_SPACE|QIF_ILIMITS|QIF_INODES|QIF_BTIME|QIF_ITIME) &&
|
||||
(req->dqb_bhardlimit > 0 ||
|
||||
req->dqb_bsoftlimit > 0 ||
|
||||
req->dqb_ihardlimit > 0 ||
|
||||
req->dqb_isoftlimit > 0 ||
|
||||
req->dqb_curspace > 0 ||
|
||||
req->dqb_curinodes > 0 ||
|
||||
req->dqb_btime > 0 ||
|
||||
req->dqb_itime > 0);
|
||||
}
|
||||
|
||||
@@ -16,3 +16,6 @@ static inline int QCMD_FIXED(uint32_t cmd, uint32_t type) {
|
||||
}
|
||||
|
||||
int quotactl_fd_with_fallback(int fd, int cmd, int id, void *addr);
|
||||
int quota_query_proj_id(int fd, uint32_t proj_id, struct dqblk *ret_req);
|
||||
int quota_proj_id_set_recursive(int fd, uint32_t proj_id, bool verify_exclusive);
|
||||
bool quota_dqblk_is_populated(const struct dqblk *dqblk);
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
#include "cgroup-show.h"
|
||||
#include "cpu-set-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "exec-directory-util.h"
|
||||
#include "exec-util.h"
|
||||
#include "exit-status.h"
|
||||
#include "extract-word.h"
|
||||
#include "format-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "hostname-setup.h"
|
||||
@@ -33,6 +35,7 @@
|
||||
#include "pager.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "percent-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "process-util.h"
|
||||
#include "set.h"
|
||||
@@ -161,6 +164,13 @@ typedef struct UnitCondition {
|
||||
LIST_FIELDS(struct UnitCondition, conditions);
|
||||
} UnitCondition;
|
||||
|
||||
typedef struct QuotaInfo {
|
||||
bool quota_enforce;
|
||||
bool quota_accounting;
|
||||
uint64_t quota_usage;
|
||||
uint64_t quota_limit;
|
||||
} QuotaInfo;
|
||||
|
||||
static UnitCondition* unit_condition_free(UnitCondition *c) {
|
||||
if (!c)
|
||||
return NULL;
|
||||
@@ -299,6 +309,9 @@ typedef struct UnitStatusInfo {
|
||||
uint64_t default_memory_low;
|
||||
uint64_t default_startup_memory_low;
|
||||
|
||||
/* Exec Quotas */
|
||||
QuotaInfo exec_directories_quota[_EXEC_DIRECTORY_TYPE_MAX];
|
||||
|
||||
LIST_HEAD(ExecStatusInfo, exec_status_info_list);
|
||||
} UnitStatusInfo;
|
||||
|
||||
@@ -338,6 +351,22 @@ static void format_enable_state(const char *enable_state, const char **enable_on
|
||||
*enable_on = *enable_off = "";
|
||||
}
|
||||
|
||||
static void print_exec_directory_quota(UnitStatusInfo *i, ExecDirectoryType dt) {
|
||||
assert(i);
|
||||
|
||||
if (!IN_SET(dt, EXEC_DIRECTORY_STATE, EXEC_DIRECTORY_CACHE, EXEC_DIRECTORY_LOGS))
|
||||
return;
|
||||
|
||||
if (i->exec_directories_quota[dt].quota_accounting) {
|
||||
printf(" %s: %s", exec_directory_type_to_string(dt), FORMAT_BYTES(i->exec_directories_quota[dt].quota_usage));
|
||||
|
||||
if (i->exec_directories_quota[dt].quota_enforce)
|
||||
printf(" (max: %s)", FORMAT_BYTES(i->exec_directories_quota[dt].quota_limit));
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void print_status_info(
|
||||
sd_bus *bus,
|
||||
UnitStatusInfo *i,
|
||||
@@ -877,6 +906,9 @@ static void print_status_info(
|
||||
if (i->cpu_usage_nsec != UINT64_MAX)
|
||||
printf(" CPU: %s\n", FORMAT_TIMESPAN(i->cpu_usage_nsec / NSEC_PER_USEC, USEC_PER_MSEC));
|
||||
|
||||
for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++)
|
||||
print_exec_directory_quota(i, dt);
|
||||
|
||||
if (i->control_group) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
static const char prefix[] = " ";
|
||||
@@ -1122,6 +1154,25 @@ static int map_exec(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_e
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int map_quota(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
|
||||
int r;
|
||||
QuotaInfo *qi = ASSERT_PTR(userdata);
|
||||
uint64_t quota_usage, quota_limit;
|
||||
|
||||
r = sd_bus_message_read(m, "(tt)", "a_usage, "a_limit);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*qi = (QuotaInfo) {
|
||||
.quota_enforce = quota_limit != UINT64_MAX,
|
||||
.quota_accounting = quota_usage != UINT64_MAX,
|
||||
.quota_usage = quota_usage,
|
||||
.quota_limit = quota_limit
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int print_property(const char *name, const char *expected_value, sd_bus_message *m, BusPrintPropertyFlags flags) {
|
||||
char bus_type;
|
||||
const char *contents;
|
||||
@@ -1338,6 +1389,27 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
|
||||
fputc('\n', stdout);
|
||||
}
|
||||
return 1;
|
||||
} else if (STR_IN_SET(name, "StateDirectoryQuota", "CacheDirectoryQuota", "LogsDirectoryQuota")) {
|
||||
uint64_t quota_absolute;
|
||||
uint32_t quota_scale;
|
||||
const char *quota_enforce;
|
||||
|
||||
r = sd_bus_message_read(m, "(tus)", "a_absolute, "a_scale, "a_enforce);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = parse_boolean(quota_enforce);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!r)
|
||||
bus_print_property_value(name, expected_value, flags, "[not set]");
|
||||
else if (quota_absolute != UINT64_MAX)
|
||||
bus_print_property_valuef(name, expected_value, flags, "%" PRIu64, quota_absolute);
|
||||
else
|
||||
bus_print_property_valuef(name, expected_value, flags, "%d%%", UINT32_SCALE_TO_PERCENT(quota_scale));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -2201,6 +2273,9 @@ static int show_one(
|
||||
{ "IPEgressBytes", "t", NULL, offsetof(UnitStatusInfo, ip_egress_bytes) },
|
||||
{ "IOReadBytes", "t", NULL, offsetof(UnitStatusInfo, io_read_bytes) },
|
||||
{ "IOWriteBytes", "t", NULL, offsetof(UnitStatusInfo, io_write_bytes) },
|
||||
{ "StateDirectoryQuotaUsage", "(tt)", map_quota, offsetof(UnitStatusInfo, exec_directories_quota[EXEC_DIRECTORY_STATE]) },
|
||||
{ "CacheDirectoryQuotaUsage", "(tt)", map_quota, offsetof(UnitStatusInfo, exec_directories_quota[EXEC_DIRECTORY_CACHE]) },
|
||||
{ "LogsDirectoryQuotaUsage", "(tt)", map_quota, offsetof(UnitStatusInfo, exec_directories_quota[EXEC_DIRECTORY_LOGS]) },
|
||||
{ "ExecCondition", "a(sasbttttuii)", map_exec, 0 },
|
||||
{ "ExecConditionEx", "a(sasasttttuii)", map_exec, 0 },
|
||||
{ "ExecStartPre", "a(sasbttttuii)", map_exec, 0 },
|
||||
|
||||
@@ -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=
|
||||
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# shellcheck disable=SC2016
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
systemd-analyze log-level debug
|
||||
|
||||
test_quotas() {
|
||||
|
||||
local directory="$1"
|
||||
local exec_directory_directive="$2"
|
||||
local exec_quota_directive="$3"
|
||||
local mountpoint="/datadrive"
|
||||
|
||||
dev_num=$(lsblk | grep "498M" | awk '{print $1}' | sed 's/[^a-zA-Z0-9]*//g')
|
||||
|
||||
if ! tune2fs -Q prjquota "/dev/${dev_num}"; then
|
||||
return
|
||||
fi
|
||||
mkdir -p "${mountpoint}"
|
||||
mount "/dev/${dev_num}" "${mountpoint}"
|
||||
|
||||
mv /var/lib/ "${mountpoint}"
|
||||
rm -rf /var/lib/ && ln -s "${mountpoint}/lib/" /var/
|
||||
|
||||
rm -rf "${directory}/quotadir"
|
||||
|
||||
cat >/run/systemd/system/testservice-07-check-quotas.service <<EOF
|
||||
[Unit]
|
||||
Description=Check quotas with ExecDirectory
|
||||
|
||||
[Service]
|
||||
# Relevant only for sanitizer runs
|
||||
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
|
||||
Type=oneshot
|
||||
|
||||
MountAPIVFS=yes
|
||||
DynamicUser=yes
|
||||
PrivateUsers=yes
|
||||
TemporaryFileSystem=/run /var/opt /var/lib /vol
|
||||
${exec_directory_directive}
|
||||
${exec_quota_directive}
|
||||
ExecStart=/bin/bash -c ' \
|
||||
set -eux; \
|
||||
set -o pipefail; \
|
||||
touch ${directory}/quotadir/testfile; \
|
||||
'
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl start testservice-07-check-quotas.service
|
||||
|
||||
proj_id=$(lsattr -p "${directory}" | grep "quotadir" | awk '{print $1}')
|
||||
[[ $proj_id -gt 0 ]]
|
||||
|
||||
block_limit=$(repquota -P "${mountpoint}" | grep -E "#$proj_id " | awk '{print $5}')
|
||||
inode_limit=$(repquota -P "${mountpoint}" | grep -E "#$proj_id " | awk '{print $8}')
|
||||
[[ $block_limit -gt 0 ]]
|
||||
[[ $inode_limit -gt 0 ]]
|
||||
|
||||
# Test exceed limit
|
||||
rm -rf "${directory}/quotadir"
|
||||
|
||||
cat >/run/systemd/system/testservice-07-check-quotas.service <<EOF
|
||||
[Unit]
|
||||
Description=Check quotas with ExecDirectory
|
||||
|
||||
[Service]
|
||||
# Relevant only for sanitizer runs
|
||||
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
|
||||
Type=oneshot
|
||||
|
||||
MountAPIVFS=yes
|
||||
DynamicUser=yes
|
||||
PrivateUsers=yes
|
||||
TemporaryFileSystem=/run /var/opt /var/lib /vol
|
||||
${exec_directory_directive}
|
||||
${exec_quota_directive}
|
||||
ExecStart=/bin/bash -c ' \
|
||||
set -eux; \
|
||||
set -o pipefail; \
|
||||
(! fallocate -l 10000G ${directory}/quotadir/largefile); \
|
||||
'
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl start testservice-07-check-quotas.service
|
||||
}
|
||||
|
||||
test_quotas "/var/lib/private" "StateDirectory=quotadir" "StateDirectoryQuota=1%"
|
||||
|
||||
systemd-analyze log-level info
|
||||
|
||||
touch /testok
|
||||
Reference in New Issue
Block a user