1
0
mirror of https://github.com/systemd/systemd synced 2026-03-18 11:04:46 +01:00

Compare commits

..

14 Commits

Author SHA1 Message Date
Srinidhi Kaushik
7f7a50dd15 tmpfiles: extend "Age" to accept an "age-by" argument
For "systemd-tmpfiles --cleanup", when the "Age" parameter
is specified, the criteria for deletion is determined from
the path's last modification timestamp ("mtime"), its last
access timestamp ("atime") and its last status change
timestamp ("ctime").

For instance, if one of those paths to be cleaned up are
opened, it results in the modification of "atime", which
results file system entry to not be removed because the
default aging algorithm would skip the entry.

Add an optional "age-by" argument by extending the "Age"
parameter to restrict the clean-up for a particular type
of file timestamp, which can be specified in "tmpfiles.d"
as follows:

  [age-by:]cleanup-age, where age-by is "[abcmACBM]+"

For example:

  d /foo/bar - - - abM:1m -

Would clean-up any files that were not accessed and created,
or directories that were not modified less than a minute ago
in "/foo/bar".

Fixes: #17002
2021-06-08 18:24:58 +02:00
Lennart Poettering
66973219c0
Merge pull request #19166 from bluca/coredump_compress_on_the_fly
coredump: compress on the fly
2021-06-08 18:24:34 +02:00
Yu Watanabe
c50404aecc udev: make WakeOnLan= take multiple features
WAKE_XXX are flag, not enum.
2021-06-08 18:24:11 +02:00
Yu Watanabe
3da0caf5bb core/socket: do not assign another fd to SocketPort which already has a fd on deserialization
Otherwise, if a socket address is duplicated, then the previous fd is
closed.

Fixes #19843.
2021-06-08 18:23:47 +02:00
Lennart Poettering
fd5f48af5c
Merge pull request #19817 from keszybz/switch-root-serialization
Drop serialization of mounts and automounts over root switch
2021-06-08 18:22:58 +02:00
Allen Webb
c46c323385 tmpfiles: add '=' action modifier.
Add the '=' action modifier that instructs tmpfiles.d to check the file
type of a path and remove objects that do not match before trying to
open or create the path.

BUG=chromium:1186405
TEST=./test/test-systemd-tmpfiles.py "$(which systemd-tmpfiles)"

Change-Id: If807dc0db427393e9e0047aba640d0d114897c26
2021-06-08 17:23:26 +02:00
Peter Morrow
90a404f5d4 man: add details on overriding top level drop-ins
When using top level drop-ins it isn't immediately obvious that one can
make use of symlinking to disable a top-level drop in for a specific
unit.

Signed-off-by: Peter Morrow <pemorrow@linux.microsoft.com>
2021-06-08 17:03:03 +02:00
Zbigniew Jędrzejewski-Szmek
755021d434 core: do not serialize mounts and automounts for switch-root
When e.g. tmp.mount is present in the initrd, and we serialize it, switch root,
and deserialize, the new systemd is confused because it thinks /tmp is mounted.
In general, it doesn't make sense to serialize anything that refers to paths in
the old root file system.

This fixes two errors for me:

1. tmp.mount was not mounted properly before local-fs.target. It would be
mounted as some point (I guess when we re-read /proc/self/mountinfo for some
other reason). In effect systemd-tmpfiles-setup.service would see one fs, and
some other units started later a different one. In particular gdm.service would
fail because the pre-created /tmp/.X11-unix with proper permissions would not
exist at time it was started.

2. # systemd[1]: proc-sys-fs-binfmt_misc.automount: Got hangup/error on autofs pipe from kernel. Likely our automount point has been unmounted by someone or something else?
   # systemd[1]: proc-sys-fs-binfmt_misc.automount: Failed with result 'unmounted'.
   # systemd[1]: Mounting proc-sys-fs-binfmt_misc.mount...
   # systemd[1]: Mounted proc-sys-fs-binfmt_misc.mount.
   # systemd[1]: Starting systemd-binfmt.service...
   # systemd[1]: Finished systemd-binfmt.service.
   # systemd[1]: proc-sys-fs-binfmt_misc.automount: Path /proc/sys/fs/binfmt_misc is already a mount point, refusing start.
   # systemd[1]: Failed to set up automount proc-sys-fs-binfmt_misc.automount.
   # systemd[1]: proc-sys-fs-binfmt_misc.automount: Path /proc/sys/fs/binfmt_misc is already a mount point, refusing start.
   # systemd[1]: Failed to set up automount proc-sys-fs-binfmt_misc.automount.
   # systemd[1]: proc-sys-fs-binfmt_misc.automount: Path /proc/sys/fs/binfmt_misc is already a mount point, refusing start.
   # systemd[1]: Failed to set up automount proc-sys-fs-binfmt_misc.automount.
   # systemd[1]: Stopping systemd-binfmt.service...
   # systemd[1]: systemd-binfmt.service: Deactivated successfully.
   # systemd[1]: Stopped systemd-binfmt.service.

I couldn't understand the error here, but in retrospect the first line is entirely
correct: "someone or something else" was the old systemd unmounting the old root.
2021-06-08 16:04:38 +02:00
Luca Boccassi
587f2a5e56 coredump: check cgroups memory limit if storing on tmpfs
When /var/lib/systemd/coredump/ is backed by a tmpfs, all disk usage
will be accounted under the systemd-coredump process cgroup memory
limit.
If MemoryMax is set, this might cause systemd-coredump to be terminated
by the kernel oom handler when writing large uncompressed core files,
even if the compressed core would fit within the limits.

Detect if a tmpfs is used, and if so check MemoryMax from the process
and slice cgroups, and do not write uncompressed core files that are
greater than half the available memory. If the limit is breached,
stop writing and compress the written chunk immediately, then delete
the uncompressed chunk to free more memory, and resume compressing
directly from STDIN.

Example debug log when this situation happens:

systemd-coredump[737455]: Setting max_size to limit writes to 51344896 bytes.
systemd-coredump[737455]: ZSTD compression finished (51344896 -> 3260 bytes, 0.0%)
systemd-coredump[737455]: ZSTD compression finished (1022786048 -> 47245 bytes, 0.0%)
systemd-coredump[737455]: Process 737445 (a.out) of user 1000 dumped core.
2021-06-08 14:05:56 +01:00
Luca Boccassi
5b6f8e13ad compress: return uncompressed size to the caller
Useful when compressing anonymous FDs that cannot be rewund
2021-06-08 14:05:56 +01:00
Luca Boccassi
93ff34e44a core: add MemoryAvailable unit property
Try to infer the unused memory that a unit can claim before the
memory.max limit is reached, including any limit set on any parent
slice above the unit itself.
2021-06-08 14:05:56 +01:00
Emilio Herrera
d477a094e8 po: Translated using Weblate (Spanish)
Currently translated at 62.9% (119 of 189 strings)

Co-authored-by: Emilio Herrera <ehespinosa57@gmail.com>
Translate-URL: https://translate.fedoraproject.org/projects/systemd/master/es/
Translation: systemd/main
2021-06-08 11:28:46 +01:00
Zbigniew Jędrzejewski-Szmek
cbd4a8df5f core: drop bitfields in VTable object
The usual: bitfields make sense as a memory-saving measure when we have many
objects of a given type. When the object appears at most in a few copies, the
overhead of additional code to access bitfields is more than the savings.
2021-06-04 14:54:19 +02:00
Zbigniew Jędrzejewski-Szmek
1ece068e83 core: update comment
unit_serialize_item() was dropped in d68c645bd3323ae1f0dfcb8fd74ea6b19681db8a.
But "cannot be restored from other sources" is also not entirely true: for
example for mounts we may be able to figure out most state from /p/s/mountinfo.
So let's make the comment more oblique.
2021-06-04 10:51:19 +02:00
33 changed files with 1312 additions and 361 deletions

View File

@ -99,7 +99,7 @@
<term><varname>ExternalSizeMax=</varname></term>
<term><varname>JournalSizeMax=</varname></term>
<listitem><para>The maximum (uncompressed) size in bytes of a
<listitem><para>The maximum (compressed or uncompressed) size in bytes of a
core to be saved. Unit suffixes are allowed just as in
<option>ProcessSizeMax=</option></para></listitem>.
</varlistentry>

View File

@ -2401,6 +2401,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@ -3504,6 +3506,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
@ -4063,6 +4067,11 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<varname>MountImages</varname>
<varname>ExtensionImages</varname>
see systemd.exec(5) for their meaning.</para>
<para><varname>MemoryAvailable</varname> indicates how much unused memory is available to the unit before
the <literal>MemoryMax</literal> or <literal>MemoryHigh</literal> (whichever is lower) limit set by the cgroup
memory controller is reached. It will take into consideration limits on all parent slices, other than the
limits set on the unit itself.</para>
</refsect2>
</refsect1>
@ -4196,6 +4205,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@ -5321,6 +5332,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
@ -5915,6 +5928,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@ -6886,6 +6901,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
@ -7601,6 +7618,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@ -8544,6 +8563,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
@ -9112,6 +9133,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@ -9403,6 +9426,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
@ -9571,6 +9596,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...];
@ -9904,6 +9931,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>

View File

@ -492,8 +492,9 @@
<varlistentry>
<term><varname>WakeOnLan=</varname></term>
<listitem>
<para>The Wake-on-LAN policy to set for the device. The
supported values are:</para>
<para>The Wake-on-LAN policy to set for the device. Takes the special value
<literal>off</literal> which disables Wake-on-LAN, or space separated list of the following
words:</para>
<variablelist>
<varlistentry>
@ -540,15 +541,11 @@
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>off</option></term>
<listitem>
<para>Never wake.</para>
</listitem>
</varlistentry>
</variablelist>
<para>Defaults to <option>off</option>.</para>
<para>Defaults to unset, and the device's default will be used. This setting can be specified
multiple times. If an empty string is assigned, then the all previous assignments are
cleared.</para>
</listitem>
</varlistentry>
<varlistentry>

View File

@ -225,10 +225,12 @@
where <replaceable>type</replaceable> may be e.g. <literal>service</literal> or <literal>socket</literal>,
that allows altering or adding to the settings of all corresponding unit files on the system.
The formatting and precedence of applying drop-in configurations follow what is defined above.
Configurations in <filename><replaceable>type</replaceable>.d/</filename> have the lowest precedence
compared to settings in the name specific override directories. So the contents of
<filename>foo-.service.d/10-override.conf</filename> would override
<filename>service.d/10-override.conf</filename>.</para>
Files in <filename><replaceable>type</replaceable>.d/</filename> have lower precedence compared
to files in name-specific override directories. The usual rules apply: multiple drop-in files
with different names are applied in lexicographic order, regardless of which of the directories
they reside in, so a file in <filename><replaceable>type</replaceable>.d/</filename> applies
to a unit only if there are no drop-ins or masks with that name in directories with higher
precedence. See Examples.</para>
<para>Note that while systemd offers a flexible dependency system
between units it is recommended to use this functionality only
@ -2201,6 +2203,62 @@ PrivateTmp=yes</programlisting>
to override the entire unit.</para>
</example>
<example>
<title>Top level drop-ins with template units</title>
<para>Top level drop-ins can be used to change some aspect of all units
of a particular type. For example by creating the
<filename index='false'>/etc/systemd/system/service.d</filename>
directory with a drop-in file, the contents of the drop-in file can be
applied to all service units. We can take this further by having the
top-level drop-in instantiate a secondary helper unit. Consider for
example the following set of units and drop-in files where we install
an OnFailure dependency for all service units.</para>
<para>
<filename index='false'>/etc/systemd/system/failure-handler@.service</filename>:</para>
<programlisting>[Unit]
Description=My failure handler for %i
[Service]
Type=oneshot
# Perform some special action for when %i exits unexpectedly.
ExecStart=/usr/sbin/myfailurehandler %i
</programlisting>
<para>We can then add an instance of the
<filename index='false'>failure-handler@.service</filename> as an
<varname>OnFailure=</varname> dependency for all service units.</para>
<para>
<filename index='false'>/etc/systemd/system/service.d/10-all.conf</filename>:</para>
<programlisting>[Unit]
OnFailure=failure-handler@%n.service
</programlisting>
<para>Now, after running <command>systemctl daemon-reload</command> all
services will have acquired an <varname>OnFailure=</varname> dependency on
<filename index='false'>failure-handler@%n.service</filename>. The
template instance units will also have gained the dependency which results
in the creation of a recursive dependency chain. We can break the chain by
disabling the drop-in for the template instance units via a symlink to
<filename index='false'>/dev/null</filename>:</para>
<programlisting>
<command>mkdir /etc/systemd/system/failure-handler@.service.d</command>
<command>ln -s /dev/null /etc/systemd/system/failure-handler@.service.d/10-all.conf</command>
<command>systemctl daemon-reload</command>
</programlisting>
<para>This ensures that if a <filename index='false'>failure-handler@.service</filename> instance fails it will not trigger an instance named
<filename index='false'>failure-handler@failure-handler.service.service</filename>.</para>
</example>
</refsect1>
<refsect1>

