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:
Yu Watanabe
2025-07-08 09:18:20 +09:00
committed by GitHub
28 changed files with 1276 additions and 35 deletions
+14
View File
@@ -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`.
+6
View File
@@ -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=
+260 -8
View File
@@ -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>
+40
View File
@@ -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>
+5
View File
@@ -0,0 +1,5 @@
[Partition]
Type=linux-generic
Format=ext4
SizeMinBytes=498M
SizeMaxBytes=498M
+79
View File
@@ -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));
}
+4
View File
@@ -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
+128
View 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;
+1
View 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);
+27 -2
View 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);
+230
View File
@@ -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, &quota_already_enforced);
if (r < 0)
goto fail;
}
/* If we are not going to run in a namespace, set up the symlinks - otherwise
+65 -1
View File
@@ -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, &quota_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, &quota_info);
if (r < 0)
return r;
r = split_pair(quota_info, " ", &quota_absolute, &quota_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)
-11
View File
@@ -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
View File
@@ -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_;
+6
View File
@@ -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])
+42
View File
@@ -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, &quota_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,
+1
View File
@@ -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);
+48
View 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;
+2
View 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);
+31
View 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 },
{}
+15
View File
@@ -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);
+19
View File
@@ -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_;
+1
View File
@@ -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',
+64
View File
@@ -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);
}
+3
View File
@@ -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);
+75
View File
@@ -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)", &quota_usage, &quota_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)", &quota_absolute, &quota_scale, &quota_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=
+95
View File
@@ -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