View File

@ -161,7 +161,7 @@ L /tmp/foobar - - - - /dev/null</programlisting>
<title>Type</title>
<para>The type consists of a single letter and optionally an exclamation mark (<literal>!</literal>)
and/or minus sign (<literal>-</literal>).</para>
minus sign (<literal>-</literal>), and/or equals sign (<literal>=</literal>).</para>
<para>The following line types are understood:</para>
@ -482,6 +482,11 @@ r! /tmp/.X[0-9]*-lock</programlisting>
<programlisting># Modify sysfs but don't fail if we are in a container with a read-only /proc
w- /proc/sys/vm/swappiness - - - - 10</programlisting></para>
<para>If the equals sign (<literal>=</literal>) is used, the file types of existing objects in the specified path
are checked, and removed if they do not match. This includes any implicitly created parent directories (which can
be either directories or directory symlinks). For example, if there is a FIFO in place of one of the parent path
components it will be replaced with a directory.</para>
<para>Note that for all line types that result in creation of any kind of file node
(i.e. <varname>f</varname>/<varname>F</varname>,
<varname>d</varname>/<varname>D</varname>/<varname>v</varname>/<varname>q</varname>/<varname>Q</varname>,
@ -585,9 +590,40 @@ w- /proc/sys/vm/swappiness - - - - 10</programlisting></para>
<para>The age of a file system entry is determined from its last
modification timestamp (mtime), its last access timestamp (atime),
and (except for directories) its last status change timestamp
(ctime). Any of these three (or two) values will prevent cleanup
if it is more recent than the current time minus the age
field.</para>
(ctime). By default, any of these three (or two) values will
prevent cleanup if it is more recent than the current time minus
the age field. To restrict the deletion based on particular type
of file timestamps, the age-by argument can be used.</para>
<para>The age-by argument, when (optionally) specified along
with age will check if the file system entry has aged by the
type of file timestamp(s) provided. It can be specified by
prefixing the age argument with a set of file timestamp types
followed by a colon character <literal>:</literal>, i.e.,
<literal><replaceable>age-by</replaceable>:<replaceable>cleanup-age</replaceable></literal>.
The argument can be a set of:
<constant>a</constant> (<constant>A</constant> for directories),
<constant>b</constant> (<constant>B</constant> for directories),
<constant>c</constant> (<constant>C</constant> for directories; ignored by default), or
<constant>m</constant> (<constant>M</constant> for directories),
indicating access, creation, last status change, and last
modification times of a file system entry respectively. See
<citerefentry project='man-pages'><refentrytitle>statx</refentrytitle><manvolnum>2</manvolnum></citerefentry>
file timestamp fields for more details.</para>
<para>If unspecified, the age-by field defaults to
<constant>abcmABM</constant>,
i.e., by default all file timestamps are taken into consideration,
with the exception of the last status change timestamp (ctime) for
directories. This is because the aging logic itself will alter the
ctime whenever it deletes a file inside it. To ensure that running
the aging logic does not feed back into the next iteration of it,
ctime for directories is ignored by default.</para>
<para>For example:<programlisting>
# Files created and modified, and directories accessed more than
# an hour ago in "/tmp/foo/bar", are subject to time-based cleanup.
d /tmp/foo/bar - - - - bmA:1h -</programlisting></para>
<para>Note that while the aging algorithm is run a 'shared' BSD file lock (see <citerefentry
project='man-pages'><refentrytitle>flock</refentrytitle><manvolnum>2</manvolnum></citerefentry>) is

View File

@ -5,13 +5,14 @@
# Daniel Mustieles <daniel.mustieles@gmail.com>, 2015.
# Álex Puchades <alex94puchades@gmail.com>, 2015.
# Adolfo Jayme Barrientos <fitoschido@gmail.com>, 2020.
# Emilio Herrera <ehespinosa57@gmail.com>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: systemd master\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-08 17:48+0100\n"
"PO-Revision-Date: 2020-08-24 07:29+0000\n"
"Last-Translator: Adolfo Jayme Barrientos <fitoschido@gmail.com>\n"
"PO-Revision-Date: 2021-06-08 09:04+0000\n"
"Last-Translator: Emilio Herrera <ehespinosa57@gmail.com>\n"
"Language-Team: Spanish <https://translate.fedoraproject.org/projects/systemd/"
"master/es/>\n"
"Language: es\n"
@ -19,7 +20,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.2.1\n"
"X-Generator: Weblate 4.6.2\n"
#: src/core/org.freedesktop.systemd1.policy.in:22
msgid "Send passphrase back to system"
@ -73,49 +74,38 @@ msgstr "Necesita autenticarse para recargar el estado de systemd."
#: src/home/org.freedesktop.home1.policy:13
msgid "Create a home area"
msgstr ""
msgstr "Crear un área home"
#: src/home/org.freedesktop.home1.policy:14
#, fuzzy
#| msgid "Authentication is required to reload the systemd state."
msgid "Authentication is required to create a user's home area."
msgstr "Se requiere autenticación para recargar el estado de systemd."
msgstr "Se requiere autenticación para crear un área home de usuario."
#: src/home/org.freedesktop.home1.policy:23
msgid "Remove a home area"
msgstr ""
msgstr "Quitar un área home"
#: src/home/org.freedesktop.home1.policy:24
#, fuzzy
#| msgid "Authentication is required to reload the systemd state."
msgid "Authentication is required to remove a user's home area."
msgstr "Se requiere autenticación para recargar el estado de systemd."
msgstr "Se requiere autenticación para quitar un área home de usuario."
#: src/home/org.freedesktop.home1.policy:33
msgid "Check credentials of a home area"
msgstr ""
msgstr "Comprobar las credenciales de un área home"
#: src/home/org.freedesktop.home1.policy:34
#, fuzzy
#| msgid ""
#| "Authentication is required to manage active sessions, users and seats."
msgid ""
"Authentication is required to check credentials against a user's home area."
msgstr ""
"Se requiere autenticación para administrar las sesiones activas, usuarios y "
"puestos de trabajo."
"Se requiere autenticación para comprobar las credenciales contra un área "
"home de usuario."
#: src/home/org.freedesktop.home1.policy:43
msgid "Update a home area"
msgstr ""
msgstr "Actualizar un área home"
#: src/home/org.freedesktop.home1.policy:44
#, fuzzy
#| msgid "Authentication is required to attach a device to a seat."
msgid "Authentication is required to update a user's home area."
msgstr ""
"Se requiere autenticación para conectar un dispositivo a un puesto de "
"trabajo."
msgstr "Se requiere autenticación para actualizar un área home de usuario."
#: src/home/org.freedesktop.home1.policy:53
msgid "Resize a home area"

View File

@ -733,7 +733,7 @@ int inotify_add_watch_and_warn(int fd, const char *pathname, uint32_t mask) {
return wd;
}
static bool unsafe_transition(const struct stat *a, const struct stat *b) {
bool unsafe_transition(const struct stat *a, const struct stat *b) {
/* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
* privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files
* making us believe we read something safe even though it isn't safe in the specific context we open it in. */

View File

@ -94,6 +94,8 @@ enum {
CHASE_WARN = 1 << 7, /* Emit an appropriate warning when an error is encountered */
};
bool unsafe_transition(const struct stat *a, const struct stat *b);
/* How many iterations to execute before returning -ELOOP */
#define CHASE_SYMLINKS_MAX 32

View File

@ -48,11 +48,7 @@ static int patch_dirfd_mode(
return 0;
}
static int unlinkat_harder(
int dfd,
const char *filename,
int unlink_flags,
RemoveFlags remove_flags) {
int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags) {
mode_t old_mode;
int r;
@ -77,13 +73,15 @@ static int unlinkat_harder(
return r;
}
/* If this worked, we won't reset the old mode, since we'll need it for other entries too, and we
* should destroy the whole thing */
if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode) < 0)
return -errno;
/* If this worked, we won't reset the old mode by default, since we'll need it for other entries too,
* and we should destroy the whole thing */
return 0;
}
static int fstatat_harder(
int dfd,
int fstatat_harder(int dfd,
const char *filename,
struct stat *ret,
int fstatat_flags,
@ -109,6 +107,9 @@ static int fstatat_harder(
return r;
}
if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode) < 0)
return -errno;
return 0;
}

View File

@ -12,9 +12,17 @@ typedef enum RemoveFlags {
REMOVE_PHYSICAL = 1 << 2, /* If not set, only removes files on tmpfs, never physical file systems */
REMOVE_SUBVOLUME = 1 << 3, /* Drop btrfs subvolumes in the tree too */
REMOVE_MISSING_OK = 1 << 4, /* If the top-level directory is missing, ignore the ENOENT for it */
REMOVE_CHMOD = 1 << 5, /* chmod() for write access if we cannot delete something */
REMOVE_CHMOD = 1 << 5, /* chmod() for write access if we cannot delete or access something */
REMOVE_CHMOD_RESTORE = 1 << 6, /* Restore the old mode before returning */
} RemoveFlags;
int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags);
int fstatat_harder(int dfd,
const char *filename,
struct stat *ret,
int fstatat_flags,
RemoveFlags remove_flags);
int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev);
int rm_rf(const char *path, RemoveFlags flags);

View File

@ -1087,6 +1087,7 @@ const UnitVTable automount_vtable = {
.can_transient = true,
.can_fail = true,
.can_trigger = true,
.exclude_from_switch_root_serialization = true,
.init = automount_init,
.load = automount_load,

View File

@ -3402,6 +3402,77 @@ int manager_notify_cgroup_empty(Manager *m, const char *cgroup) {
return 1;
}
int unit_get_memory_available(Unit *u, uint64_t *ret) {
uint64_t unit_current, available = UINT64_MAX;
CGroupContext *unit_context;
const char *memory_file;
int r;
assert(u);
assert(ret);
/* If data from cgroups can be accessed, try to find out how much more memory a unit can
* claim before hitting the configured cgroup limits (if any). Consider both MemoryHigh
* and MemoryMax, and also any slice the unit might be nested below. */
if (!UNIT_CGROUP_BOOL(u, memory_accounting))
return -ENODATA;
if (!u->cgroup_path)
return -ENODATA;
/* The root cgroup doesn't expose this information */
if (unit_has_host_root_cgroup(u))
return -ENODATA;
if ((u->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0)
return -ENODATA;
r = cg_all_unified();
if (r < 0)
return r;
memory_file = r > 0 ? "memory.current" : "memory.usage_in_bytes";
r = cg_get_attribute_as_uint64("memory", u->cgroup_path, memory_file, &unit_current);
if (r < 0)
return r;
assert_se(unit_context = unit_get_cgroup_context(u));
if (unit_context->memory_max != UINT64_MAX || unit_context->memory_high != UINT64_MAX)
available = LESS_BY(MIN(unit_context->memory_max, unit_context->memory_high), unit_current);
for (Unit *slice = UNIT_GET_SLICE(u); slice; slice = UNIT_GET_SLICE(slice)) {
uint64_t slice_current, slice_available = UINT64_MAX;
CGroupContext *slice_context;
/* No point in continuing if we can't go any lower */
if (available == 0)
break;
if (!slice->cgroup_path)
continue;
slice_context = unit_get_cgroup_context(slice);
if (!slice_context)
continue;
if (slice_context->memory_max == UINT64_MAX && slice_context->memory_high == UINT64_MAX)
continue;
r = cg_get_attribute_as_uint64("memory", slice->cgroup_path, memory_file, &slice_current);
if (r < 0)
continue;
slice_available = LESS_BY(MIN(slice_context->memory_max, slice_context->memory_high), slice_current);
available = MIN(slice_available, available);
}
*ret = available;
return 0;
}
int unit_get_memory_current(Unit *u, uint64_t *ret) {
int r;

View File

@ -282,6 +282,7 @@ int unit_watch_all_pids(Unit *u);
int unit_synthesize_cgroup_empty_event(Unit *u);
int unit_get_memory_current(Unit *u, uint64_t *ret);
int unit_get_memory_available(Unit *u, uint64_t *ret);
int unit_get_tasks_current(Unit *u, uint64_t *ret);
int unit_get_cpu_usage(Unit *u, nsec_t *ret);
int unit_get_io_accounting(Unit *u, CGroupIOAccountingMetric metric, bool allow_cache, uint64_t *ret);

View File

@ -1097,6 +1097,30 @@ static int property_get_current_memory(
return sd_bus_message_append(reply, "t", sz);
}
static int property_get_available_memory(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
uint64_t sz = UINT64_MAX;
Unit *u = userdata;
int r;
assert(bus);
assert(reply);
assert(u);
r = unit_get_memory_available(u, &sz);
if (r < 0 && r != -ENODATA)
log_unit_warning_errno(u, r, "Failed to get total available memory from cgroup: %m");
return sd_bus_message_append(reply, "t", sz);
}
static int property_get_current_tasks(
sd_bus *bus,
const char *path,
@ -1541,6 +1565,7 @@ const sd_bus_vtable bus_unit_cgroup_vtable[] = {
SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0),
SD_BUS_PROPERTY("ControlGroup", "s", property_get_cgroup, 0, 0),
SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0),
SD_BUS_PROPERTY("MemoryAvailable", "t", property_get_available_memory, 0, 0),
SD_BUS_PROPERTY("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0),
SD_BUS_PROPERTY("EffectiveCPUs", "ay", property_get_cpuset_cpus, 0, 0),
SD_BUS_PROPERTY("EffectiveMemoryNodes", "ay", property_get_cpuset_mems, 0, 0),

View File

@ -3321,11 +3321,7 @@ int manager_serialize(
if (u->id != t)
continue;
/* Start marker */
fputs(u->id, f);
fputc('\n', f);
r = unit_serialize(u, f, fds, !switching_root);
r = unit_serialize(u, f, fds, switching_root);
if (r < 0)
return r;
}

View File

@ -2176,6 +2176,7 @@ const UnitVTable mount_vtable = {
.can_transient = true,
.can_fail = true,
.exclude_from_switch_root_serialization = true,
.init = mount_init,
.load = mount_load,

View File

@ -2626,13 +2626,6 @@ static int socket_serialize(Unit *u, FILE *f, FDSet *fds) {
return 0;
}
static void socket_port_take_fd(SocketPort *p, FDSet *fds, int fd) {
assert(p);
safe_close(p->fd);
p->fd = fdset_remove(fds, fd);
}
static int socket_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
Socket *s = SOCKET(u);
@ -2694,13 +2687,20 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value,
if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd))
log_unit_debug(u, "Failed to parse fifo value: %s", value);
else
else {
bool found = false;
LIST_FOREACH(port, p, s->ports)
if (p->type == SOCKET_FIFO &&
if (p->fd < 0 &&
p->type == SOCKET_FIFO &&
path_equal_or_files_same(p->path, value+skip, 0)) {
socket_port_take_fd(p, fds, fd);
p->fd = fdset_remove(fds, fd);
found = true;
break;
}
if (!found)
log_unit_debug(u, "No matching fifo socket found: %s", value+skip);
}
} else if (streq(key, "special")) {
int fd, skip = 0;
@ -2708,13 +2708,20 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value,
if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd))
log_unit_debug(u, "Failed to parse special value: %s", value);
else
else {
bool found = false;
LIST_FOREACH(port, p, s->ports)
if (p->type == SOCKET_SPECIAL &&
if (p->fd < 0 &&
p->type == SOCKET_SPECIAL &&
path_equal_or_files_same(p->path, value+skip, 0)) {
socket_port_take_fd(p, fds, fd);
p->fd = fdset_remove(fds, fd);
found = true;
break;
}
if (!found)
log_unit_debug(u, "No matching special socket found: %s", value+skip);
}
} else if (streq(key, "mqueue")) {
int fd, skip = 0;
@ -2722,13 +2729,20 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value,
if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd))
log_unit_debug(u, "Failed to parse mqueue value: %s", value);
else
else {
bool found = false;
LIST_FOREACH(port, p, s->ports)
if (p->type == SOCKET_MQUEUE &&
if (p->fd < 0 &&
p->type == SOCKET_MQUEUE &&
streq(p->path, value+skip)) {
socket_port_take_fd(p, fds, fd);
p->fd = fdset_remove(fds, fd);
found = true;
break;
}
if (!found)
log_unit_debug(u, "No matching mqueue socket found: %s", value+skip);
}
} else if (streq(key, "socket")) {
int fd, type, skip = 0;
@ -2736,12 +2750,20 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value,
if (sscanf(value, "%i %i %n", &fd, &type, &skip) < 2 || fd < 0 || type < 0 || !fdset_contains(fds, fd))
log_unit_debug(u, "Failed to parse socket value: %s", value);
else
else {
bool found = false;
LIST_FOREACH(port, p, s->ports)
if (socket_address_is(&p->address, value+skip, type)) {
socket_port_take_fd(p, fds, fd);
if (p->fd < 0 &&
socket_address_is(&p->address, value+skip, type)) {
p->fd = fdset_remove(fds, fd);
found = true;
break;
}
if (!found)
log_unit_debug(u, "No matching %s socket found: %s",
socket_address_type_to_string(type), value+skip);
}
} else if (streq(key, "netlink")) {
int fd, skip = 0;
@ -2749,12 +2771,19 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value,
if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd))
log_unit_debug(u, "Failed to parse socket value: %s", value);
else
else {
bool found = false;
LIST_FOREACH(port, p, s->ports)
if (socket_address_is_netlink(&p->address, value+skip)) {
socket_port_take_fd(p, fds, fd);
if (p->fd < 0 &&
socket_address_is_netlink(&p->address, value+skip)) {
p->fd = fdset_remove(fds, fd);
found = true;
break;
}
if (!found)
log_unit_debug(u, "No matching netlink socket found: %s", value+skip);
}
} else if (streq(key, "ffs")) {
int fd, skip = 0;
@ -2762,13 +2791,20 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value,
if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd))
log_unit_debug(u, "Failed to parse ffs value: %s", value);
else
else {
bool found = false;
LIST_FOREACH(port, p, s->ports)
if (p->type == SOCKET_USB_FUNCTION &&
if (p->fd < 0 &&
p->type == SOCKET_USB_FUNCTION &&
path_equal_or_files_same(p->path, value+skip, 0)) {
socket_port_take_fd(p, fds, fd);
p->fd = fdset_remove(fds, fd);
found = true;
break;
}
if (!found)
log_unit_debug(u, "No matching ffs socket found: %s", value+skip);
}
} else
log_unit_debug(UNIT(s), "Unknown serialization key: %s", key);

View File

@ -89,13 +89,25 @@ static const char *const io_accounting_metric_field_last[_CGROUP_IO_ACCOUNTING_M
[CGROUP_IO_WRITE_OPERATIONS] = "io-accounting-write-operations-last",
};
int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool switching_root) {
int r;
assert(u);
assert(f);
assert(fds);
if (switching_root && UNIT_VTABLE(u)->exclude_from_switch_root_serialization) {
/* In the new root, paths for mounts and automounts will be different, so it doesn't make
* much sense to serialize things. API file systems will be moved to the new root, but we
* don't have mount units for those. */
log_unit_debug(u, "not serializing before switch-root");
return 0;
}
/* Start marker */
fputs(u->id, f);
fputc('\n', f);
if (unit_can_serialize(u)) {
r = UNIT_VTABLE(u)->serialize(u, f, fds);
if (r < 0)
@ -175,7 +187,7 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
(void) serialize_item_format(f, ip_accounting_metric_field[m], "%" PRIu64, v);
}
if (serialize_jobs) {
if (!switching_root) {
if (u->job) {
fputs("job\n", f);
job_serialize(u->job, f);

View File

@ -543,8 +543,8 @@ typedef struct UnitVTable {
bool (*can_reload)(Unit *u);
/* Write all data that cannot be restored from other sources
* away using unit_serialize_item() */
/* Serialize state and file descriptors that should be carried over into the new
* instance after reexecution. */
int (*serialize)(Unit *u, FILE *f, FDSet *fds);
/* Restore one item from the serialization */
@ -646,7 +646,7 @@ typedef struct UnitVTable {
/* Type specific cleanups. */
void (*shutdown)(Manager *m);
/* If this function is set and return false all jobs for units
/* If this function is set and returns false all jobs for units
* of this type will immediately fail. */
bool (*supported)(void);
@ -654,25 +654,28 @@ typedef struct UnitVTable {
UnitStatusMessageFormats status_message_formats;
/* True if transient units of this type are OK */
bool can_transient:1;
bool can_transient;
/* True if cgroup delegation is permissible */
bool can_delegate:1;
bool can_delegate;
/* True if the unit type triggers other units, i.e. can have a UNIT_TRIGGERS dependency */
bool can_trigger:1;
bool can_trigger;
/* True if the unit type knows a failure state, and thus can be source of an OnFailure= dependency */
bool can_fail:1;
bool can_fail;
/* True if units of this type shall be startable only once and then never again */
bool once_only:1;
bool once_only;
/* Do not serialize this unit when preparing for root switch */
bool exclude_from_switch_root_serialization;
/* True if queued jobs of this type should be GC'ed if no other job needs them anymore */
bool gc_jobs:1;
bool gc_jobs;
/* True if systemd-oomd can monitor and act on this unit's recursive children's cgroup(s) */
bool can_set_managed_oom:1;
bool can_set_managed_oom;
} UnitVTable;
extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX];

View File

@ -3,6 +3,7 @@
#include <errno.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/statvfs.h>
#include <sys/xattr.h>
#include <unistd.h>
@ -18,6 +19,7 @@
#include "acl-util.h"
#include "alloc-util.h"
#include "bus-error.h"
#include "capability-util.h"
#include "cgroup-util.h"
#include "compress.h"
@ -42,6 +44,7 @@
#include "socket-util.h"
#include "special.h"
#include "stacktrace.h"
#include "stat-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
@ -63,6 +66,10 @@
#define JOURNAL_SIZE_MAX ((size_t) (10LU*1024LU*1024LU))
#endif
/* When checking for available memory and setting lower limits, don't
* go below 4MB for writing core files to storage. */
#define PROCESS_SIZE_MIN (4U*1024U*1024U)
/* Make sure to not make this larger than the maximum journal entry
* size. See DATA_SIZE_MAX in journal-importer.h. */
assert_cc(JOURNAL_SIZE_MAX <= DATA_SIZE_MAX);
@ -329,11 +336,14 @@ static int save_external_coredump(
int *ret_node_fd,
int *ret_data_fd,
uint64_t *ret_size,
uint64_t *ret_compressed_size,
bool *ret_truncated) {
_cleanup_free_ char *fn = NULL, *tmp = NULL;
_cleanup_(unlink_and_freep) char *tmp = NULL;
_cleanup_free_ char *fn = NULL;
_cleanup_close_ int fd = -1;
uint64_t rlimit, process_limit, max_size;
bool truncated, storage_on_tmpfs;
struct stat st;
uid_t uid;
int r;
@ -343,6 +353,8 @@ static int save_external_coredump(
assert(ret_node_fd);
assert(ret_data_fd);
assert(ret_size);
assert(ret_compressed_size);
assert(ret_truncated);
r = parse_uid(context->meta[META_ARGV_UID], &uid);
if (r < 0)
@ -379,92 +391,145 @@ static int save_external_coredump(
if (fd < 0)
return log_error_errno(fd, "Failed to create temporary file for coredump %s: %m", fn);
r = copy_bytes(input_fd, fd, max_size, 0);
if (r < 0) {
log_error_errno(r, "Cannot store coredump of %s (%s): %m",
context->meta[META_ARGV_PID], context->meta[META_COMM]);
goto fail;
/* If storage is on tmpfs, the kernel oomd might kill us if there's MemoryMax set on
* the service or the slice it belongs to. This is common on low-resources systems,
* to avoid crashing processes to take away too many system resources.
* Check the cgroup settings, and set max_size to a bit less than half of the
* available memory left to the process.
* Then, attempt to write the core file uncompressed first - if the write gets
* interrupted, we know we won't be able to write it all, so instead compress what
* was written so far, delete the uncompressed truncated core, and then continue
* compressing from STDIN. Given the compressed core cannot be larger than the
* uncompressed one, and 1KB for metadata is accounted for in the calculation, we
* should be able to at least store the full compressed core file. */
storage_on_tmpfs = fd_is_temporary_fs(fd) > 0;
if (storage_on_tmpfs && arg_compress) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
uint64_t cgroup_limit = UINT64_MAX;
struct statvfs sv;
/* If we can't get the cgroup limit, just ignore it, but don't fail,
* try anyway with the config settings. */
r = sd_bus_default_system(&bus);
if (r < 0)
log_info_errno(r, "Failed to connect to system bus, skipping MemoryAvailable check: %m");
else {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = sd_bus_get_property_trivial(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1/unit/self",
"org.freedesktop.systemd1.Service",
"MemoryAvailable",
&error,
't', &cgroup_limit);
if (r < 0)
log_warning_errno(r,
"Failed to query MemoryAvailable for current unit, "
"falling back to static config settings: %s",
bus_error_message(&error, r));
}
*ret_truncated = r == 1;
if (*ret_truncated)
max_size = MIN(cgroup_limit, max_size);
max_size = LESS_BY(max_size, 1024U) / 2; /* Account for 1KB metadata overhead for compressing */
max_size = MAX(PROCESS_SIZE_MIN, max_size); /* Impose a lower minimum */
/* tmpfs might get full quickly, so check the available space too.
* But don't worry about errors here, failing to access the storage
* location will be better logged when writing to it. */
if (statvfs("/var/lib/systemd/coredump/", &sv) >= 0)
max_size = MIN((uint64_t)sv.f_frsize * (uint64_t)sv.f_bfree, max_size);
log_debug("Limiting core file size to %" PRIu64 " bytes due to cgroup memory limits.", max_size);
}
r = copy_bytes(input_fd, fd, max_size, 0);
if (r < 0)
return log_error_errno(r, "Cannot store coredump of %s (%s): %m",
context->meta[META_ARGV_PID], context->meta[META_COMM]);
truncated = r == 1;
#if HAVE_COMPRESSION
if (arg_compress) {
_cleanup_(unlink_and_freep) char *tmp_compressed = NULL;
_cleanup_free_ char *fn_compressed = NULL;
_cleanup_close_ int fd_compressed = -1;
uint64_t uncompressed_size = 0;
if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
return log_error_errno(errno, "Failed to seek on coredump %s: %m", fn);
fn_compressed = strjoin(fn, COMPRESSED_EXT);
if (!fn_compressed)
return log_oom();
fd_compressed = open_tmpfile_linkable(fn_compressed, O_RDWR|O_CLOEXEC, &tmp_compressed);
if (fd_compressed < 0)
return log_error_errno(fd_compressed, "Failed to create temporary file for coredump %s: %m", fn_compressed);
r = compress_stream(fd, fd_compressed, max_size, &uncompressed_size);
if (r < 0)
return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed));
if (truncated && storage_on_tmpfs) {
uint64_t partial_uncompressed_size = 0;
/* Uncompressed write was truncated and we are writing to tmpfs: delete
* the uncompressed core, and compress the remaining part from STDIN. */
tmp = unlink_and_free(tmp);
fd = safe_close(fd);
r = compress_stream(input_fd, fd_compressed, max_size, &partial_uncompressed_size);
if (r < 0)
return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed));
uncompressed_size += partial_uncompressed_size;
}
r = fix_permissions(fd_compressed, tmp_compressed, fn_compressed, context, uid);
if (r < 0)
return r;
if (fstat(fd_compressed, &st) < 0)
return log_error_errno(errno,
"Failed to fstat core file %s: %m",
coredump_tmpfile_name(tmp_compressed));
*ret_filename = TAKE_PTR(fn_compressed); /* compressed */
*ret_node_fd = TAKE_FD(fd_compressed); /* compressed */
*ret_compressed_size = (uint64_t) st.st_size; /* compressed */
*ret_data_fd = TAKE_FD(fd);
*ret_size = uncompressed_size;
*ret_truncated = truncated;
tmp_compressed = mfree(tmp_compressed);
return 0;
}
#endif
if (truncated)
log_struct(LOG_INFO,
LOG_MESSAGE("Core file was truncated to %zu bytes.", max_size),
"SIZE_LIMIT=%zu", max_size,
"MESSAGE_ID=" SD_MESSAGE_TRUNCATED_CORE_STR);
if (fstat(fd, &st) < 0) {
log_error_errno(errno, "Failed to fstat core file %s: %m", coredump_tmpfile_name(tmp));
goto fail;
}
if (lseek(fd, 0, SEEK_SET) == (off_t) -1) {
log_error_errno(errno, "Failed to seek on %s: %m", coredump_tmpfile_name(tmp));
goto fail;
}
#if HAVE_COMPRESSION
/* If we will remove the coredump anyway, do not compress. */
if (arg_compress && !maybe_remove_external_coredump(NULL, st.st_size)) {
_cleanup_free_ char *fn_compressed = NULL, *tmp_compressed = NULL;
_cleanup_close_ int fd_compressed = -1;
fn_compressed = strjoin(fn, COMPRESSED_EXT);
if (!fn_compressed) {
log_oom();
goto uncompressed;
}
fd_compressed = open_tmpfile_linkable(fn_compressed, O_RDWR|O_CLOEXEC, &tmp_compressed);
if (fd_compressed < 0) {
log_error_errno(fd_compressed, "Failed to create temporary file for coredump %s: %m", fn_compressed);
goto uncompressed;
}
r = compress_stream(fd, fd_compressed, -1);
if (r < 0) {
log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed));
goto fail_compressed;
}
r = fix_permissions(fd_compressed, tmp_compressed, fn_compressed, context, uid);
if (r < 0)
goto fail_compressed;
/* OK, this worked, we can get rid of the uncompressed version now */
if (tmp)
unlink_noerrno(tmp);
*ret_filename = TAKE_PTR(fn_compressed); /* compressed */
*ret_node_fd = TAKE_FD(fd_compressed); /* compressed */
*ret_data_fd = TAKE_FD(fd); /* uncompressed */
*ret_size = (uint64_t) st.st_size; /* uncompressed */
return 0;
fail_compressed:
if (tmp_compressed)
(void) unlink(tmp_compressed);
}
uncompressed:
#endif
r = fix_permissions(fd, tmp, fn, context, uid);
if (r < 0)
goto fail;
return log_error_errno(r, "Failed to fix permissions and finalize coredump %s into %s: %m", coredump_tmpfile_name(tmp), fn);
if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Failed to fstat core file %s: %m", coredump_tmpfile_name(tmp));
if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
return log_error_errno(errno, "Failed to seek on coredump %s: %m", fn);
*ret_filename = TAKE_PTR(fn);
*ret_data_fd = TAKE_FD(fd);
*ret_node_fd = -1;
*ret_size = (uint64_t) st.st_size;
*ret_truncated = truncated;
return 0;
fail:
if (tmp)
(void) unlink(tmp);
return r;
}
static int allocate_journal_field(int fd, size_t size, char **ret, size_t *ret_size) {
@ -709,7 +774,7 @@ static int submit_coredump(
_cleanup_free_ char *stacktrace = NULL;
char *core_message;
const char *module_name;
uint64_t coredump_size = UINT64_MAX;
uint64_t coredump_size = UINT64_MAX, coredump_compressed_size = UINT64_MAX;
bool truncated = false;
JsonVariant *module_json;
int r;
@ -722,7 +787,8 @@ static int submit_coredump(
/* Always stream the coredump to disk, if that's possible */
r = save_external_coredump(context, input_fd,
&filename, &coredump_node_fd, &coredump_fd, &coredump_size, &truncated);
&filename, &coredump_node_fd, &coredump_fd,
&coredump_size, &coredump_compressed_size, &truncated);
if (r < 0)
/* Skip whole core dumping part */
goto log;
@ -730,7 +796,7 @@ static int submit_coredump(
/* If we don't want to keep the coredump on disk, remove it now, as later on we
* will lack the privileges for it. However, we keep the fd to it, so that we can
* still process it and log it. */
r = maybe_remove_external_coredump(filename, coredump_size);
r = maybe_remove_external_coredump(filename, coredump_node_fd >= 0 ? coredump_compressed_size : coredump_size);
if (r < 0)
return r;
if (r == 0) {
@ -738,7 +804,7 @@ static int submit_coredump(
} else if (arg_storage == COREDUMP_STORAGE_EXTERNAL)
log_info("The core will not be stored: size %"PRIu64" is greater than %"PRIu64" (the configured maximum)",
coredump_size, arg_external_size_max);
coredump_node_fd >= 0 ? coredump_compressed_size : coredump_size, arg_external_size_max);
/* Vacuum again, but exclude the coredump we just created */
(void) coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use);
@ -758,7 +824,7 @@ static int submit_coredump(
log_debug("Not generating stack trace: core size %"PRIu64" is greater "
"than %"PRIu64" (the configured maximum)",
coredump_size, arg_process_size_max);
} else
} else if (coredump_fd >= 0)
coredump_parse_core(coredump_fd, context->meta[META_EXE], &stacktrace, &json_metadata);
#endif
@ -812,7 +878,7 @@ log:
}
/* Optionally store the entire coredump in the journal */
if (arg_storage == COREDUMP_STORAGE_JOURNAL) {
if (arg_storage == COREDUMP_STORAGE_JOURNAL && coredump_fd >= 0) {
if (coredump_size <= arg_journal_size_max) {
size_t sz = 0;

View File

@ -550,7 +550,7 @@ int decompress_startswith(
return -EBADMSG;
}
int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes) {
int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) {
#if HAVE_XZ
_cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT;
lzma_ret ret;
@ -611,6 +611,9 @@ int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes) {
return k;
if (ret == LZMA_STREAM_END) {
if (ret_uncompressed_size)
*ret_uncompressed_size = s.total_in;
log_debug("XZ compression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)",
s.total_in, s.total_out,
(double) s.total_out / s.total_in * 100);
@ -626,14 +629,15 @@ int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes) {
#define LZ4_BUFSIZE (512*1024u)
int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) {
int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) {
#if HAVE_LZ4
LZ4F_errorCode_t c;
_cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL;
_cleanup_free_ void *in_buff = NULL;
_cleanup_free_ char *out_buff = NULL;
size_t out_allocsize, n, total_in = 0, total_out, offset = 0, frame_size;
size_t out_allocsize, n, offset = 0, frame_size;
uint64_t total_in = 0, total_out;
int r;
static const LZ4F_preferences_t preferences = {
.frameInfo.blockSizeID = 5,
@ -698,7 +702,10 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) {
if (r < 0)
return r;
log_debug("LZ4 compression finished (%zu -> %zu bytes, %.1f%%)",
if (ret_uncompressed_size)
*ret_uncompressed_size = total_in;
log_debug("LZ4 compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)",
total_in, total_out,
(double) total_out / total_in * 100);
@ -844,7 +851,7 @@ int decompress_stream_lz4(int in, int out, uint64_t max_bytes) {
#endif
}
int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) {
int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) {
#if HAVE_ZSTD
_cleanup_(ZSTD_freeCCtxp) ZSTD_CCtx *cctx = NULL;
_cleanup_free_ void *in_buff = NULL, *out_buff = NULL;
@ -933,6 +940,9 @@ int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) {
break;
}
if (ret_uncompressed_size)
*ret_uncompressed_size = in_bytes;
if (in_bytes > 0)
log_debug("ZSTD compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)",
in_bytes, max_bytes - left, (double) (max_bytes - left) / in_bytes * 100);

View File

@ -64,9 +64,9 @@ int decompress_startswith(int compression,
const void *prefix, size_t prefix_len,
uint8_t extra);
int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes);
int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes);
int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes);
int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size);
int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size);
int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size);
int decompress_stream_xz(int fdf, int fdt, uint64_t max_size);
int decompress_stream_lz4(int fdf, int fdt, uint64_t max_size);
@ -82,7 +82,7 @@ int decompress_stream_zstd(int fdf, int fdt, uint64_t max_size);
# define compress_stream compress_stream_xz
# define COMPRESSED_EXT ".xz"
#else
static inline int compress_stream(int fdf, int fdt, uint64_t max_size) {
static inline int compress_stream(int fdf, int fdt, uint64_t max_size, uint64_t *ret_uncompressed_size) {
return -EOPNOTSUPP;
}
# define COMPRESSED_EXT ""

View File

@ -41,7 +41,7 @@ typedef int (decompress_sw_t)(const void *src, uint64_t src_size,
const void *prefix, size_t prefix_len,
uint8_t extra);
typedef int (compress_stream_t)(int fdf, int fdt, uint64_t max_bytes);
typedef int (compress_stream_t)(int fdf, int fdt, uint64_t max_bytes, uint64_t *uncompressed_size);
typedef int (decompress_stream_t)(int fdf, int fdt, uint64_t max_size);
#if HAVE_COMPRESSION
@ -176,6 +176,7 @@ _unused_ static void test_compress_stream(const char *compression,
int r;
_cleanup_free_ char *cmd = NULL, *cmd2 = NULL;
struct stat st = {};
uint64_t uncompressed_size;
r = find_executable(cat, NULL);
if (r < 0) {
@ -193,7 +194,7 @@ _unused_ static void test_compress_stream(const char *compression,
assert_se((dst = mkostemp_safe(pattern)) >= 0);
assert_se(compress(src, dst, -1) == 0);
assert_se(compress(src, dst, -1, &uncompressed_size) == 0);
if (cat) {
assert_se(asprintf(&cmd, "%s %s | diff %s -", cat, pattern, srcfile) > 0);
@ -205,6 +206,7 @@ _unused_ static void test_compress_stream(const char *compression,
assert_se((dst2 = mkostemp_safe(pattern2)) >= 0);
assert_se(stat(srcfile, &st) == 0);
assert_se((uint64_t)st.st_size == uncompressed_size);
assert_se(lseek(dst, 0, SEEK_SET) == 0);
r = decompress(dst, dst2, st.st_size);

View File

@ -37,7 +37,6 @@ int main(int argc, char **argv) {
test_table(netdev_kind, NETDEV_KIND);
test_table(nl_union_link_info_data, NL_UNION_LINK_INFO_DATA);
test_table(radv_prefix_delegation, RADV_PREFIX_DELEGATION);
test_table(wol, WOL);
test_table(lldp_event, SD_LLDP_EVENT);
test_table(ndisc_event, SD_NDISC_EVENT);
test_table(dhcp_lease_server_type, SD_DHCP_LEASE_SERVER_TYPE);

View File

@ -165,7 +165,7 @@ static int bus_print_property(const char *name, const char *expected_value, sd_b
bus_print_property_value(name, expected_value, flags, "[not set]");
else if ((STR_IN_SET(name, "DefaultMemoryLow", "DefaultMemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) ||
else if ((STR_IN_SET(name, "DefaultMemoryLow", "DefaultMemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "MemoryAvailable") && u == CGROUP_LIMIT_MAX) ||
(STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == UINT64_MAX) ||
(startswith(name, "Limit") && u == UINT64_MAX) ||
(startswith(name, "DefaultLimit") && u == UINT64_MAX))

View File

@ -24,19 +24,38 @@ static const char* const duplex_table[_DUP_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting");
static const char* const wol_table[_WOL_MAX] = {
[WOL_PHY] = "phy",
[WOL_UCAST] = "unicast",
[WOL_MCAST] = "multicast",
[WOL_BCAST] = "broadcast",
[WOL_ARP] = "arp",
[WOL_MAGIC] = "magic",
[WOL_MAGICSECURE] = "secureon",
[WOL_OFF] = "off",
static const struct {
uint32_t opt;
const char *name;
} wol_option_map[] = {
{ WAKE_PHY, "phy" },
{ WAKE_UCAST, "unicast", },
{ WAKE_MCAST, "multicast", },
{ WAKE_BCAST, "broadcast", },
{ WAKE_ARP, "arp", },
{ WAKE_MAGIC, "magic", },
{ WAKE_MAGICSECURE, "secureon", },
};
DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan);
DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting");
int wol_options_to_string_alloc(uint32_t opts, char **ret) {
_cleanup_free_ char *str = NULL;
assert(ret);
for (size_t i = 0; i < ELEMENTSOF(wol_option_map); i++)
if (opts & wol_option_map[i].opt &&
!strextend_with_separator(&str, ",", wol_option_map[i].name))
return -ENOMEM;
if (!str) {
str = strdup("off");
if (!str)
return -ENOMEM;
}
*ret = TAKE_PTR(str);
return 0;
}
static const char* const port_table[] = {
[NET_DEV_PORT_TP] = "tp",
@ -310,7 +329,7 @@ int ethtool_get_permanent_macaddr(int *ethtool_fd, const char *ifname, struct et
dest = _v; \
} while(false)
int ethtool_set_wol(int *ethtool_fd, const char *ifname, WakeOnLan wol) {
int ethtool_set_wol(int *ethtool_fd, const char *ifname, uint32_t wolopts) {
struct ethtool_wolinfo ecmd = {
.cmd = ETHTOOL_GWOL,
};
@ -323,7 +342,7 @@ int ethtool_set_wol(int *ethtool_fd, const char *ifname, WakeOnLan wol) {
assert(ethtool_fd);
assert(ifname);
if (wol == _WOL_INVALID)
if (wolopts == UINT32_MAX)
return 0;
r = ethtool_connect(ethtool_fd);
@ -336,66 +355,15 @@ int ethtool_set_wol(int *ethtool_fd, const char *ifname, WakeOnLan wol) {
if (r < 0)
return -errno;
switch (wol) {
case WOL_PHY:
if (ecmd.wolopts != WAKE_PHY) {
ecmd.wolopts = WAKE_PHY;
need_update = true;
}
break;
case WOL_UCAST:
if (ecmd.wolopts != WAKE_UCAST) {
ecmd.wolopts = WAKE_UCAST;
need_update = true;
}
break;
case WOL_MCAST:
if (ecmd.wolopts != WAKE_MCAST) {
ecmd.wolopts = WAKE_MCAST;
need_update = true;
}
break;
case WOL_BCAST:
if (ecmd.wolopts != WAKE_BCAST) {
ecmd.wolopts = WAKE_BCAST;
need_update = true;
}
break;
case WOL_ARP:
if (ecmd.wolopts != WAKE_ARP) {
ecmd.wolopts = WAKE_ARP;
need_update = true;
}
break;
case WOL_MAGIC:
if (ecmd.wolopts != WAKE_MAGIC) {
ecmd.wolopts = WAKE_MAGIC;
need_update = true;
}
break;
case WOL_MAGICSECURE:
if (ecmd.wolopts != WAKE_MAGICSECURE) {
ecmd.wolopts = WAKE_MAGICSECURE;
need_update = true;
}
break;
case WOL_OFF:
if (ecmd.wolopts != 0) {
ecmd.wolopts = 0;
need_update = true;
}
break;
default:
break;
}
UPDATE(ecmd.wolopts, wolopts, need_update);
if (!need_update)
return 0;
if (need_update) {
ecmd.cmd = ETHTOOL_SWOL;
r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
if (r < 0)
return -errno;
}
return 0;
}
@ -1005,7 +973,6 @@ int config_parse_advertise(
void *userdata) {
uint32_t *advertise = data;
const char *p;
int r;
assert(filename);
@ -1020,7 +987,7 @@ int config_parse_advertise(
return 0;
}
for (p = rvalue;;) {
for (const char *p = rvalue;;) {
_cleanup_free_ char *w = NULL;
enum ethtool_link_mode_bit_indices mode;
@ -1098,3 +1065,69 @@ int config_parse_nic_buffer_size(
return 0;
}
int config_parse_wol(
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) {
uint32_t new_opts = 0, *opts = data;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
if (isempty(rvalue)) {
*opts = UINT32_MAX; /* Do not update WOL option. */
return 0;
}
if (streq(rvalue, "off")) {
*opts = 0; /* Disable WOL. */
return 0;
}
for (const char *p = rvalue;;) {
_cleanup_free_ char *w = NULL;
bool found = false;
r = extract_first_word(&p, &w, NULL, 0);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to split wake-on-lan modes '%s', ignoring assignment: %m", rvalue);
return 0;
}
if (r == 0)
break;
for (size_t i = 0; i < ELEMENTSOF(wol_option_map); i++)
if (streq(w, wol_option_map[i].name)) {
new_opts |= wol_option_map[i].opt;
found = true;
break;
}
if (!found)
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Unknown wake-on-lan mode '%s', ignoring.", w);
}
if (*opts == UINT32_MAX)
*opts = new_opts;
else
*opts |= new_opts;
return 0;
}

View File

@ -18,19 +18,6 @@ typedef enum Duplex {
_DUP_INVALID = -EINVAL,
} Duplex;
typedef enum WakeOnLan {
WOL_PHY,
WOL_UCAST,
WOL_MCAST,
WOL_BCAST,
WOL_ARP,
WOL_MAGIC,
WOL_MAGICSECURE,
WOL_OFF,
_WOL_MAX,
_WOL_INVALID = -EINVAL,
} WakeOnLan;
typedef enum NetDevFeature {
NET_DEV_FEAT_RX,
NET_DEV_FEAT_TX,
@ -99,7 +86,7 @@ int ethtool_get_link_info(int *ethtool_fd, const char *ifname,
int *ret_autonegotiation, uint64_t *ret_speed,
Duplex *ret_duplex, NetDevPort *ret_port);
int ethtool_get_permanent_macaddr(int *ethtool_fd, const char *ifname, struct ether_addr *ret);
int ethtool_set_wol(int *ethtool_fd, const char *ifname, WakeOnLan wol);
int ethtool_set_wol(int *ethtool_fd, const char *ifname, uint32_t wolopts);
int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, const netdev_ring_param *ring);
int ethtool_set_features(int *ethtool_fd, const char *ifname, const int *features);
int ethtool_set_glinksettings(int *ethtool_fd, const char *ifname,
@ -111,8 +98,7 @@ int ethtool_set_flow_control(int *fd, const char *ifname, int rx, int tx, int au
const char *duplex_to_string(Duplex d) _const_;
Duplex duplex_from_string(const char *d) _pure_;
const char *wol_to_string(WakeOnLan wol) _const_;
WakeOnLan wol_from_string(const char *wol) _pure_;
int wol_options_to_string_alloc(uint32_t opts, char **ret);
const char *port_to_string(NetDevPort port) _const_;
NetDevPort port_from_string(const char *port) _pure_;

View File

@ -247,6 +247,7 @@ typedef struct UnitStatusInfo {
uint64_t memory_max;
uint64_t memory_swap_max;
uint64_t memory_limit;
uint64_t memory_available;
uint64_t cpu_usage_nsec;
uint64_t tasks_current;
uint64_t tasks_max;
@ -682,6 +683,7 @@ static void print_status_info(
if (i->memory_min > 0 || i->memory_low > 0 ||
i->memory_high != CGROUP_LIMIT_MAX || i->memory_max != CGROUP_LIMIT_MAX ||
i->memory_swap_max != CGROUP_LIMIT_MAX ||
i->memory_available != CGROUP_LIMIT_MAX ||
i->memory_limit != CGROUP_LIMIT_MAX) {
const char *prefix = "";
@ -710,6 +712,10 @@ static void print_status_info(
printf("%slimit: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_limit));
prefix = " ";
}
if (i->memory_available != CGROUP_LIMIT_MAX) {
printf("%savailable: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_available));
prefix = " ";
}
printf(")");
}
printf("\n");
@ -1827,6 +1833,7 @@ static int show_one(
{ "Where", "s", NULL, offsetof(UnitStatusInfo, where) },
{ "What", "s", NULL, offsetof(UnitStatusInfo, what) },
{ "MemoryCurrent", "t", NULL, offsetof(UnitStatusInfo, memory_current) },
{ "MemoryAvailable", "t", NULL, offsetof(UnitStatusInfo, memory_available) },
{ "DefaultMemoryMin", "t", NULL, offsetof(UnitStatusInfo, default_memory_min) },
{ "DefaultMemoryLow", "t", NULL, offsetof(UnitStatusInfo, default_memory_low) },
{ "MemoryMin", "t", NULL, offsetof(UnitStatusInfo, memory_min) },
@ -1869,6 +1876,7 @@ static int show_one(
.memory_max = CGROUP_LIMIT_MAX,
.memory_swap_max = CGROUP_LIMIT_MAX,
.memory_limit = UINT64_MAX,
.memory_available = CGROUP_LIMIT_MAX,
.cpu_usage_nsec = UINT64_MAX,
.tasks_current = UINT64_MAX,
.tasks_max = UINT64_MAX,

View File

@ -110,6 +110,17 @@ typedef enum ItemType {
ADJUST_MODE = 'm', /* legacy, 'z' is identical to this */
} ItemType;
typedef enum AgeBy {
AGE_BY_ATIME = 1 << 0,
AGE_BY_BTIME = 1 << 1,
AGE_BY_CTIME = 1 << 2,
AGE_BY_MTIME = 1 << 3,
/* All file timestamp types are checked by default. */
AGE_BY_DEFAULT_FILE = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_CTIME | AGE_BY_MTIME,
AGE_BY_DEFAULT_DIR = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_MTIME
} AgeBy;
typedef struct Item {
ItemType type;
@ -124,6 +135,7 @@ typedef struct Item {
gid_t gid;
mode_t mode;
usec_t age;
AgeBy age_by_file, age_by_dir;
dev_t major_minor;
unsigned attribute_value;
@ -142,6 +154,8 @@ typedef struct Item {
bool allow_failure:1;
bool try_replace:1;
OperationMask done;
} Item;
@ -503,6 +517,64 @@ static inline nsec_t load_statx_timestamp_nsec(const struct statx_timestamp *ts)
return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec;
}
static bool needs_cleanup(
nsec_t atime,
nsec_t btime,
nsec_t ctime,
nsec_t mtime,
nsec_t cutoff,
const char *sub_path,
AgeBy age_by,
bool is_dir) {
if (FLAGS_SET(age_by, AGE_BY_MTIME) && mtime != NSEC_INFINITY && mtime >= cutoff) {
char a[FORMAT_TIMESTAMP_MAX];
/* Follows spelling in stat(1). */
log_debug("%s \"%s\": modify time %s is too new.",
is_dir ? "Directory" : "File",
sub_path,
format_timestamp_style(a, sizeof(a), mtime / NSEC_PER_USEC, TIMESTAMP_US));
return false;
}
if (FLAGS_SET(age_by, AGE_BY_ATIME) && atime != NSEC_INFINITY && atime >= cutoff) {
char a[FORMAT_TIMESTAMP_MAX];
log_debug("%s \"%s\": access time %s is too new.",
is_dir ? "Directory" : "File",
sub_path,
format_timestamp_style(a, sizeof(a), atime / NSEC_PER_USEC, TIMESTAMP_US));
return false;
}
/*
* Note: Unless explicitly specified by the user, "ctime" is ignored
* by default for directories, because we change it when deleting.
*/
if (FLAGS_SET(age_by, AGE_BY_CTIME) && ctime != NSEC_INFINITY && ctime >= cutoff) {
char a[FORMAT_TIMESTAMP_MAX];
log_debug("%s \"%s\": change time %s is too new.",
is_dir ? "Directory" : "File",
sub_path,
format_timestamp_style(a, sizeof(a), ctime / NSEC_PER_USEC, TIMESTAMP_US));
return false;
}
if (FLAGS_SET(age_by, AGE_BY_BTIME) && btime != NSEC_INFINITY && btime >= cutoff) {
char a[FORMAT_TIMESTAMP_MAX];
log_debug("%s \"%s\": birth time %s is too new.",
is_dir ? "Directory" : "File",
sub_path,
format_timestamp_style(a, sizeof(a), btime / NSEC_PER_USEC, TIMESTAMP_US));
return false;
}
return true;
}
static int dir_cleanup(
Item *i,
const char *p,
@ -514,7 +586,9 @@ static int dir_cleanup(
dev_t rootdev_minor,
bool mountpoint,
int maxdepth,
bool keep_this_level) {
bool keep_this_level,
AgeBy age_by_file,
AgeBy age_by_dir) {
bool deleted = false;
struct dirent *dent;
@ -639,7 +713,8 @@ static int dir_cleanup(
sub_path, sub_dir,
atime_nsec, mtime_nsec, cutoff_nsec,
rootdev_major, rootdev_minor,
false, maxdepth-1, false);
false, maxdepth-1, false,
age_by_file, age_by_dir);
if (q < 0)
r = q;
}
@ -654,31 +729,13 @@ static int dir_cleanup(
continue;
}
/* Ignore ctime, we change it when deleting */
if (mtime_nsec != NSEC_INFINITY && mtime_nsec >= cutoff_nsec) {
char a[FORMAT_TIMESTAMP_MAX];
/* Follows spelling in stat(1). */
log_debug("Directory \"%s\": modify time %s is too new.",
sub_path,
format_timestamp_style(a, sizeof(a), mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
/*
* Check the file timestamps of an entry against the
* given cutoff time; delete if it is older.
*/
if (!needs_cleanup(atime_nsec, btime_nsec, ctime_nsec, mtime_nsec,
cutoff_nsec, sub_path, age_by_dir, true))
continue;
}
if (atime_nsec != NSEC_INFINITY && atime_nsec >= cutoff_nsec) {
char a[FORMAT_TIMESTAMP_MAX];
log_debug("Directory \"%s\": access time %s is too new.",
sub_path,
format_timestamp_style(a, sizeof(a), atime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue;
}
if (btime_nsec != NSEC_INFINITY && btime_nsec >= cutoff_nsec) {
char a[FORMAT_TIMESTAMP_MAX];
log_debug("Directory \"%s\": birth time %s is too new.",
sub_path,
format_timestamp_style(a, sizeof(a), btime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue;
}
log_debug("Removing directory \"%s\".", sub_path);
if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0)
@ -722,38 +779,9 @@ static int dir_cleanup(
continue;
}
if (mtime_nsec != NSEC_INFINITY && mtime_nsec >= cutoff_nsec) {
char a[FORMAT_TIMESTAMP_MAX];
/* Follows spelling in stat(1). */
log_debug("File \"%s\": modify time %s is too new.",
sub_path,
format_timestamp_style(a, sizeof(a), mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
if (!needs_cleanup(atime_nsec, btime_nsec, ctime_nsec, mtime_nsec,
cutoff_nsec, sub_path, age_by_file, false))
continue;
}
if (atime_nsec != NSEC_INFINITY && atime_nsec >= cutoff_nsec) {
char a[FORMAT_TIMESTAMP_MAX];
log_debug("File \"%s\": access time %s is too new.",
sub_path,
format_timestamp_style(a, sizeof(a), atime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue;
}
if (ctime_nsec != NSEC_INFINITY && ctime_nsec >= cutoff_nsec) {
char a[FORMAT_TIMESTAMP_MAX];
log_debug("File \"%s\": change time %s is too new.",
sub_path,
format_timestamp_style(a, sizeof(a), ctime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue;
}
if (btime_nsec != NSEC_INFINITY && btime_nsec >= cutoff_nsec) {
char a[FORMAT_TIMESTAMP_MAX];
log_debug("File \"%s\": birth time %s is too new.",
sub_path,
format_timestamp_style(a, sizeof(a), btime_nsec / NSEC_PER_USEC, TIMESTAMP_US));
continue;
}
log_debug("Removing \"%s\".", sub_path);
if (unlinkat(dirfd(d), dent->d_name, 0) < 0)
@ -2002,6 +2030,180 @@ static int glob_item_recursively(Item *i, fdaction_t action) {
return r;
}
static int rm_if_wrong_type_safe(
mode_t mode,
int parent_fd,
const struct stat *parent_st /* Only used if follow is true. */,
const char *name,
int flags) {
_cleanup_free_ char *parent_name = NULL;
bool follow_links = !FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW);
struct stat st;
int r;
assert(name);
assert((mode & ~S_IFMT) == 0);
assert(!follow_links || parent_st);
assert((flags & ~AT_SYMLINK_NOFOLLOW) == 0);
if (!filename_is_valid(name))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "\"%s\" is not a valid filename.", name);
r = fstatat_harder(parent_fd, name, &st, flags, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE);
if (r < 0) {
(void) fd_get_path(parent_fd, &parent_name);
return log_full_errno(r == -ENOENT? LOG_DEBUG : LOG_ERR, r,
"Failed to stat \"%s\" at \"%s\": %m", name, strna(parent_name));
}
/* Fail before removing anything if this is an unsafe transition. */
if (follow_links && unsafe_transition(parent_st, &st)) {
(void) fd_get_path(parent_fd, &parent_name);
return log_error_errno(SYNTHETIC_ERRNO(ENOLINK),
"Unsafe transition from \"%s\" to \"%s\".", parent_name, name);
}
if ((st.st_mode & S_IFMT) == mode)
return 0;
(void) fd_get_path(parent_fd, &parent_name);
log_notice("Wrong file type 0x%x; rm -rf \"%s/%s\"", st.st_mode & S_IFMT, strna(parent_name), name);
/* If the target of the symlink was the wrong type, the link needs to be removed instead of the
* target, so make sure it is identified as a link and not a directory. */
if (follow_links) {
r = fstatat_harder(parent_fd, name, &st, AT_SYMLINK_NOFOLLOW, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE);
if (r < 0)
return log_error_errno(r, "Failed to stat \"%s\" at \"%s\": %m", name, strna(parent_name));
}
/* Do not remove mount points. */
r = fd_is_mount_point(parent_fd, name, follow_links ? AT_SYMLINK_FOLLOW : 0);
if (r < 0)
(void) log_warning_errno(r, "Failed to check if \"%s/%s\" is a mount point: %m; Continuing",
strna(parent_name), name);
else if (r > 0)
return log_error_errno(SYNTHETIC_ERRNO(EBUSY),
"Not removing \"%s/%s\" because it is a mount point.", strna(parent_name), name);
if ((st.st_mode & S_IFMT) == S_IFDIR) {
_cleanup_close_ int child_fd = -1;
child_fd = openat(parent_fd, name, O_NOCTTY | O_CLOEXEC | O_DIRECTORY);
if (child_fd < 0)
return log_error_errno(errno, "Failed to open \"%s\" at \"%s\": %m", name, strna(parent_name));
r = rm_rf_children(TAKE_FD(child_fd), REMOVE_ROOT|REMOVE_SUBVOLUME|REMOVE_PHYSICAL, &st);
if (r < 0)
return log_error_errno(r, "Failed to remove contents of \"%s\" at \"%s\": %m", name, strna(parent_name));
r = unlinkat_harder(parent_fd, name, AT_REMOVEDIR, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE);
} else
r = unlinkat_harder(parent_fd, name, 0, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE);
if (r < 0)
return log_error_errno(r, "Failed to remove \"%s\" at \"%s\": %m", name, strna(parent_name));
/* This is covered by the log_notice "Wrong file type..." It is logged earlier because it gives
* context to other error messages that might follow. */
return -ENOENT;
}
/* If child_mode is non-zero, rm_if_wrong_type_safe will be executed for the last path component. */
static int mkdir_parents_rm_if_wrong_type(mode_t child_mode, const char *path) {
_cleanup_close_ int parent_fd = -1, next_fd = -1;
_cleanup_free_ char *parent_name = NULL;
struct stat parent_st;
const char *s, *e;
int r;
size_t path_len;
assert(path);
assert((child_mode & ~S_IFMT) == 0);
path_len = strlen(path);
if (!is_path(path))
/* rm_if_wrong_type_safe already logs errors. */
return child_mode != 0 ? rm_if_wrong_type_safe(child_mode, AT_FDCWD, NULL, path, AT_SYMLINK_NOFOLLOW) : 0;
if (child_mode != 0 && endswith(path, "/"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Trailing path separators are only allowed if child_mode is not set; got \"%s\"", path);
/* Get the parent_fd and stat. */
parent_fd = AT_FDCWD;
if (path_is_absolute(path)) {
parent_fd = open("/", O_NOCTTY | O_CLOEXEC | O_DIRECTORY);
if (parent_fd < 0)
return log_error_errno(errno, "Failed to open root: %m");
}
if (fstat(parent_fd, &parent_st) < 0)
return log_error_errno(errno, "Failed to stat root: %m");
/* Check every parent directory in the path, except the last component */
e = path;
for (;;) {
char t[path_len + 1];
/* Find the start of the next path component. */
s = e + strspn(e, "/");
/* Find the end of the next path component. */
e = s + strcspn(s, "/");
/* Copy the path component to t so it can be a null terminated string. */
*((char*) mempcpy(t, s, e - s)) = 0;
/* Is this the last component? If so, then check the type */
if (*e == 0)
return child_mode != 0 ? rm_if_wrong_type_safe(child_mode, parent_fd, &parent_st, t, AT_SYMLINK_NOFOLLOW) : 0;
else {
r = rm_if_wrong_type_safe(S_IFDIR, parent_fd, &parent_st, t, 0);
/* Remove dangling symlinks. */
if (r == -ENOENT)
r = rm_if_wrong_type_safe(S_IFDIR, parent_fd, &parent_st, t, AT_SYMLINK_NOFOLLOW);
}
if (r == -ENOENT) {
RUN_WITH_UMASK(0000)
r = mkdirat_label(parent_fd, t, 0755);
if (r < 0) {
(void) fd_get_path(parent_fd, &parent_name);
return log_error_errno(r, "Failed to mkdir \"%s\" at \"%s\": %m", t, parent_name);
}
} else if (r < 0)
/* rm_if_wrong_type_safe already logs errors. */
return r;
next_fd = openat(parent_fd, t, O_NOCTTY | O_CLOEXEC | O_DIRECTORY);
if (next_fd < 0) {
r = -errno;
(void) fd_get_path(parent_fd, &parent_name);
return log_error_errno(r, "Failed to open \"%s\" at \"%s\": %m", t, parent_name);
}
if (fstat(next_fd, &parent_st) < 0) {
r = -errno;
(void) fd_get_path(parent_fd, &parent_name);
return log_error_errno(r, "Failed to stat \"%s\" at \"%s\": %m", t, parent_name);
}
safe_close(parent_fd);
parent_fd = TAKE_FD(next_fd);
}
}
static int mkdir_parents_item(Item *i, mode_t child_mode) {
int r;
if (i->try_replace) {
r = mkdir_parents_rm_if_wrong_type(child_mode, i->path);
if (r < 0 && r != -ENOENT)
return r;
} else
RUN_WITH_UMASK(0000)
(void) mkdir_parents_label(i->path, 0755);
return 0;
}
static int create_item(Item *i) {
CreationMode creation;
int r = 0;
@ -2020,8 +2222,9 @@ static int create_item(Item *i) {
case TRUNCATE_FILE:
case CREATE_FILE:
RUN_WITH_UMASK(0000)
(void) mkdir_parents_label(i->path, 0755);
r = mkdir_parents_item(i, S_IFREG);
if (r < 0)
return r;
if ((i->type == CREATE_FILE && i->append_or_force) || i->type == TRUNCATE_FILE)
r = truncate_file(i, i->path);
@ -2033,8 +2236,9 @@ static int create_item(Item *i) {
break;
case COPY_FILES:
RUN_WITH_UMASK(0000)
(void) mkdir_parents_label(i->path, 0755);
r = mkdir_parents_item(i, 0);
if (r < 0)
return r;
r = copy_files(i);
if (r < 0)
@ -2050,8 +2254,9 @@ static int create_item(Item *i) {
case CREATE_DIRECTORY:
case TRUNCATE_DIRECTORY:
RUN_WITH_UMASK(0000)
(void) mkdir_parents_label(i->path, 0755);
r = mkdir_parents_item(i, S_IFDIR);
if (r < 0)
return r;
r = create_directory(i, i->path);
if (r < 0)
@ -2061,8 +2266,9 @@ static int create_item(Item *i) {
case CREATE_SUBVOLUME:
case CREATE_SUBVOLUME_INHERIT_QUOTA:
case CREATE_SUBVOLUME_NEW_QUOTA:
RUN_WITH_UMASK(0000)
(void) mkdir_parents_label(i->path, 0755);
r = mkdir_parents_item(i, S_IFDIR);
if (r < 0)
return r;
r = create_subvolume(i, i->path);
if (r < 0)
@ -2076,8 +2282,9 @@ static int create_item(Item *i) {
break;
case CREATE_FIFO:
RUN_WITH_UMASK(0000)
(void) mkdir_parents_label(i->path, 0755);
r = mkdir_parents_item(i, S_IFIFO);
if (r < 0)
return r;
r = create_fifo(i, i->path);
if (r < 0)
@ -2085,8 +2292,9 @@ static int create_item(Item *i) {
break;
case CREATE_SYMLINK: {
RUN_WITH_UMASK(0000)
(void) mkdir_parents_label(i->path, 0755);
r = mkdir_parents_item(i, S_IFLNK);
if (r < 0)
return r;
mac_selinux_create_file_prepare(i->path, S_IFLNK);
r = symlink(i->argument, i->path);
@ -2142,8 +2350,9 @@ static int create_item(Item *i) {
return 0;
}
RUN_WITH_UMASK(0000)
(void) mkdir_parents_label(i->path, 0755);
r = mkdir_parents_item(i, i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR);
if (r < 0)
return r;
r = create_device(i, i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR);
if (r < 0)
@ -2260,6 +2469,23 @@ static int remove_item(Item *i) {
}
}
static char *age_by_to_string(AgeBy ab, bool is_dir) {
static const char ab_map[] = { 'a', 'b', 'c', 'm' };
size_t j = 0;
char *ret;
ret = new(char, ELEMENTSOF(ab_map) + 1);
if (!ret)
return NULL;
for (size_t i = 0; i < ELEMENTSOF(ab_map); i++)
if (FLAGS_SET(ab, 1U << i))
ret[j++] = is_dir ? ascii_toupper(ab_map[i]) : ab_map[i];
ret[j] = 0;
return ret;
}
static int clean_item_instance(Item *i, const char* instance) {
char timestamp[FORMAT_TIMESTAMP_MAX];
_cleanup_closedir_ DIR *d = NULL;
@ -2306,17 +2532,31 @@ static int clean_item_instance(Item *i, const char* instance) {
sx.stx_ino != ps.st_ino;
}
log_debug("Cleanup threshold for %s \"%s\" is %s",
if (DEBUG_LOGGING) {
_cleanup_free_ char *ab_f = NULL, *ab_d = NULL;
ab_f = age_by_to_string(i->age_by_file, false);
if (!ab_f)
return log_oom();
ab_d = age_by_to_string(i->age_by_dir, true);
if (!ab_d)
return log_oom();
log_debug("Cleanup threshold for %s \"%s\" is %s; age-by: %s%s",
mountpoint ? "mount point" : "directory",
instance,
format_timestamp_style(timestamp, sizeof(timestamp), cutoff, TIMESTAMP_US));
format_timestamp_style(timestamp, sizeof(timestamp), cutoff, TIMESTAMP_US),
ab_f, ab_d);
}
return dir_cleanup(i, instance, d,
load_statx_timestamp_nsec(&sx.stx_atime),
load_statx_timestamp_nsec(&sx.stx_mtime),
cutoff * NSEC_PER_USEC,
sx.stx_dev_major, sx.stx_dev_minor, mountpoint,
MAX_DEPTH, i->keep_first_level);
MAX_DEPTH, i->keep_first_level,
i->age_by_file, i->age_by_dir);
}
static int clean_item(Item *i) {
@ -2482,6 +2722,9 @@ static bool item_compatible(Item *a, Item *b) {
a->age_set == b->age_set &&
a->age == b->age &&
a->age_by_file == b->age_by_file &&
a->age_by_dir == b->age_by_dir &&
a->mask_perms == b->mask_perms &&
a->keep_first_level == b->keep_first_level &&
@ -2646,6 +2889,58 @@ static int find_gid(const char *group, gid_t *ret_gid, Hashmap **cache) {
return name_to_gid_offline(arg_root, group, ret_gid, cache);
}
static int parse_age_by_from_arg(const char *age_by_str, Item *item) {
AgeBy ab_f = 0, ab_d = 0;
static const struct {
char age_by_chr;
AgeBy age_by_flag;
} age_by_types[] = {
{ 'a', AGE_BY_ATIME },
{ 'b', AGE_BY_BTIME },
{ 'c', AGE_BY_CTIME },
{ 'm', AGE_BY_MTIME },
};
assert(age_by_str);
assert(item);
if (isempty(age_by_str))
return -EINVAL;
for (const char *s = age_by_str; *s != 0; s++) {
size_t i;
/* Ignore whitespace. */
if (strchr(WHITESPACE, *s))
continue;
for (i = 0; i < ELEMENTSOF(age_by_types); i++) {
/* Check lower-case for files, upper-case for directories. */
if (*s == age_by_types[i].age_by_chr) {
ab_f |= age_by_types[i].age_by_flag;
break;
} else if (*s == ascii_toupper(age_by_types[i].age_by_chr)) {
ab_d |= age_by_types[i].age_by_flag;
break;
}
}
/* Invalid character. */
if (i >= ELEMENTSOF(age_by_types))
return -EINVAL;
}
/* No match. */
if (ab_f == 0 && ab_d == 0)
return -EINVAL;
item->age_by_file = ab_f > 0 ? ab_f : AGE_BY_DEFAULT_FILE;
item->age_by_dir = ab_d > 0 ? ab_d : AGE_BY_DEFAULT_DIR;
return 0;
}
static int parse_line(
const char *fname,
unsigned line,
@ -2655,11 +2950,15 @@ static int parse_line(
Hashmap **gid_cache) {
_cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
_cleanup_(item_free_contents) Item i = {};
_cleanup_(item_free_contents) Item i = {
/* The "age-by" argument considers all file timestamp types by default. */
.age_by_file = AGE_BY_DEFAULT_FILE,
.age_by_dir = AGE_BY_DEFAULT_DIR,
};
ItemArray *existing;
OrderedHashmap *h;
int r, pos;
bool append_or_force = false, boot = false, allow_failure = false;
bool append_or_force = false, boot = false, allow_failure = false, try_replace = false;
assert(fname);
assert(line >= 1);
@ -2704,6 +3003,8 @@ static int parse_line(
append_or_force = true;
else if (action[pos] == '-' && !allow_failure)
allow_failure = true;
else if (action[pos] == '=' && !try_replace)
try_replace = true;
else {
*invalid_config = true;
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Unknown modifiers in command '%s'", action);
@ -2718,6 +3019,7 @@ static int parse_line(
i.type = action[0];
i.append_or_force = append_or_force;
i.allow_failure = allow_failure;
i.try_replace = try_replace;
r = specifier_printf(path, PATH_MAX-1, specifier_table, NULL, &i.path);
if (r == -ENXIO)
@ -2926,16 +3228,37 @@ static int parse_line(
if (!empty_or_dash(age)) {
const char *a = age;
_cleanup_free_ char *seconds = NULL, *age_by = NULL;
if (*a == '~') {
i.keep_first_level = true;
a++;
}
/* Format: "age-by:age"; where age-by is "[abcmABCM]+". */
r = split_pair(a, ":", &age_by, &seconds);
if (r == -ENOMEM)
return log_oom();
if (r < 0 && r != -EINVAL)
return log_error_errno(r, "Failed to parse age-by for '%s': %m", age);
if (r >= 0) {
/* We found a ":", parse the "age-by" part. */
r = parse_age_by_from_arg(age_by, &i);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
*invalid_config = true;
return log_syntax(NULL, LOG_ERR, fname, line, r, "Invalid age-by '%s'.", age_by);
}
/* For parsing the "age" part, after the ":". */
a = seconds;
}
r = parse_sec(a, &i.age);
if (r < 0) {
*invalid_config = true;
return log_syntax(NULL, LOG_ERR, fname, line, r, "Invalid age '%s'.", age);
return log_syntax(NULL, LOG_ERR, fname, line, r, "Invalid age '%s'.", a);
}
i.age_set = true;

View File

@ -134,7 +134,7 @@ int link_load_one(LinkConfigContext *ctx, const char *filename) {
*link = (LinkConfig) {
.filename = TAKE_PTR(name),
.mac_address_policy = _MAC_ADDRESS_POLICY_INVALID,
.wol = _WOL_INVALID,
.wol = UINT32_MAX, /* UINT32_MAX means do not change WOL setting. */
.duplex = _DUP_INVALID,
.port = _NET_DEV_PORT_INVALID,
.autonegotiation = -1,
@ -329,9 +329,13 @@ static int link_config_apply_ethtool_settings(int *ethtool_fd, const LinkConfig
}
r = ethtool_set_wol(ethtool_fd, name, config->wol);
if (r < 0)
if (r < 0) {
_cleanup_free_ char *str = NULL;
(void) wol_options_to_string_alloc(config->wol, &str);
log_device_warning_errno(device, r, "Could not set WakeOnLan to %s, ignoring: %m",
wol_to_string(config->wol));
strna(str));
}
r = ethtool_set_features(ethtool_fd, name, config->features);
if (r < 0)

View File

@ -56,7 +56,7 @@ struct LinkConfig {
Duplex duplex;
int autonegotiation;
uint32_t advertise[N_ADVERTISE];
WakeOnLan wol;
uint32_t wol;
NetDevPort port;
int features[_NET_DEV_FEAT_MAX];
netdev_channels channels;

View File

@ -48,6 +48,7 @@ def test_invalids(*, user):
test_line('f++ /too/many/plusses', user=user)
test_line('f+!+ /too/many/plusses', user=user)
test_line('f!+! /too/many/bangs', user=user)
test_line('f== /too/many/equals', user=user)
test_line('w /unresolved/argument - - - - "%Y"', user=user)
test_line('w /unresolved/argument/sandwich - - - - "%v%Y%v"', user=user)
test_line('w /unresolved/filename/%Y - - - - "whatever"', user=user)
@ -73,9 +74,11 @@ def test_uninitialized_t():
test_line('w /foo - - - - "specifier for --user %t"',
user=True, returncode=0, extra={'env':{}})
def test_content(line, expected, *, user, extra={}):
def test_content(line, expected, *, user, extra={}, subpath='/arg', path_cb=None):
d = tempfile.TemporaryDirectory(prefix='test-systemd-tmpfiles.')
arg = d.name + '/arg'
if path_cb is not None:
path_cb(d.name, subpath)
arg = d.name + subpath
spec = line.format(arg)
test_line(spec, user=user, returncode=0, extra=extra)
content = open(arg).read()
@ -134,6 +137,57 @@ def test_valid_specifiers(*, user):
test_content('f {} - - - - %%', '%', user=user)
def mkfifo(parent, subpath):
os.makedirs(parent, mode=0o755, exist_ok=True)
first_component = subpath.split('/')[1]
path = parent + '/' + first_component
print('path: {}'.format(path))
os.mkfifo(path)
def mkdir(parent, subpath):
first_component = subpath.split('/')[1]
path = parent + '/' + first_component
os.makedirs(path, mode=0o755, exist_ok=True)
os.symlink(path, path + '/self', target_is_directory=True)
def symlink(parent, subpath):
link_path = parent + '/link-target'
os.makedirs(parent, mode=0o755, exist_ok=True)
with open(link_path, 'wb') as f:
f.write(b'target')
first_component = subpath.split('/')[1]
path = parent + '/' + first_component
os.symlink(link_path, path, target_is_directory=True)
def file(parent, subpath):
content = 'file-' + subpath.split('/')[1]
path = parent + subpath
os.makedirs(os.path.dirname(path), mode=0o755, exist_ok=True)
with open(path, 'wb') as f:
f.write(content.encode())
def valid_symlink(parent, subpath):
target = 'link-target'
link_path = parent + target
os.makedirs(link_path, mode=0o755, exist_ok=True)
first_component = subpath.split('/')[1]
path = parent + '/' + first_component
os.symlink(target, path, target_is_directory=True)
def test_hard_cleanup(*, user):
type_cbs = [None, file, mkdir, symlink]
if 'mkfifo' in dir(os):
type_cbs.append(mkfifo)
for type_cb in type_cbs:
for subpath in ['/shallow', '/deep/1/2']:
label = '{}-{}'.format('None' if type_cb is None else type_cb.__name__, subpath.split('/')[1])
test_content('f= {} - - - - ' + label, label, user=user, subpath=subpath, path_cb=type_cb)
# Test the case that a valid symlink is in the path.
label = 'valid_symlink-deep'
test_content('f= {} - - - - ' + label, label, user=user, subpath='/deep/1/2', path_cb=valid_symlink)
if __name__ == '__main__':
test_invalids(user=False)
test_invalids(user=True)
@ -141,3 +195,6 @@ if __name__ == '__main__':
test_valid_specifiers(user=False)
test_valid_specifiers(user=True)
test_hard_cleanup(user=False)
test_hard_cleanup(user=True)

196
test/units/testsuite-22.12.sh Executable file
View File

@ -0,0 +1,196 @@
#! /bin/bash
# Test the "Age" parameter (with age-by) for systemd-tmpfiles.
set -e
set -x
# Test directory structure looks like this:
# /tmp/ageby/
# ├── d1
# │   ├── f1
# │   ├── f2
# │   ├── f3
# │   └── f4
# ├── d2
# │   ├── f1
# │   ├── f2
# ...
export SYSTEMD_LOG_LEVEL="debug"
rm -rf /tmp/ageby
mkdir -p /tmp/ageby/d{1..4}
# TODO: There is probably a better way to figure this out.
# Test for [bB] age-by arguments only on filesystems that expose
# the creation time. Note that this is _not_ an accurate way to
# check if the filesystem or kernel version don't provide the
# timestamp. But, if the timestamp is visible in "stat" it is a
# good indicator that the test can be run.
TEST_TMPFILES_AGEBY_BTIME=${TEST_TMPFILES_AGEBY_BTIME:-0}
if stat --format "%w" /tmp/ageby 2>/dev/null | grep -qv '^[\?\-]$'; then
TEST_TMPFILES_AGEBY_BTIME=1
fi
touch -a --date "2 minutes ago" /tmp/ageby/d1/f1
touch -m --date "4 minutes ago" /tmp/ageby/d2/f1
# Create a bunch of other files.
touch /tmp/ageby/d{1,2}/f{2..4}
# For "ctime".
touch /tmp/ageby/d3/f1
chmod +x /tmp/ageby/d3/f1
sleep 1
# For "btime".
touch /tmp/ageby/d4/f1
sleep 1
# More files with recent "{a,b}time" values.
touch /tmp/ageby/d{3,4}/f{2..4}
# Check for cleanup of "f1" in each of "/tmp/d{1..4}".
systemd-tmpfiles --clean - <<-EOF
d /tmp/ageby/d1 - - - a:1m -
e /tmp/ageby/d2 - - - m:3m -
D /tmp/ageby/d3 - - - c:2s -
EOF
for d in d{1..3}; do
test ! -f "/tmp/ageby/${d}/f1"
done
if [[ $TEST_TMPFILES_AGEBY_BTIME -gt 0 ]]; then
systemd-tmpfiles --clean - <<-EOF
d /tmp/ageby/d4 - - - b:1s -
EOF
test ! -f "/tmp/ageby/d4/f1"
else
# Remove the file manually.
rm "/tmp/ageby/d4/f1"
fi
# Check for an invalid "age" and "age-by" arguments.
for a in ':' ':1s' '2:1h' 'nope:42h' '" :7m"' 'm:' '::' '"+r^w-x:2/h"' 'b ar::64'; do
systemd-tmpfiles --clean - <<EOF 2>&1 | grep -q -F 'Invalid age'
d /tmp/ageby - - - ${a} -
EOF
done
for d in d{1..4}; do
for f in f{2..4}; do
test -f "/tmp/ageby/${d}/${f}"
done
done
# Check for parsing with whitespace, repeated values
# for "age-by" (valid arguments).
for a in '" a:24h"' 'cccaab:2h' '" aa : 4h"' '" a A B C c:1h"'; do
systemd-tmpfiles --clean - <<EOF
d /tmp/ageby - - - ${a} -
EOF
done
for d in d{1..4}; do
for f in f{2..4}; do
test -f "/tmp/ageby/${d}/${f}"
done
done
# Check that all files are removed if the "Age" is
# set to "0" (regardless of "age-by" argument).
systemd-tmpfiles --clean - <<-EOF
d /tmp/ageby/d1 - - - abc:0 -
e /tmp/ageby/d2 - - - cmb:0 -
EOF
for d in d{1,2}; do
for f in f{2..4}; do
test ! -f "/tmp/ageby/${d}/${f}"
done
done
# Check for combinations:
# - "/tmp/ageby/d3/f2" has file timestamps that
# are older than the specified age, it will be
# removed
# - "/tmp/ageby/d4/f2", has not aged for the given
# timestamp combination, it will not be removed
touch -a -m --date "4 minutes ago" /tmp/ageby/d3/f2
touch -a -m --date "8 minutes ago" /tmp/ageby/d4/f2
systemd-tmpfiles --clean - <<-EOF
e /tmp/ageby/d3 - - - am:3m -
D /tmp/ageby/d4 - - - mc:7m -
EOF
test ! -f "/tmp/ageby/d3/f2"
test -f "/tmp/ageby/d4/f2"
# Check that all files are removed if only "Age" is set to 0.
systemd-tmpfiles --clean - <<-EOF
e /tmp/ageby/d3 - - - 0s
d /tmp/ageby/d4 - - - 0s
EOF
for d in d{3,4}; do
for f in f{2..4}; do
test ! -f "/tmp/ageby/$d/${f}"
done
done
# Check "age-by" argument for sub-directories in "/tmp/ageby".
systemd-tmpfiles --clean - <<-EOF
d /tmp/ageby/ - - - A:1m -
EOF
for d in d{1..4}; do
test -d "/tmp/ageby/${d}"
done
# Check for combinations.
touch -a -m --date "5 seconds ago" /tmp/ageby/d{1,2}
systemd-tmpfiles --clean - <<-EOF
e /tmp/ageby/ - - - AM:4s -
EOF
for d in d{1,2}; do
test ! -d "/tmp/ageby/${d}"
done
for d in d{3,4}; do
test -d "/tmp/ageby/${d}"
done
# Check "btime" for directories.
if [[ $TEST_TMPFILES_AGEBY_BTIME -gt 0 ]]; then
systemd-tmpfiles --clean - <<-EOF
d /tmp/ageby/ - - - B:8s -
EOF
for d in d{3,4}; do
test -d "/tmp/ageby/${d}"
done
fi
# To bump "atime".
touch -a --date "1 second ago" /tmp/ageby/d3
systemd-tmpfiles --clean - <<-EOF
d /tmp/ageby/ - - - A:2s -
EOF
test -d /tmp/ageby/d3
test ! -d /tmp/ageby/d4
# Check if sub-directories are removed regardless
# of "age-by", when "Age" is set to "0".
systemd-tmpfiles --clean - <<-EOF
D /tmp/ageby/ - - - AM:0 -
EOF
test ! -d /tmp/ageby/d3
# Cleanup the test directory (fail if not empty).
rmdir /tmp/ageby