tags:
- "v*"
+permissions:
+ contents: read
+
jobs:
- build:
+ release:
+ if: github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable'
runs-on: ubuntu-latest
+
+ permissions:
+ contents: write
+
steps:
- - name: Checkout
- uses: actions/checkout@v3
- name: Release
- uses: softprops/action-gh-release@v1
+ uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
with:
prerelease: ${{ contains(github.ref_name, '-rc') }}
draft: ${{ github.repository == 'systemd/systemd' }}
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- - uses: systemd/mkosi@af1fe54805c84bb09e80bb585399121946fec701
+ - uses: systemd/mkosi@ee7f2950a25fdaaa7027937c5d9f3df6761e9fd9
- name: Configure
run: |
* add support for asymmetric LUKS2 TPM based encryption. i.e. allow preparing
an encrypted image on some host given a public key belonging to a specific
other host, so that only hosts possessing the private key in the TPM2 chip
- can decrypt the volume key and activate the volume. Usecase: systemd-syscfg
- for a central orchestrator to generate syscfg images securely that can only
+ can decrypt the volume key and activate the volume. Usecase: systemd-confext
+ for a central orchestrator to generate confext images securely that can only
be activated on one specific host (which can be used for installing a bunch
of creds in /etc/credstore/ for example). Extending on this: allow binding
LUKS2 TPM based encryption also to the TPM2 internal clock. Net result:
- prepare a syscfg image that can only be activated on a specific host that
- runs a specific software in a specific time window. syscfg would be
+ prepare a confext image that can only be activated on a specific host that
+ runs a specific software in a specific time window. confext would be
automatically invalidated outside of it.
* maybe add a "systemd-report" tool, that generates a TPM2-backed "report" of
this: have the report tool upload these reports every 3min somewhere. Then
have the orchestrator collect these reports centrally over a 3min time
window, and use them to determine what which node should now start/stop what,
- and generate a small syscfg for each node, that uses Uphold= to pin services
- on each node. The syscfg would be encrypted using the asymmetric encryption
+ and generate a small confext for each node, that uses Uphold= to pin services
+ on each node. The confext would be encrypted using the asymmetric encryption
proposed above, so that it can only be activated on the specific host, if the
software is in a good state, and within a specific time frame. Then run a
loop on each node that sends report to orchestrator and then sysupdate to
- update syscfg. Orchestrator would be stateless, i.e. operate on desired
+ update confext. Orchestrator would be stateless, i.e. operate on desired
config and collected reports in the last 3min time window only, and thus can
be trivially scaled up since all instances of the orchestrator should come to
the same conclusions given the same inputs of reports/desired workload info.
Could also be used to deliver Wireguard secrets and thus to clients, thus
- permitting zero-trust networking: secrets are rolled over via syscfg updates,
+ permitting zero-trust networking: secrets are rolled over via confext updates,
and via the time window TPM logic invalidated if node doesn't keep itself
updated, or becomes corrupted in some way.
keyring, so that the kernel does this validation for us for verity and kernel
modules
-* for systemd-syscfg: add a tool that can generate suitable DDIs with verity +
+* for systemd-confext: add a tool that can generate suitable DDIs with verity +
sig using squashfs-tools-ng's library. Maybe just systemd-repart called under
a new name with a built-in config?
* sysext: measure all activated sysext into a TPM PCR
-* maybe add a "syscfg" concept, that is almost entirely identical to "sysext",
- but operates on /etc/ instead of /usr/ and /opt/. Use case would be: trusted,
- authenticated, atomic, additive configuration management primitive: drop in a
- configuration bundle, and activate it, so that it is instantly visible,
- comprehensively.
-
* systemd-dissect: show available versions inside of a disk image, i.e. if
multiple versions are around of the same resource, show which ones. (in other
words: show partition labels).
* `$SYSTEMD_OS_RELEASE` — if set, use this path instead of `/etc/os-release` or
`/usr/lib/os-release`. When operating under some root (e.g. `systemctl
- --root=…`), the path is taken relative to the outside root. Only useful for
- debugging.
+ --root=…`), the path is prefixed with the root. Only useful for debugging.
* `$SYSTEMD_FSTAB` — if set, use this path instead of `/etc/fstab`. Only useful
for debugging.
paths. Only "real" file systems and directories that only contain "real" file
systems as submounts should be used. Do not specify API file systems such as
`/proc/` or `/sys/` here, or hierarchies that have them as submounts. In
- particular, do not specify the root directory `/` here.
+ particular, do not specify the root directory `/` here. Similarly,
+ `$SYSTEMD_CONFEXT_HIERARCHIES` works for confext images and supports the
+ systemd-confext multi-call functionality of sysext.
`systemd-tmpfiles`:
journal. Note that journal files in compact mode are limited to 4G to allow use of
32-bit offsets. Enabled by default.
+* `$SYSTEMD_JOURNAL_COMPRESS` – Takes a boolean, or one of the compression
+ algorithms "XZ", "LZ4", and "ZSTD". If enabled, the default compression
+ algorithm set at compile time will be used when opening a new journal file.
+ If disabled, the journal file compression will be disabled. Note that the
+ compression mode of existing journal files are not changed. To make the
+ specified algorithm takes an effect immediately, you need to explicitly run
+ `journalctl --rotate`.
+
`systemd-pcrphase`, `systemd-cryptsetup`:
* `$SYSTEMD_FORCE_MEASURE=1` — If set, force measuring of resources (which are
KEYBOARD_KEY_090010=f20 # Microphone mute button; should be micmute
# Lenovo 3000
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*3000*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*3000*:pvr*
KEYBOARD_KEY_8b=switchvideomode # Fn+F7 video
KEYBOARD_KEY_96=wlan # Fn+F5 wireless
KEYBOARD_KEY_97=sleep # Fn+F4 suspend
KEYBOARD_KEY_b4=prog1
# Lenovo IdeaPad
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*:pvr*
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pnS10-*:*
KEYBOARD_KEY_81=rfkill # does nothing in BIOS
KEYBOARD_KEY_83=display_off # BIOS toggles screen state
KEYBOARD_KEY_81=insert
# Thinkpad X200_Tablet
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X2*T*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X2*T*:rvn*
KEYBOARD_KEY_5d=menu
KEYBOARD_KEY_63=fn
KEYBOARD_KEY_66=screenlock
KEYBOARD_KEY_6c=direction # rotate screen
# ThinkPad X6 Tablet
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X6*Tablet*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X6*Tablet*:rvn*
KEYBOARD_KEY_6c=direction # rotate
KEYBOARD_KEY_68=leftmeta # toolbox
KEYBOARD_KEY_6b=esc # escape
KEYBOARD_KEY_42=f23
KEYBOARD_KEY_43=f22
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*Y550*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*Y550*:pvr*
KEYBOARD_KEY_95=media
KEYBOARD_KEY_a3=play
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*U300s*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*U300s*:pvr*
KEYBOARD_KEY_f1=f21
KEYBOARD_KEY_ce=f20 # micmute
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPad*Z370*:*
+evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPad*Z370*:pvr*
KEYBOARD_KEY_a0=!mute
KEYBOARD_KEY_ae=!volumedown
KEYBOARD_KEY_b0=!volumeup
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPadFlex5*:*
+evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPadFlex5*:pvr*
KEYBOARD_KEY_a0=!mute
KEYBOARD_KEY_ae=!volumedown
KEYBOARD_KEY_b0=!volumeup
KEYBOARD_KEY_b0=!volumeup
# Lenovo Y50-70
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*20378*:*
+evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*20378*:pvr*
KEYBOARD_KEY_f3=f21 # Fn+F6 (toggle touchpad)
# V480
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*Lenovo*V480*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*Lenovo*V480*:pvr*
KEYBOARD_KEY_f1=f21
# Lenovo ThinkCentre M800z/M820z/M920z AIO machines
sensor:modalias:acpi:BOSC0200*:dmi:*svn*ASUSTeK*:*pn*TP412UA:*
ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, 1
+sensor:modalias:acpi:BOSC0200*:dmi:*svn*ASUSTeK*:pn*BR1100FKA:*
+ ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, -1
+
#########################################
# Axxo
#########################################
sensor:modalias:acpi:BMI0160*:dmi:*:rnONEXPLAYER:rvrV01:*
ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, -1
+#########################################
+# Passion
+#########################################
+
+# Passion P612F
+sensor:modalias:acpi:MXC6655*:dmi:*:svnDefaultstring*:pnP612F:*
+ ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
+
#########################################
# Peaq
#########################################
condition is a trigger condition, whether the condition is reversed, the right hand side of the
condition (e.g. the path in case of <varname>ConditionPathExists</varname>), and the status. The status
can be 0, in which case the condition hasn't been checked yet, a positive value, in which case the
- condition passed, or a negative value, in which case the condition failed. Currently only 0, +1, and -1
+ condition passed, or a negative value, in which case the condition is not met. Currently only 0, +1, and -1
are used, but additional values may be used in the future, retaining the meaning of
zero/positive/negative values.</para>
</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>CONFEXT_LEVEL=</varname></term>
+
+ <listitem><para>Semantically the same as <varname>SYSEXT_LEVEL=</varname> but for confext images.
+ See <filename>/etc/extension-release.d/extension-release.<replaceable>IMAGE</replaceable></filename>
+ for more information.</para>
+
+ <para>Examples: <literal>CONFEXT_LEVEL=2</literal>, <literal>CONFEXT_LEVEL=15.14</literal>.
+ </para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>SYSEXT_SCOPE=</varname></term>
<listitem><para>Takes a space-separated list of one or more of the strings
but not to initrd environments.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>CONFEXT_SCOPE=</varname></term>
+
+ <listitem><para>Semantically the same as <varname>SYSEXT_SCOPE=</varname> but for confext images.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>PORTABLE_PREFIXES=</varname></term>
<listitem><para>Takes a space-separated list of one or more valid prefix match strings for the
'systemd-suspend-then-hibernate.service'],
''],
['systemd-sysctl.service', '8', ['systemd-sysctl'], ''],
- ['systemd-sysext', '8', ['systemd-sysext.service'], ''],
+ ['systemd-sysext',
+ '8',
+ ['systemd-confext', 'systemd-confext.service', 'systemd-sysext.service'],
+ ''],
['systemd-system-update-generator', '8', [], ''],
['systemd-system.conf',
'5',
<refsect1>
<title>Description</title>
- <para><function>sd_notify()</function> may be called by a service
- to notify the service manager about state changes. It can be used
- to send arbitrary information, encoded in an
- environment-block-like string. Most importantly, it can be used for
- start-up completion notification.</para>
-
- <para>If the <parameter>unset_environment</parameter> parameter is
- non-zero, <function>sd_notify()</function> will unset the
- <varname>$NOTIFY_SOCKET</varname> environment variable before
- returning (regardless of whether the function call itself
- succeeded or not). Further calls to
- <function>sd_notify()</function> will then fail, but the variable
- is no longer inherited by child processes.</para>
-
- <para>The <parameter>state</parameter> parameter should contain a
- newline-separated list of variable assignments, similar in style
- to an environment block. A trailing newline is implied if none is
- specified. The string may contain any kind of variable
- assignments, but the following shall be considered
+
+ <para><function>sd_notify()</function> may be called by a service to notify the service manager about
+ state changes. It can be used to send arbitrary information, encoded in an environment-block-like
+ string. Most importantly, it can be used for start-up completion notification.</para>
+
+ <para>If the <parameter>unset_environment</parameter> parameter is non-zero,
+ <function>sd_notify()</function> will unset the <varname>$NOTIFY_SOCKET</varname> environment variable
+ before returning (regardless of whether the function call itself succeeded or not). Further calls to
+ <function>sd_notify()</function> will then fail, but the variable is no longer inherited by child
+ processes.</para>
+
+ <para>The <parameter>state</parameter> parameter should contain a newline-separated list of variable
+ assignments, similar in style to an environment block. A trailing newline is implied if none is
+ specified. The string may contain any kind of variable assignments, but the following shall be considered
well-known:</para>
<variablelist>
<varlistentry>
<term>STOPPING=1</term>
- <listitem><para>Tells the service manager that the service is
- beginning its shutdown. This is useful to allow the service
- manager to track the service's internal state, and present it
- to the user.</para></listitem>
+ <listitem><para>Tells the service manager that the service is beginning its shutdown. This is useful
+ to allow the service manager to track the service's internal state, and present it to the
+ user.</para></listitem>
</varlistentry>
<varlistentry>
<term>STATUS=…</term>
- <listitem><para>Passes a single-line UTF-8 status string back
- to the service manager that describes the service state. This
- is free-form and can be used for various purposes: general
- state feedback, fsck-like programs could pass completion
- percentages and failing programs could pass a human-readable
- error message. Example: <literal>STATUS=Completed 66% of file
- system check…</literal></para></listitem>
+ <listitem><para>Passes a single-line UTF-8 status string back to the service manager that describes
+ the service state. This is free-form and can be used for various purposes: general state feedback,
+ fsck-like programs could pass completion percentages and failing programs could pass a human-readable
+ error message. Example: <literal>STATUS=Completed 66% of file system
+ check…</literal></para></listitem>
</varlistentry>
<varlistentry>
<term>NOTIFYACCESS=…</term>
- <listitem><para>Reset the access to the service status notification
- socket during runtime, overriding <varname>NotifyAccess=</varname> setting
- in the service unit file. See <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
- for details, specifically <literal>NotifyAccess=</literal> for a list of
- accepted values.</para></listitem>
+ <listitem><para>Reset the access to the service status notification socket during runtime, overriding
+ <varname>NotifyAccess=</varname> setting in the service unit file. See
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details, specifically <literal>NotifyAccess=</literal> for a list of accepted
+ values.</para></listitem>
</varlistentry>
<varlistentry>
<term>ERRNO=…</term>
- <listitem><para>If a service fails, the errno-style error
- code, formatted as string. Example: <literal>ERRNO=2</literal>
- for ENOENT.</para></listitem>
+ <listitem><para>If a service fails, the errno-style error code, formatted as string. Example:
+ <literal>ERRNO=2</literal> for ENOENT.</para></listitem>
</varlistentry>
<varlistentry>
<term>BUSERROR=…</term>
- <listitem><para>If a service fails, the D-Bus error-style
- error code. Example:
+ <listitem><para>If a service fails, the D-Bus error-style error code. Example:
<literal>BUSERROR=org.freedesktop.DBus.Error.TimedOut</literal></para></listitem>
</varlistentry>
<varlistentry>
<term>MAINPID=…</term>
- <listitem><para>The main process ID (PID) of the service, in
- case the service manager did not fork off the process itself.
- Example: <literal>MAINPID=4711</literal></para></listitem>
+ <listitem><para>The main process ID (PID) of the service, in case the service manager did not fork
+ off the process itself. Example: <literal>MAINPID=4711</literal></para></listitem>
</varlistentry>
<varlistentry>
<term>WATCHDOG=1</term>
- <listitem><para>Tells the service manager to update the
- watchdog timestamp. This is the keep-alive ping that services
- need to issue in regular intervals if
- <varname>WatchdogSec=</varname> is enabled for it. See
+ <listitem><para>Tells the service manager to update the watchdog timestamp. This is the keep-alive
+ ping that services need to issue in regular intervals if <varname>WatchdogSec=</varname> is enabled
+ for it. See
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for information how to enable this functionality and
<citerefentry><refentrytitle>sd_watchdog_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>
- for the details of how the service can check whether the
- watchdog is enabled. </para></listitem>
+ for the details of how the service can check whether the watchdog is enabled. </para></listitem>
</varlistentry>
<varlistentry>
<term>WATCHDOG=trigger</term>
- <listitem><para>Tells the service manager that the service detected an internal error that should be handled by
- the configured watchdog options. This will trigger the same behaviour as if <varname>WatchdogSec=</varname> is
- enabled and the service did not send <literal>WATCHDOG=1</literal> in time. Note that
- <varname>WatchdogSec=</varname> does not need to be enabled for <literal>WATCHDOG=trigger</literal> to trigger
- the watchdog action. See
- <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
- information about the watchdog behavior. </para></listitem>
+ <listitem><para>Tells the service manager that the service detected an internal error that should be
+ handled by the configured watchdog options. This will trigger the same behaviour as if
+ <varname>WatchdogSec=</varname> is enabled and the service did not send <literal>WATCHDOG=1</literal>
+ in time. Note that <varname>WatchdogSec=</varname> does not need to be enabled for
+ <literal>WATCHDOG=trigger</literal> to trigger the watchdog action. See
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for information about the watchdog behavior. </para></listitem>
</varlistentry>
<varlistentry>
<term>WATCHDOG_USEC=…</term>
- <listitem><para>Reset <varname>watchdog_usec</varname> value during runtime.
- Notice that this is not available when using <function>sd_event_set_watchdog()</function>
- or <function>sd_watchdog_enabled()</function>.
- Example : <literal>WATCHDOG_USEC=20000000</literal></para></listitem>
+ <listitem><para>Reset <varname>watchdog_usec</varname> value during runtime. Notice that this is not
+ available when using <function>sd_event_set_watchdog()</function> or
+ <function>sd_watchdog_enabled()</function>. Example :
+ <literal>WATCHDOG_USEC=20000000</literal></para></listitem>
</varlistentry>
<varlistentry>
<term>EXTEND_TIMEOUT_USEC=…</term>
<listitem><para>Tells the service manager to extend the startup, runtime or shutdown service timeout
- corresponding the current state. The value specified is a time in microseconds during which the service must
- send a new message. A service timeout will occur if the message isn't received, but only if the runtime of the
- current state is beyond the original maximum times of <varname>TimeoutStartSec=</varname>, <varname>RuntimeMaxSec=</varname>,
- and <varname>TimeoutStopSec=</varname>.
- See <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ corresponding the current state. The value specified is a time in microseconds during which the
+ service must send a new message. A service timeout will occur if the message isn't received, but only
+ if the runtime of the current state is beyond the original maximum times of
+ <varname>TimeoutStartSec=</varname>, <varname>RuntimeMaxSec=</varname>, and
+ <varname>TimeoutStopSec=</varname>. See
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for effects on the service timeouts.</para></listitem>
</varlistentry>
<varlistentry>
<term>FDSTORE=1</term>
- <listitem><para>Stores additional file descriptors in the service manager. File descriptors sent this way will
- be maintained per-service by the service manager and will later be handed back using the usual file descriptor
- passing logic at the next invocation of the service (e.g. when it is restarted), see
- <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>. This is
- useful for implementing services that can restart after an explicit request or a crash without losing
- state. Any open sockets and other file descriptors which should not be closed during the restart may be stored
- this way. Application state can either be serialized to a file in <filename>/run/</filename>, or better, stored
- in a <citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry> memory
- file descriptor. Note that the service manager will accept messages for a service only if its
+ <listitem><para>Stores additional file descriptors in the service manager. File descriptors sent this
+ way will be maintained per-service by the service manager and will later be handed back using the
+ usual file descriptor passing logic at the next invocation of the service (e.g. when it is
+ restarted), see
+ <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
+ This is useful for implementing services that can restart after an explicit request or a crash
+ without losing state. Any open sockets and other file descriptors which should not be closed during
+ the restart may be stored this way. Application state can either be serialized to a file in
+ <filename>/run/</filename>, or better, stored in a
+ <citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+ memory file descriptor. Note that the service manager will accept messages for a service only if its
<varname>FileDescriptorStoreMax=</varname> setting is non-zero (defaults to zero, see
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>). If
<varname>FDPOLL=0</varname> is not set and the file descriptors sent are pollable (see
- <citerefentry><refentrytitle>epoll_ctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>), then any
- <constant>EPOLLHUP</constant> or <constant>EPOLLERR</constant> event seen on them will result in their
- automatic removal from the store. Multiple arrays of file descriptors may be sent in separate messages, in
- which case the arrays are combined. Note that the service manager removes duplicate (pointing to the same
- object) file descriptors before passing them to the service. When a service is stopped, its file descriptor
- store is discarded and all file descriptors in it are closed. Use <function>sd_pid_notify_with_fds()</function>
- to send messages with <literal>FDSTORE=1</literal>, see below.</para></listitem>
+ <citerefentry><refentrytitle>epoll_ctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>), then
+ any <constant>EPOLLHUP</constant> or <constant>EPOLLERR</constant> event seen on them will result in
+ their automatic removal from the store. Multiple arrays of file descriptors may be sent in separate
+ messages, in which case the arrays are combined. Note that the service manager removes duplicate
+ (pointing to the same object) file descriptors before passing them to the service. When a service is
+ stopped, its file descriptor store is discarded and all file descriptors in it are closed. Use
+ <function>sd_pid_notify_with_fds()</function> to send messages with <literal>FDSTORE=1</literal>, see
+ below. The service manager will set the <varname>$FDSTORE</varname> environment variable for services
+ that have the file descriptor store enabled.</para></listitem>
</varlistentry>
<varlistentry>
<term>FDSTOREREMOVE=1</term>
- <listitem><para>Removes file descriptors from the file descriptor store. This field needs to be combined with
- <varname>FDNAME=</varname> to specify the name of the file descriptors to remove.</para></listitem>
+ <listitem><para>Removes file descriptors from the file descriptor store. This field needs to be
+ combined with <varname>FDNAME=</varname> to specify the name of the file descriptors to
+ remove.</para></listitem>
</varlistentry>
<varlistentry>
<term>FDNAME=…</term>
- <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, specifies a name for the submitted
- file descriptors. When used with <varname>FDSTOREREMOVE=1</varname>, specifies the name for the file
- descriptors to remove. This name is passed to the service during activation, and may be queried using
+ <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, specifies a name for the
+ submitted file descriptors. When used with <varname>FDSTOREREMOVE=1</varname>, specifies the name for
+ the file descriptors to remove. This name is passed to the service during activation, and may be
+ queried using
<citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>. File
descriptors submitted without this field set, will implicitly get the name <literal>stored</literal>
- assigned. Note that, if multiple file descriptors are submitted at once, the specified name will be assigned to
- all of them. In order to assign different names to submitted file descriptors, submit them in separate
- invocations of <function>sd_pid_notify_with_fds()</function>. The name may consist of arbitrary ASCII
- characters except control characters or <literal>:</literal>. It may not be longer than 255 characters. If a
- submitted name does not follow these restrictions, it is ignored.</para></listitem>
+ assigned. Note that, if multiple file descriptors are submitted at once, the specified name will be
+ assigned to all of them. In order to assign different names to submitted file descriptors, submit
+ them in separate invocations of <function>sd_pid_notify_with_fds()</function>. The name may consist
+ of arbitrary ASCII characters except control characters or <literal>:</literal>. It may not be longer
+ than 255 characters. If a submitted name does not follow these restrictions, it is
+ ignored.</para></listitem>
</varlistentry>
<varlistentry>
<term>FDPOLL=0</term>
- <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, disables polling of the stored
- file descriptors regardless of whether or not they are pollable. As this option disables automatic cleanup
- of the stored file descriptors on EPOLLERR and EPOLLHUP, care must be taken to ensure proper manual cleanup.
- Use of this option is not generally recommended except for when automatic cleanup has unwanted behavior such
- as prematurely discarding file descriptors from the store.</para></listitem>
+ <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, disables polling of the
+ stored file descriptors regardless of whether or not they are pollable. As this option disables
+ automatic cleanup of the stored file descriptors on EPOLLERR and EPOLLHUP, care must be taken to
+ ensure proper manual cleanup. Use of this option is not generally recommended except for when
+ automatic cleanup has unwanted behavior such as prematurely discarding file descriptors from the
+ store.</para></listitem>
</varlistentry>
<varlistentry>
</varlistentry>
</variablelist>
- <para>It is recommended to prefix variable names that are not
- listed above with <varname>X_</varname> to avoid namespace
- clashes.</para>
-
- <para>Note that systemd will accept status data sent from a
- service only if the <varname>NotifyAccess=</varname> option is
- correctly set in the service definition file. See
- <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
- for details.</para>
-
- <para>Note that <function>sd_notify()</function> notifications may be attributed to units correctly only if either
- the sending process is still around at the time PID 1 processes the message, or if the sending process is
- explicitly runtime-tracked by the service manager. The latter is the case if the service manager originally forked
- off the process, i.e. on all processes that match <varname>NotifyAccess=</varname><option>main</option> or
- <varname>NotifyAccess=</varname><option>exec</option>. Conversely, if an auxiliary process of the unit sends an
- <function>sd_notify()</function> message and immediately exits, the service manager might not be able to properly
- attribute the message to the unit, and thus will ignore it, even if
+ <para>It is recommended to prefix variable names that are not listed above with <varname>X_</varname> to
+ avoid namespace clashes.</para>
+
+ <para>Note that systemd will accept status data sent from a service only if the
+ <varname>NotifyAccess=</varname> option is correctly set in the service definition file. See
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+ details.</para>
+
+ <para>Note that <function>sd_notify()</function> notifications may be attributed to units correctly only
+ if either the sending process is still around at the time PID 1 processes the message, or if the sending
+ process is explicitly runtime-tracked by the service manager. The latter is the case if the service
+ manager originally forked off the process, i.e. on all processes that match
+ <varname>NotifyAccess=</varname><option>main</option> or
+ <varname>NotifyAccess=</varname><option>exec</option>. Conversely, if an auxiliary process of the unit
+ sends an <function>sd_notify()</function> message and immediately exits, the service manager might not be
+ able to properly attribute the message to the unit, and thus will ignore it, even if
<varname>NotifyAccess=</varname><option>all</option> is set for it.</para>
<para>Hence, to eliminate all race conditions involving lookup of the client's unit and attribution of notifications
service manager, otherwise this synchronization mechanism is unnecessary for attribution of notifications to the
unit.</para>
- <para><function>sd_notifyf()</function> is similar to
- <function>sd_notify()</function> but takes a
- <function>printf()</function>-like format string plus
- arguments.</para>
-
- <para><function>sd_pid_notify()</function> and
- <function>sd_pid_notifyf()</function> are similar to
- <function>sd_notify()</function> and
- <function>sd_notifyf()</function> but take a process ID (PID) to
- use as originating PID for the message as first argument. This is
- useful to send notification messages on behalf of other processes,
- provided the appropriate privileges are available. If the PID
- argument is specified as 0, the process ID of the calling process
- is used, in which case the calls are fully equivalent to
- <function>sd_notify()</function> and
- <function>sd_notifyf()</function>.</para>
-
- <para><function>sd_pid_notify_with_fds()</function> is similar to
- <function>sd_pid_notify()</function> but takes an additional array
- of file descriptors. These file descriptors are sent along the
- notification message to the service manager. This is particularly
- useful for sending <literal>FDSTORE=1</literal> messages, as
- described above. The additional arguments are a pointer to the
- file descriptor array plus the number of file descriptors in the
- array. If the number of file descriptors is passed as 0, the call
- is fully equivalent to <function>sd_pid_notify()</function>, i.e.
- no file descriptors are passed. Note that sending file descriptors
- to the service manager on messages that do not expect them (i.e.
- without <literal>FDSTORE=1</literal>) they are immediately closed
- on reception.</para>
-
- <para><function>sd_notify_barrier()</function> allows the caller to
- synchronize against reception of previously sent notification messages
- and uses the <varname>BARRIER=1</varname> command. It takes a relative
- <varname>timeout</varname> value in microseconds which is passed to
+ <para><function>sd_notifyf()</function> is similar to <function>sd_notify()</function> but takes a
+ <function>printf()</function>-like format string plus arguments.</para>
+
+ <para><function>sd_pid_notify()</function> and <function>sd_pid_notifyf()</function> are similar to
+ <function>sd_notify()</function> and <function>sd_notifyf()</function> but take a process ID (PID) to use
+ as originating PID for the message as first argument. This is useful to send notification messages on
+ behalf of other processes, provided the appropriate privileges are available. If the PID argument is
+ specified as 0, the process ID of the calling process is used, in which case the calls are fully
+ equivalent to <function>sd_notify()</function> and <function>sd_notifyf()</function>.</para>
+
+ <para><function>sd_pid_notify_with_fds()</function> is similar to <function>sd_pid_notify()</function>
+ but takes an additional array of file descriptors. These file descriptors are sent along the notification
+ message to the service manager. This is particularly useful for sending <literal>FDSTORE=1</literal>
+ messages, as described above. The additional arguments are a pointer to the file descriptor array plus
+ the number of file descriptors in the array. If the number of file descriptors is passed as 0, the call
+ is fully equivalent to <function>sd_pid_notify()</function>, i.e. no file descriptors are passed. Note
+ that file descriptors sent to the service manager on a message without <literal>FDSTORE=1</literal> are
+ immediately closed on reception.</para>
+
+ <para><function>sd_notify_barrier()</function> allows the caller to synchronize against reception of
+ previously sent notification messages and uses the <varname>BARRIER=1</varname> command. It takes a
+ relative <varname>timeout</varname> value in microseconds which is passed to
<citerefentry><refentrytitle>ppoll</refentrytitle><manvolnum>2</manvolnum>
</citerefentry>. A value of UINT64_MAX is interpreted as infinite timeout.
</para>
<refsect1>
<title>Return Value</title>
- <para>On failure, these calls return a negative errno-style error code. If <varname>$NOTIFY_SOCKET</varname> was
- not set and hence no status message could be sent, 0 is returned. If the status was sent, these functions return a
- positive value. In order to support both service managers that implement this scheme and those which do not, it is
- generally recommended to ignore the return value of this call. Note that the return value simply indicates whether
- the notification message was enqueued properly, it does not reflect whether the message could be processed
+ <para>On failure, these calls return a negative errno-style error code. If
+ <varname>$NOTIFY_SOCKET</varname> was not set and hence no status message could be sent, 0 is
+ returned. If the status was sent, these functions return a positive value. In order to support both
+ service managers that implement this scheme and those which do not, it is generally recommended to ignore
+ the return value of this call. Note that the return value simply indicates whether the notification
+ message was enqueued properly, it does not reflect whether the message could be processed
successfully. Specifically, no error is returned when a file descriptor is attempted to be stored using
- <varname>FDSTORE=1</varname> but the service is not actually configured to permit storing of file descriptors (see
- above).</para>
+ <varname>FDSTORE=1</varname> but the service is not actually configured to permit storing of file
+ descriptors (see above).</para>
</refsect1>
<refsect1>
<xi:include href="libsystemd-pkgconfig.xml" xpointer="pkgconfig-text"/>
<xi:include href="threads-aware.xml" xpointer="getenv"/>
- <para>These functions send a single datagram with the
- state string as payload to the socket referenced in the
- <varname>$NOTIFY_SOCKET</varname> environment variable. If the
- first character of <varname>$NOTIFY_SOCKET</varname> is
- <literal>/</literal> or <literal>@</literal>, the string is understood
- as an <constant>AF_UNIX</constant> or Linux abstract namespace socket
- (respectively), and in both cases the datagram is accompanied by the
- process credentials of the sending service, using SCM_CREDENTIALS. If
- the string starts with <literal>vsock:</literal> then the string is
- understood as an <constant>AF_VSOCK</constant> address, which is useful
- for hypervisors/VMMs or other processes on the host to receive a
- notification when a virtual machine has finished booting. Note that in
- case the hypervisor does not support <constant>SOCK_DGRAM</constant>
- over <constant>AF_VSOCK</constant>, <constant>SOCK_SEQPACKET</constant>
- will be used instead. The address should be in the form:
- <literal>vsock:CID:PORT</literal>. Note that unlike other uses of vsock,
- the CID is mandatory and cannot be <literal>VMADDR_CID_ANY</literal>.
- Note that PID1 will send the VSOCK packets from a privileged port
- (i.e.: lower than 1024), as an attempt to address concerns that unprivileged
- processes in the guest might try to send malicious notifications to the
- host, driving it to make destructive decisions based on them.</para>
+ <para>These functions send a single datagram with the state string as payload to the socket referenced in
+ the <varname>$NOTIFY_SOCKET</varname> environment variable. If the first character of
+ <varname>$NOTIFY_SOCKET</varname> is <literal>/</literal> or <literal>@</literal>, the string is
+ understood as an <constant>AF_UNIX</constant> or Linux abstract namespace socket (respectively), and in
+ both cases the datagram is accompanied by the process credentials of the sending service, using
+ SCM_CREDENTIALS. If the string starts with <literal>vsock:</literal> then the string is understood as an
+ <constant>AF_VSOCK</constant> address, which is useful for hypervisors/VMMs or other processes on the
+ host to receive a notification when a virtual machine has finished booting. Note that in case the
+ hypervisor does not support <constant>SOCK_DGRAM</constant> over <constant>AF_VSOCK</constant>,
+ <constant>SOCK_SEQPACKET</constant> will be used instead. The address should be in the form:
+ <literal>vsock:CID:PORT</literal>. Note that unlike other uses of vsock, the CID is mandatory and cannot
+ be <literal>VMADDR_CID_ANY</literal>. Note that PID1 will send the VSOCK packets from a privileged port
+ (i.e.: lower than 1024), as an attempt to address concerns that unprivileged processes in the guest might
+ try to send malicious notifications to the host, driving it to make destructive decisions based on
+ them.</para>
</refsect1>
<refsect1>
<varlistentry>
<term><varname>$NOTIFY_SOCKET</varname></term>
- <listitem><para>Set by the service manager for supervised
- processes for status and start-up completion notification.
- This environment variable specifies the socket
- <function>sd_notify()</function> talks to. See above for
- details.</para></listitem>
+ <listitem><para>Set by the service manager for supervised processes for status and start-up
+ completion notification. This environment variable specifies the socket
+ <function>sd_notify()</function> talks to. See above for details.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<example>
<title>Start-up Notification</title>
- <para>When a service finished starting up, it might issue the
- following call to notify the service manager:</para>
+ <para>When a service finished starting up, it might issue the following call to notify the service
+ manager:</para>
<programlisting>sd_notify(0, "READY=1");</programlisting>
</example>
<example>
<title>Extended Start-up Notification</title>
- <para>A service could send the following after completing
- initialization:</para>
+ <para>A service could send the following after completing initialization:</para>
<programlisting>
sd_notifyf(0, "READY=1\n"
<example>
<title>Store a File Descriptor in the Service Manager</title>
- <para>To store an open file descriptor in the service manager,
- in order to continue operation after a service restart without
- losing state, use <literal>FDSTORE=1</literal>:</para>
+ <para>To store an open file descriptor in the service manager, in order to continue operation after a
+ service restart without losing state, use <literal>FDSTORE=1</literal>:</para>
<programlisting>sd_pid_notify_with_fds(0, 0, "FDSTORE=1\nFDNAME=foobar", &fd, 1);</programlisting>
</example>
<example>
<title>Eliminating race conditions</title>
- <para>When the client sending the notifications is not spawned
- by the service manager, it may exit too quickly and the service
- manager may fail to attribute them correctly to the unit. To
- prevent such races, use <function>sd_notify_barrier()</function>
- to synchronize against reception of all notifications sent before
- this call is made.</para>
+ <para>When the client sending the notifications is not spawned by the service manager, it may exit too
+ quickly and the service manager may fail to attribute them correctly to the unit. To prevent such
+ races, use <function>sd_notify_barrier()</function> to synchronize against reception of all
+ notifications sent before this call is made.</para>
<programlisting>
sd_notify(0, "READY=1");
<term><option>--discover</option></term>
<listitem><para>Show a list of DDIs in well-known directories. This will show machine, portable
- service and system extension disk images in the usual directories
+ service and system/configuration extension disk images in the usual directories
<filename>/usr/lib/machines/</filename>, <filename>/usr/lib/portables/</filename>,
- <filename>/usr/lib/extensions/</filename>, <filename>/var/lib/machines/</filename>,
+ <filename>/usr/lib/confexts/</filename>, <filename>/var/lib/machines/</filename>,
<filename>/var/lib/portables/</filename>, <filename>/var/lib/extensions/</filename> and so
on.</para></listitem>
</varlistentry>
<listitem><para>This option only has an effect in automount mode,
and controls whether the automount unit shall be bound to the backing device's lifetime. If set, the
- automount point will be removed automatically when the backing device vanishes. By default the automount point
+ automount unit will be stopped automatically when the backing device vanishes. By default the automount unit
stays around, and subsequent accesses will block until backing device is replugged. This option has no effect
in case of non-device mounts, such as network or virtual file system mounts.</para>
<refnamediv>
<refname>systemd-sysext</refname>
<refname>systemd-sysext.service</refname>
+ <refname>systemd-confext</refname>
+ <refname>systemd-confext.service</refname>
<refpurpose>Activates System Extension Images</refpurpose>
</refnamediv>
<para><literallayout><filename>systemd-sysext.service</filename></literallayout></para>
+ <cmdsynopsis>
+ <command>systemd-confext</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">COMMAND</arg>
+ </cmdsynopsis>
+
+ <para><literallayout><filename>systemd-confext.service</filename></literallayout></para>
+
</refsynopsisdiv>
<refsect1>
The <filename>extension-release</filename> file follows the same format and semantics, and carries the same
content, as the <filename>os-release</filename> file of the OS, but it describes the resources carried
in the extension image.</para>
+
+ <para>The <command>systemd-confext</command> concept follows the same principle as the
+ <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ functionality but instead of working on <filename>/usr</filename> and <filename>/opt</filename>,
+ <command>confext</command> will extend only <filename>/etc</filename>. Files and directories contained
+ in the confext images outside of the <filename>/etc/</filename> hierarchy are <emphasis>not</emphasis>
+ merged, and hence have no effect when included in the image. Formats for these images are of the
+ same as sysext images.</para>
+
+ <para>Confexts are looked for in the directories <filename>/run/confexts/</filename>,
+ <filename>/var/lib/confexts/</filename>, <filename>/usr/lib/confexts/</filename> and
+ <filename>/usr/local/lib/confexts/</filename>. The first two listed directories are not suitable for
+ carrying large binary images, however are still useful for carrying symlinks to them. The primary place
+ for installing system extensions is <filename>/var/lib/confexts/</filename>. Any directories found in
+ these search directories are considered directory based confext images, any files with the
+ <filename>.raw</filename> suffix are considered disk image based confext images.</para>
+
+ <para>Again, just like sysext images, the confext images will contain a
+ <filename>/etc/extension-release.d/extension-release.<replaceable>$name</replaceable></filename>
+ file, which must match the image name (with the usual escape hatch of xattr), and again with content
+ being one or more of <varname>ID=</varname>, <varname>VERSION_ID=</varname>, and
+ <varname>CONFEXT_LEVEL</varname>. Confext images will then be checked and matched against the
+ base OS layer.</para>
</refsect1>
<refsect1>
<filename>/usr/</filename> as if it was installed in the OS image itself.) This case works regardless if
the underlying host <filename>/usr/</filename> is managed as immutable disk image or is a traditional
package manager controlled (i.e. writable) tree.</para>
- </refsect1>
+
+ <para>For the confext case, the OSConfig project aims to perform runtime reconfiguration of OS services.
+ Sometimes, there is a need to swap certain configuration parameter values or restart only a specific
+ service without deployment of new code or a complete OS deployment. In other words, we want to be able
+ to tie the most frequently configured options to runtime updateable flags that can be changed without a
+ system reboot. This will help reduce servicing times when there is a need for changing the OS configuration.</para></refsect1>
<refsect1>
<title>Commands</title>
- <para>The following commands are understood:</para>
+ <para>The following commands are understood by both the sysext and confext concepts:</para>
<variablelist>
<varlistentry>
<term><option>status</option></term>
<listitem><para>When invoked without any command verb, or when <option>status</option> is specified
- the current merge status is shown, separately for both <filename>/usr/</filename> and
- <filename>/opt/</filename>.</para></listitem>
+ the current merge status is shown, separately (for both <filename>/usr/</filename> and
+ <filename>/opt/</filename> of sysext and for <filename>/etc/</filename> of confext).</para></listitem>
</varlistentry>
<varlistentry>
<listitem><para>Merges all currently installed system extension images into
<filename>/usr/</filename> and <filename>/opt/</filename>, by overmounting these hierarchies with an
<literal>overlayfs</literal> file system combining the underlying hierarchies with those included in
- the extension images. This command will fail if the hierarchies are already merged.</para></listitem>
+ the extension images. This command will fail if the hierarchies are already merged. For confext, the merge
+ happens into the <filename>/etc/</filename> directory instead.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>unmerge</option></term>
<listitem><para>Unmerges all currently installed system extension images from
- <filename>/usr/</filename> and <filename>/opt/</filename>, by unmounting the
- <literal>overlayfs</literal> file systems created by <option>merge</option>
+ <filename>/usr/</filename> and <filename>/opt/</filename> for sysext and <filename>/etc/</filename>,
+ for confext, by unmounting the <literal>overlayfs</literal> file systems created by <option>merge</option>
prior.</para></listitem>
</varlistentry>
mounted the existing <literal>overlayfs</literal> instance is unmounted temporarily, and then
replaced by a new version. This command is useful after installing/removing system extension images,
in order to update the <literal>overlayfs</literal> file system accordingly. If no system extensions
- are installed when this command is executed, the equivalent of <option>unmerge</option> is
- executed, without establishing any new <literal>overlayfs</literal> instance. Note that currently
- there's a brief moment where neither the old nor the new <literal>overlayfs</literal> file system is
- mounted. This implies that all resources supplied by a system extension will briefly disappear — even
- if it exists continuously during the refresh operation.</para></listitem>
+ are installed when this command is executed, the equivalent of <option>unmerge</option> is executed,
+ without establishing any new <literal>overlayfs</literal> instance.
+ Note that currently there's a brief moment where neither the old nor the new <literal>overlayfs</literal>
+ file system is mounted. This implies that all resources supplied by a system extension will briefly
+ disappear — even if it exists continuously during the refresh operation.</para></listitem>
</varlistentry>
<varlistentry>
<listitem><para>Operate relative to the specified root directory, i.e. establish the
<literal>overlayfs</literal> mount not on the top-level host <filename>/usr/</filename> and
- <filename>/opt/</filename> hierarchies, but below some specified root directory.</para></listitem>
+ <filename>/opt/</filename> hierarchies for sysext or <filename>/etc/</filename> for confext,
+ but below some specified root directory.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--force</option></term>
<listitem><para>When merging system extensions into <filename>/usr/</filename> and
- <filename>/opt/</filename>, ignore version incompatibilities, i.e. force merging regardless of
- whether the version information included in the extension images matches the host or
- not.</para></listitem>
+ <filename>/opt/</filename> for sysext and <filename>/etc/</filename> for confext,
+ ignore version incompatibilities, i.e. force merging regardless of
+ whether the version information included in the images matches the host or not.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><varname>$NOTIFY_SOCKET</varname></term>
- <listitem><para>The socket
- <function>sd_notify()</function> talks to. See
+ <listitem><para>The socket <function>sd_notify()</function> talks to. See
<citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
</para></listitem>
</varlistentry>
convey.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>$FDSTORE</varname></term>
+
+ <listitem><para>If the file descriptor store is enabled for a service
+ (i.e. <varname>FileDescriptorStoreMax=</varname> is set to a non-zero value, see
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details), this environment variable will be set to the maximum number of permitted entries, as
+ per the setting. Applications may check this environment variable before sending file descriptors
+ to the service manager via <function>sd_pid_notify_with_fds()</function> (see
+ <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry> for
+ details).</para></listitem>
+ </varlistentry>
+
</variablelist>
<para>For system services, when <varname>PAMName=</varname> is enabled and <command>pam_systemd</command> is part
<term><varname>Endpoint=</varname></term>
<listitem>
<para>Sets an endpoint IP address or hostname, followed by a colon, and then
- a port number. This endpoint will be updated automatically once to
+ a port number. IPv6 address must be in the square brackets. For example,
+ <literal>111.222.333.444:51820</literal> for IPv4 and <literal>[1111:2222::3333]:51820</literal>
+ for IPv6 address. This endpoint will be updated automatically once to
the most recent source IP address and port of correctly
authenticated packets from the peer at configuration time.</para>
</listitem>
allow unprivileged clients to query the list of currently open file descriptors of a
service. Sensitive data may hence be safely placed inside the referenced files, but should not be
attached to the metadata (e.g. included in filenames) of the stored file
- descriptors.</para></listitem>
+ descriptors.</para>
+
+ <para>If this option is set to a non-zero value the <varname>$FDSTORE</varname> environment variable
+ will be set for processes invoked for this service. See
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+ details.</para></listitem>
</varlistentry>
<varlistentry>
tools/testing/selftests/bpf/config.x86_64 \
tools/testing/selftests/bpf/config
- make O="$BUILDDIR" -j "$(nproc)"
+ # Make sure systemd-boot boots this kernel and not the distro provided one by overriding the version.
+ make O="$BUILDDIR" VERSION=99 -j "$(nproc)"
- KERNEL_RELEASE=$(make O="$BUILDDIR" -s kernelrelease)
+ KERNEL_RELEASE=$(make O="$BUILDDIR" VERSION=99 -s kernelrelease)
mkdir -p "$DESTDIR/usr/lib/modules/$KERNEL_RELEASE"
- make O="$BUILDDIR" INSTALL_MOD_PATH="$DESTDIR/usr" modules_install
- make O="$BUILDDIR" INSTALL_PATH="$DESTDIR/usr/lib/modules/$KERNEL_RELEASE" install
+ make O="$BUILDDIR" VERSION=99 INSTALL_MOD_PATH="$DESTDIR/usr" modules_install
+ make O="$BUILDDIR" VERSION=99 INSTALL_PATH="$DESTDIR/usr/lib/modules/$KERNEL_RELEASE" install
mkdir -p "$DESTDIR/usr/lib/kernel/selftests"
- make -C tools/testing/selftests -j "$(nproc)" O="$BUILDDIR" KSFT_INSTALL_PATH="$DESTDIR/usr/lib/kernel/selftests" SKIP_TARGETS="" install
+ make -C tools/testing/selftests -j "$(nproc)" O="$BUILDDIR" VERSION=99 KSFT_INSTALL_PATH="$DESTDIR/usr/lib/kernel/selftests" SKIP_TARGETS="" install
ln -sf /usr/lib/kernel/selftests/bpf/bpftool "$DESTDIR/usr/bin/bpftool"
fi
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-
[Output]
Bootable=yes
# Prevent ASAN warnings when building the image and ship the real ASAN options prefixed with MKOSI_.
[Content]
BuildDirectory=mkosi.builddir
-Cache=mkosi.cache
+CacheDirectory=mkosi.cache
ExtraTrees=src:/root/src
Packages=
acl
#
# Copyright © 2016 Zeal Jagannatha
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.conf in the project root directory and invoke "mkosi" to build an OS image.
-
-[Distribution]
+[Match]
Distribution=arch
[Content]
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.conf in the project root directory and invoke "mkosi" to build an OS image.
-
-# We use python3[.][9]dist() throughout this file because we need to make sure the python3.9dis() packages
-# are installed on CentOS Stream 8. mkosi doesn't support release specific configuration yet so we use the
-# globs to get the necessary packages on both CentOS Stream 8 and CentOS Stream 9.
+[Match]
+Distribution=centos
[Distribution]
-Distribution=centos
Repositories=epel
-RepositoryDirectory=mkosi.conf.d/centos/mkosi.reposdir
[Content]
Packages=
polkit
popt
procps-ng
- python3[.][9]dist(pefile)
- python3[.][9]dist(pluggy) # python39-pluggy is a pytest dependency that's not installed for some reason.
- python3[.][9]dist(psutil)
- python3[.][9]dist(pytest)
- python39
+ python3-docutils
quota
tpm2-tss
vim-common
glibc-devel.i686
glibc-static
glibc-static.i686
- libgcrypt-devel # CentOS Stream 8 libgcrypt-devel doesn't ship a pkg-config file.
libxslt
pam-devel
perl-interpreter
pkgconfig(tss2-rc)
pkgconfig(valgrind)
pkgconfig(xkbcommon)
- python3-docutils
- python3[.][9]dist(jinja2)
- python3[.][9]dist(lxml)
- python3[.][9]dist(pyelftools)
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.conf in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=debian
[Distribution]
-Distribution=debian
Release=testing
[Content]
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.conf in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=fedora
[Distribution]
-Distribution=fedora
Release=37
[Content]
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.conf in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=opensuse
[Distribution]
-Distribution=opensuse
Release=tumbleweed
[Content]
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.conf in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=ubuntu
[Distribution]
-Distribution=ubuntu
Release=jammy
Repositories=main,universe
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=centos
+Release=8
+
+[Content]
+Packages=
+ python39
+ python3.9dist(pefile)
+ python3.9dist(pluggy) # python39-pluggy is a pytest dependency that's not installed for some reason.
+ python3.9dist(psutil)
+ python3.9dist(pytest)
+
+BuildPackages=
+ libgcrypt-devel # CentOS Stream 8 libgcrypt-devel doesn't ship a pkg-config file.
+ python3.9dist(jinja2)
+ python3.9dist(lxml)
+ python3.9dist(pyelftools)
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
[powertools-hotfixes]
name=powertools-hotfixes
mirrorlist=http://mirrorlist.centos.org/?release=$stream&arch=$basearch&repo=PowerTools
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=centos
+Release=9
+
+[Content]
+Packages=
+ python3dist(pefile)
+ python3dist(pluggy) # python39-pluggy is a pytest dependency that's not installed for some reason.
+ python3dist(psutil)
+ python3dist(pytest)
+
+BuildPackages=
+ pkgconfig(libgcrypt)
+ python3dist(jinja2)
+ python3dist(lxml)
+ python3dist(pyelftools)
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG_SECONDARY_KEYRING=y
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
CONFIG_DM_VERITY=y
+CONFIG_DMI_SYSFS=y
+CONFIG_DMI=y
CONFIG_EFI_MIXED=y
CONFIG_EFI_STUB=y
CONFIG_EFI_ZBOOT=y
CONFIG_HOTPLUG_PCI=y
CONFIG_HPET=y
CONFIG_HUGETLBFS=y
+CONFIG_HW_RANDOM_VIRTIO=y
CONFIG_HW_RANDOM=y
CONFIG_HYPERVISOR_GUEST=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_IKCONFIG=y
CONFIG_IMA_APPRAISE=y
CONFIG_IMA_ARCH_POLICY=y
CONFIG_IMA=y
CONFIG_INTEGRITY_MACHINE_KEYRING=y
CONFIG_INTEGRITY_PLATFORM_KEYRING=y
CONFIG_INTEGRITY_SIGNATURE=y
+CONFIG_IOSCHED_BFQ=y
CONFIG_IP_ADVANCED_ROUTER=y
CONFIG_IP_MULTICAST=y
CONFIG_IP_MULTIPLE_TABLES=y
CONFIG_SCSI=y
CONFIG_SECONDARY_TRUSTED_KEYRING=y
CONFIG_SECURITY_NETWORK=y
+CONFIG_SECURITY_YAMA=y
CONFIG_SECURITY=y
CONFIG_SERIAL_8250_CONSOLE=y
-CONFIG_SERIAL_8250_DETECT_IRQ=y
-CONFIG_SERIAL_8250_EXTENDED=y
-CONFIG_SERIAL_8250_MANY_PORTS=y
-CONFIG_SERIAL_8250_NR_UARTS=32
-CONFIG_SERIAL_8250_RSA=y
-CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_PCI=y
CONFIG_SERIAL_8250=y
-CONFIG_SERIAL_NONSTANDARD=y
CONFIG_SMP=y
CONFIG_SWAP=y
CONFIG_SYSTEM_BLACKLIST_KEYRING=y
CONFIG_VIRTIO_INPUT=y
CONFIG_VIRTIO_NET=y
CONFIG_VIRTIO_PCI=y
+CONFIG_VIRTIO_VSOCKETS=y
+CONFIG_VSOCKETS=y
CONFIG_WATCHDOG=y
CONFIG_X86_ACPI_CPUFREQ=y
CONFIG_X86_CPUID=y
ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_tape_end"
# type 8 devices are "Medium Changers"
-SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --allowlisted -d $devnode", \
SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL} tape/by-id/scsi-$env{ID_SERIAL}-changer"
# iSCSI devices from the same host have all the same ID_SERIAL,
KERNEL=="st*[0-9]|nst*[0-9]", ATTRS{ieee1394_id}=="?*", ENV{ID_SERIAL}="$attr{ieee1394_id}", ENV{ID_BUS}="ieee1394"
KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", KERNELS=="[0-9]*:*[0-9]", ENV{.BSG_DEV}="$root/bsg/$id"
-KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --whitelisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
+KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --allowlisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
KERNEL=="st*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}", OPTIONS+="link_priority=10"
KERNEL=="st*[0-9]", ENV{ID_SCSI_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SCSI_SERIAL}"
KERNEL=="nst*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}-nst"
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{nsid}=="?*", ENV{ID_NSID}="$attr{nsid}"
+# obsolete symlink with non-escaped characters, kept for backward compatiblity
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
+ ENV{ID_MODEL}!="*/*", ENV{ID_SERIAL_SHORT}!="*/*", \
+ ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
# obsolete symlink that might get overridden on adding a new nvme controller, kept for backward compatibility
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{nsid}=="?*", ENV{ID_NSID}="$attr{nsid}"
+# obsolete symlink with non-escaped characters, kept for backward compatiblity
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
+ ENV{ID_MODEL}!="*/*", ENV{ID_SERIAL_SHORT}!="*/*", \
+ ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
# obsolete symlink that might get overridden on adding a new nvme controller, kept for backward compatibility
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
-KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", ENV{ID_NSID}=="?*", \
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", ENV{ID_NSID}=="?*", \
OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}_$env{ID_NSID}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
# virtio-blk
KERNEL=="sd*[!0-9]|sr*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
# SCSI devices
-KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="scsi"
-KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="cciss"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="scsi"
+KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="cciss"
KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
KERNEL=="sd*|cciss*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n"
# Previously, ata_id in the above might not be able to retrieve attributes correctly,
--show-machine --unique --acquired --activatable --list
-q --quiet --verbose --expect-reply=no --auto-start=no
--allow-interactive-authorization=no --augment-creds=no
- --watch-bind=yes -j -l --full'
+ --watch-bind=yes -j -l --full --xml-interface'
[ARG]='--address -H --host -M --machine --match --timeout --size --json
--destination'
)
--- /dev/null
+# systemd-confext(8) completion -*- shell-script -*-
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <https://www.gnu.org/licenses/>.
+
+__contains_word() {
+ local w word=$1; shift
+ for w in "$@"; do
+ [[ $w = "$word" ]] && return
+ done
+}
+
+_systemd-confext() {
+ local i verb comps
+ local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
+ local -A OPTS=(
+ [STANDALONE]='-h --help --version
+ --no-pager
+ --no-legend
+ --force'
+ [ARG]='--root
+ --json'
+ )
+
+ local -A VERBS=(
+ [STANDALONE]='status
+ merge
+ unmerge
+ refresh
+ list'
+ )
+
+ _init_completion || return
+
+ if __contains_word "$prev" ${OPTS[ARG]}; then
+ case $prev in
+ --root)
+ comps=$(compgen -A directory -- "$cur" )
+ compopt -o dirnames
+ ;;
+ --json)
+ comps='pretty short off'
+ ;;
+ esac
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+ fi
+
+ if [[ "$cur" = -* ]]; then
+ COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
+ return 0
+ fi
+
+ for ((i=0; i < COMP_CWORD; i++)); do
+ if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} &&
+ ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then
+ verb=${COMP_WORDS[i]}
+ break
+ fi
+ done
+
+ if [[ -z ${verb-} ]]; then
+ comps=${VERBS[*]}
+ elif __contains_word "$verb" ${VERBS[STANDALONE]}; then
+ comps=''
+ fi
+
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+}
+
+complete -F _systemd-confext systemd-confext
'--list[Do not show tree, but simple object path list]' \
{-q,--quiet}'[Do not show method call reply]'\
'--verbose[Show result values in long format]' \
+ '--xml-interface[Dump the XML description in introspect command]' \
'--json=[Show result values in long format]:format:_busctl_get_json' \
'-j[Show pretty json in interactive sessions, short json otherwise]' \
'--expect-reply=[Expect a method call reply]:boolean:(1 0)' \
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+hl:aE:d", options, NULL)) >= 0)
switch (c) {
case 'h':
if (r < 0)
return bus_log_connect_error(r, arg_transport);
- n = acquire_time_data(bus, ×);
+ n = acquire_time_data(bus, /* require_finished = */ false, ×);
if (n <= 0)
return n;
typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
- r = acquire_boot_times(bus, &boot);
+ r = acquire_boot_times(bus, /* require_finished = */ true, &boot);
if (r < 0)
return r;
times = hashmap_get(unit_times_hashmap, id);
- r = acquire_boot_times(bus, &boot);
+ r = acquire_boot_times(bus, /* require_finished = */ true, &boot);
if (r < 0)
return r;
if (r < 0)
return bus_log_connect_error(r, arg_transport);
- n = acquire_time_data(bus, ×);
+ n = acquire_time_data(bus, /* require_finished = */ true, ×);
if (n <= 0)
return n;
if (r < 0)
return bus_log_connect_error(r, arg_transport);
- n = acquire_boot_times(bus, &boot);
+ n = acquire_boot_times(bus, /* require_finished = */ true, &boot);
if (n < 0)
return n;
return n;
}
- n = acquire_time_data(bus, ×);
+ n = acquire_time_data(bus, /* require_finished = */ true, ×);
if (n <= 0)
return n;
}
}
-int acquire_boot_times(sd_bus *bus, BootTimes **ret) {
+static int log_not_finished(usec_t finish_time) {
+ return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS),
+ "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
+ "Please try again later.\n"
+ "Hint: Use 'systemctl%s list-jobs' to see active jobs",
+ finish_time,
+ arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "" : " --user");
+}
+
+int acquire_boot_times(sd_bus *bus, bool require_finished, BootTimes **ret) {
static const struct bus_properties_map property_map[] = {
{ "FirmwareTimestampMonotonic", "t", NULL, offsetof(BootTimes, firmware_time) },
{ "LoaderTimestampMonotonic", "t", NULL, offsetof(BootTimes, loader_time) },
static bool cached = false;
int r;
- if (cached)
- goto finish;
+ if (cached) {
+ if (require_finished && times.finish_time <= 0)
+ return log_not_finished(times.finish_time);
+
+ if (ret)
+ *ret = ×
+ return 0;
+ }
assert_cc(sizeof(usec_t) == sizeof(uint64_t));
if (r < 0)
return log_error_errno(r, "Failed to get timestamp properties: %s", bus_error_message(&error, r));
- if (times.finish_time <= 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS),
- "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
- "Please try again later.\n"
- "Hint: Use 'systemctl%s list-jobs' to see active jobs",
- times.finish_time,
- arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "" : " --user");
+ if (require_finished && times.finish_time <= 0)
+ return log_not_finished(times.finish_time);
if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM && times.security_start_time > 0) {
/* security_start_time is set when systemd is not running under container environment. */
times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time =
times.userspace_time = times.security_start_time = times.security_finish_time = 0;
- subtract_timestamp(×.finish_time, times.reverse_offset);
+ if (times.finish_time > 0)
+ subtract_timestamp(×.finish_time, times.reverse_offset);
subtract_timestamp(×.generators_start_time, times.reverse_offset);
subtract_timestamp(×.generators_finish_time, times.reverse_offset);
cached = true;
-finish:
- *ret = ×
+ if (ret)
+ *ret = ×
return 0;
}
BootTimes *t;
int r;
- r = acquire_boot_times(bus, &t);
+ r = acquire_boot_times(bus, /* require_finished = */ true, &t);
if (r < 0)
return r;
return mfree(t);
}
-int acquire_time_data(sd_bus *bus, UnitTimes **out) {
+int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out) {
static const struct bus_properties_map property_map[] = {
{ "InactiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activating) },
{ "ActiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activated) },
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(unit_times_free_arrayp) UnitTimes *unit_times = NULL;
- BootTimes *boot_times = NULL;
+ BootTimes *boot_times;
size_t c = 0;
UnitInfo u;
int r;
- r = acquire_boot_times(bus, &boot_times);
+ r = acquire_boot_times(bus, require_finished, &boot_times);
if (r < 0)
return r;
usec_t time;
} UnitTimes;
-int acquire_boot_times(sd_bus *bus, BootTimes **ret);
+int acquire_boot_times(sd_bus *bus, bool require_finished, BootTimes **ret);
int pretty_boot_time(sd_bus *bus, char **ret);
UnitTimes* unit_times_free_array(UnitTimes *t);
DEFINE_TRIVIAL_CLEANUP_FUNC(UnitTimes*, unit_times_free_array);
-int acquire_time_data(sd_bus *bus, UnitTimes **out);
+int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out);
/* Note the asymmetry: the long option --echo= allows an optional argument, the short option does
* not. */
+
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+hen", options, NULL)) >= 0)
switch (c) {
_cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
bool exists = true, append_trail_slash = false;
- struct stat previous_stat;
+ struct stat st; /* stat obtained from fd */
const char *todo;
int r;
/* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
* set and doesn't care about any of the other special features we provide either. */
- r = openat(dir_fd, buffer ?: path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
+ r = openat(dir_fd, path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
if (r < 0)
return -errno;
return 0;
}
- if (!buffer) {
- buffer = strdup(path);
- if (!buffer)
- return -ENOMEM;
- }
+ buffer = strdup(path);
+ if (!buffer)
+ return -ENOMEM;
/* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
* a relative path would be interpreted relative to the current working directory. */
if (fd < 0)
return -errno;
- if (fstat(fd, &previous_stat) < 0)
+ if (fstat(fd, &st) < 0)
return -errno;
if (flags & CHASE_TRAIL_SLASH)
for (todo = buffer;;) {
_cleanup_free_ char *first = NULL;
_cleanup_close_ int child = -EBADF;
- struct stat st;
+ struct stat st_child;
const char *e;
r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
if (path_equal(first, "..")) {
_cleanup_free_ char *parent = NULL;
_cleanup_close_ int fd_parent = -EBADF;
+ struct stat st_parent;
/* If we already are at the top, then going up will not change anything. This is
* in-line with how the kernel handles this. */
if (fd_parent < 0)
return -errno;
- if (fstat(fd_parent, &st) < 0)
+ if (fstat(fd_parent, &st_parent) < 0)
return -errno;
- /* If we opened the same directory, that means we're at the host root directory, so
+ /* If we opened the same directory, that _may_ indicate that we're at the host root
+ * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
* going up won't change anything. */
- if (st.st_dev == previous_stat.st_dev && st.st_ino == previous_stat.st_ino)
- continue;
+ if (stat_inode_same(&st_parent, &st)) {
+ r = dir_fd_is_root(fd);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+ }
r = path_extract_directory(done, &parent);
if (r >= 0 || r == -EDESTADDRREQ)
if (flags & CHASE_STEP)
goto chased_one;
- if (flags & CHASE_SAFE) {
- if (unsafe_transition(&previous_stat, &st))
- return log_unsafe_transition(fd, fd_parent, path, flags);
-
- previous_stat = st;
- }
+ if (flags & CHASE_SAFE &&
+ unsafe_transition(&st, &st_parent))
+ return log_unsafe_transition(fd, fd_parent, path, flags);
if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
break;
+ /* update fd and stat */
+ st = st_parent;
close_and_replace(fd, fd_parent);
-
continue;
}
return r;
}
- if (fstat(child, &st) < 0)
+ if (fstat(child, &st_child) < 0)
return -errno;
+
if ((flags & CHASE_SAFE) &&
- unsafe_transition(&previous_stat, &st))
+ unsafe_transition(&st, &st_child))
return log_unsafe_transition(fd, child, path, flags);
- previous_stat = st;
-
if ((flags & CHASE_NO_AUTOFS) &&
fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
return log_autofs_mount_point(child, path, flags);
- if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
+ if (S_ISLNK(st_child.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
_cleanup_free_ char *destination = NULL;
if (flags & CHASE_PROHIBIT_SYMLINKS)
if (fd < 0)
return fd;
- if (flags & CHASE_SAFE) {
- if (fstat(fd, &st) < 0)
- return -errno;
+ if (fstat(fd, &st) < 0)
+ return -errno;
- if (unsafe_transition(&previous_stat, &st))
- return log_unsafe_transition(child, fd, path, flags);
-
- previous_stat = st;
- }
+ if (flags & CHASE_SAFE &&
+ unsafe_transition(&st_child, &st))
+ return log_unsafe_transition(child, fd, path, flags);
r = free_and_strdup(&done, need_absolute ? "/" : NULL);
if (r < 0)
break;
/* And iterate again, but go one directory further down. */
+ st = st_child;
close_and_replace(fd, child);
}
if (flags & CHASE_PARENT) {
- r = fd_verify_directory(fd);
+ r = stat_verify_directory(&st);
if (r < 0)
return r;
}
return r;
/* Simplify the root directory, so that it has no duplicate slashes and nothing at the
- * end. While we won't resolve the root path we still simplify it. Note that dropping the
- * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
- * anyway. Moreover at the end of this function after processing everything we'll always turn
- * the empty string back to "/". */
- delete_trailing_chars(root, "/");
+ * end. While we won't resolve the root path we still simplify it. */
path_simplify(root);
+ assert(path_is_absolute(root));
+ assert(!empty_or_root(root));
+
if (flags & CHASE_PREFIX_ROOT) {
absolute = path_join(root, path);
if (!absolute)
return -ENOMEM;
}
+
+ flags |= CHASE_AT_RESOLVE_IN_ROOT;
}
if (!absolute) {
if (fd < 0)
return -errno;
- if (!empty_or_root(root))
- flags |= CHASE_AT_RESOLVE_IN_ROOT;
-
r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
if (r < 0)
return r;
CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */
CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. Note that the
* full path is still stored in ret_path and only the returned
- * file descriptor will point to the parent directory. */
+ * file descriptor will point to the parent directory. Note that
+ * the result path is the root or '.', then the file descriptor
+ * also points to the result path even if this flag is set. */
CHASE_MKDIR_0755 = 1 << 11, /* Create any missing parent directories in the given path. */
CHASE_EXTRACT_FILENAME = 1 << 12, /* Only return the last component of the resolved path */
} ChaseFlags;
int chase_and_fopenat_unlocked(int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path);
int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename);
-
DEFINE_STRING_TABLE_LOOKUP(compression, Compression);
+bool compression_supported(Compression c) {
+ static const unsigned supported =
+ (1U << COMPRESSION_NONE) |
+ (1U << COMPRESSION_XZ) * HAVE_XZ |
+ (1U << COMPRESSION_LZ4) * HAVE_LZ4 |
+ (1U << COMPRESSION_ZSTD) * HAVE_ZSTD;
+
+ return c >= 0 && c < _COMPRESSION_MAX && FLAGS_SET(supported, 1U << c);
+}
+
int compress_blob_xz(const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size) {
#if HAVE_XZ
return -ENOBUFS;
*dst_size = out_pos;
- return COMPRESSION_XZ;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
unaligned_write_le64(dst, src_size);
*dst_size = r + 8;
- return COMPRESSION_LZ4;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
return zstd_ret_to_errno(k);
*dst_size = k;
- return COMPRESSION_ZSTD;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
s.total_in, s.total_out,
(double) s.total_out / s.total_in * 100);
- return COMPRESSION_XZ;
+ return 0;
}
}
}
total_in, total_out,
(double) total_out / total_in * 100);
- return COMPRESSION_LZ4;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
log_debug("ZSTD compression finished (%" PRIu64 " -> %" PRIu64 " bytes)",
in_bytes, max_bytes - left);
- return COMPRESSION_ZSTD;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
#pragma once
#include <errno.h>
+#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
const char* compression_to_string(Compression compression);
Compression compression_from_string(const char *compression);
+bool compression_supported(Compression c);
+
int compress_blob_xz(const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size);
int compress_blob_lz4(const void *src, uint64_t src_size,
int decompress_stream_lz4(int fdf, int fdt, uint64_t max_size);
int decompress_stream_zstd(int fdf, int fdt, uint64_t max_size);
-static inline int compress_blob_explicit(
+static inline int compress_blob(
Compression compression,
const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size) {
}
}
-#define compress_blob(src, src_size, dst, dst_alloc_size, dst_size) \
- compress_blob_explicit( \
- DEFAULT_COMPRESSION, \
- src, src_size, \
- dst, dst_alloc_size, dst_size)
-
static inline int compress_stream(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) {
switch (DEFAULT_COMPRESSION) {
case COMPRESSION_ZSTD:
#include "terminal-util.h"
static int files_add(
- Hashmap **h,
+ DIR *dir,
+ const char *dirpath,
+ Hashmap **files,
Set **masked,
const char *suffix,
- const char *root,
- unsigned flags,
- const char *path) {
+ unsigned flags) {
- _cleanup_free_ char *dirpath = NULL;
- _cleanup_closedir_ DIR *dir = NULL;
int r;
- assert(h);
+ assert(dir);
+ assert(dirpath);
+ assert(files);
assert(masked);
- assert(path);
-
- r = chase_and_opendir(path, root, CHASE_PREFIX_ROOT, &dirpath, &dir);
- if (r == -ENOENT)
- return 0;
- if (r < 0)
- return log_debug_errno(r, "Failed to open directory '%s/%s': %m", empty_or_root(root) ? "" : root, dirpath);
FOREACH_DIRENT(de, dir, return -errno) {
_cleanup_free_ char *n = NULL, *p = NULL;
continue;
/* Has this file already been found in an earlier directory? */
- if (hashmap_contains(*h, de->d_name)) {
+ if (hashmap_contains(*files, de->d_name)) {
log_debug("Skipping overridden file '%s/%s'.", dirpath, de->d_name);
continue;
}
return -ENOMEM;
if ((flags & CONF_FILES_BASENAME))
- r = hashmap_ensure_put(h, &string_hash_ops_free, n, n);
+ r = hashmap_ensure_put(files, &string_hash_ops_free, n, n);
else {
p = path_join(dirpath, de->d_name);
if (!p)
return -ENOMEM;
- r = hashmap_ensure_put(h, &string_hash_ops_free_free, n, p);
+ r = hashmap_ensure_put(files, &string_hash_ops_free_free, n, p);
}
if (r < 0)
return r;
}
static int base_cmp(char * const *a, char * const *b) {
- return strcmp(basename(*a), basename(*b));
+ assert(a);
+ assert(b);
+ return path_compare_filename(*a, *b);
}
-static int conf_files_list_strv_internal(
+static int copy_and_sort_files_from_hashmap(Hashmap *fh, char ***ret) {
+ _cleanup_free_ char **sv = NULL;
+ char **files;
+
+ assert(ret);
+
+ sv = hashmap_get_strv(fh);
+ if (!sv)
+ return -ENOMEM;
+
+ /* The entries in the array given by hashmap_get_strv() are still owned by the hashmap. */
+ files = strv_copy(sv);
+ if (!files)
+ return -ENOMEM;
+
+ typesafe_qsort(files, strv_length(files), base_cmp);
+
+ *ret = files;
+ return 0;
+}
+
+int conf_files_list_strv(
char ***ret,
const char *suffix,
const char *root,
unsigned flags,
- char **dirs) {
+ const char * const *dirs) {
_cleanup_hashmap_free_ Hashmap *fh = NULL;
_cleanup_set_free_ Set *masked = NULL;
- _cleanup_strv_free_ char **files = NULL;
- _cleanup_free_ char **sv = NULL;
int r;
assert(ret);
- /* This alters the dirs string array */
- if (!path_strv_resolve_uniq(dirs, root))
- return -ENOMEM;
-
STRV_FOREACH(p, dirs) {
- r = files_add(&fh, &masked, suffix, root, flags, *p);
+ _cleanup_closedir_ DIR *dir = NULL;
+ _cleanup_free_ char *path = NULL;
+
+ r = chase_and_opendir(*p, root, CHASE_PREFIX_ROOT, &path, &dir);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_debug_errno(r, "Failed to chase and open directory '%s', ignoring: %m", *p);
+ continue;
+ }
+
+ r = files_add(dir, path, &fh, &masked, suffix, flags);
if (r == -ENOMEM)
return r;
if (r < 0)
- log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p);
+ log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path);
}
- sv = hashmap_get_strv(fh);
- if (!sv)
- return -ENOMEM;
+ return copy_and_sort_files_from_hashmap(fh, ret);
+}
- /* The entries in the array given by hashmap_get_strv() are still owned by the hashmap. */
- files = strv_copy(sv);
- if (!files)
- return -ENOMEM;
+int conf_files_list_strv_at(
+ char ***ret,
+ const char *suffix,
+ int rfd,
+ unsigned flags,
+ const char * const *dirs) {
- typesafe_qsort(files, strv_length(files), base_cmp);
- *ret = TAKE_PTR(files);
+ _cleanup_hashmap_free_ Hashmap *fh = NULL;
+ _cleanup_set_free_ Set *masked = NULL;
+ int r;
- return 0;
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(ret);
+
+ STRV_FOREACH(p, dirs) {
+ _cleanup_closedir_ DIR *dir = NULL;
+ _cleanup_free_ char *path = NULL;
+
+ r = chase_and_opendirat(rfd, *p, CHASE_AT_RESOLVE_IN_ROOT, &path, &dir);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_debug_errno(r, "Failed to chase and open directory '%s', ignoring: %m", *p);
+ continue;
+ }
+
+ r = files_add(dir, path, &fh, &masked, suffix, flags);
+ if (r == -ENOMEM)
+ return r;
+ if (r < 0)
+ log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path);
+ }
+
+ return copy_and_sort_files_from_hashmap(fh, ret);
}
int conf_files_insert(char ***strv, const char *root, char **dirs, const char *path) {
return r;
}
-int conf_files_list_strv(char ***ret, const char *suffix, const char *root, unsigned flags, const char* const* dirs) {
- _cleanup_strv_free_ char **copy = NULL;
-
- assert(ret);
-
- copy = strv_copy((char**) dirs);
- if (!copy)
- return -ENOMEM;
+int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir) {
+ return conf_files_list_strv(ret, suffix, root, flags, STRV_MAKE_CONST(dir));
+}
- return conf_files_list_strv_internal(ret, suffix, root, flags, copy);
+int conf_files_list_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dir) {
+ return conf_files_list_strv_at(ret, suffix, rfd, flags, STRV_MAKE_CONST(dir));
}
-int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir) {
- _cleanup_strv_free_ char **dirs = NULL;
+int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs) {
+ _cleanup_strv_free_ char **d = NULL;
assert(ret);
- dirs = strv_new(dir);
- if (!dirs)
+ d = strv_split_nulstr(dirs);
+ if (!d)
return -ENOMEM;
- return conf_files_list_strv_internal(ret, suffix, root, flags, dirs);
+ return conf_files_list_strv(ret, suffix, root, flags, (const char**) d);
}
-int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs) {
+int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dirs) {
_cleanup_strv_free_ char **d = NULL;
assert(ret);
if (!d)
return -ENOMEM;
- return conf_files_list_strv_internal(ret, suffix, root, flags, d);
+ return conf_files_list_strv_at(ret, suffix, rfd, flags, (const char**) d);
}
int conf_files_list_with_replacement(
};
int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir);
+int conf_files_list_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dir);
int conf_files_list_strv(char ***ret, const char *suffix, const char *root, unsigned flags, const char* const* dirs);
+int conf_files_list_strv_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char * const *dirs);
int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs);
+int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dirs);
int conf_files_insert(char ***strv, const char *root, char **dirs, const char *path);
int conf_files_list_with_replacement(
const char *root,
return r;
}
+int parse_env_file_fdv(int fd, const char *fname, va_list ap) {
+ _cleanup_fclose_ FILE *f = NULL;
+ va_list aq;
+ int r;
+
+ assert(fd >= 0);
+
+ r = fdopen_independent(fd, "re", &f);
+ if (r < 0)
+ return r;
+
+ va_copy(aq, ap);
+ r = parse_env_file_internal(f, fname, parse_env_file_push, &aq);
+ va_end(aq);
+ return r;
+}
+
int parse_env_file_sentinel(
FILE *f,
const char *fname,
const char *fname, /* only used for logging */
...) {
- _cleanup_fclose_ FILE *f = NULL;
va_list ap;
int r;
assert(fd >= 0);
- r = fdopen_independent(fd, "re", &f);
- if (r < 0)
- return r;
-
va_start(ap, fname);
- r = parse_env_filev(f, fname, ap);
+ r = parse_env_file_fdv(fd, fname, ap);
va_end(ap);
return r;
#include "macro.h"
int parse_env_filev(FILE *f, const char *fname, va_list ap);
+int parse_env_file_fdv(int fd, const char *fname, va_list ap);
int parse_env_file_sentinel(FILE *f, const char *fname, ...) _sentinel_;
#define parse_env_file(f, fname, ...) parse_env_file_sentinel(f, fname, __VA_ARGS__, NULL)
int parse_env_file_fd_sentinel(int fd, const char *fname, ...) _sentinel_;
if (r < 0)
return r;
+ r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx);
+ if (r < 0)
+ return r;
+
+ /* First, compare inode. If these are different, the fd does not point to the root directory "/". */
+ if (!statx_inode_same(&st.sx, &pst.sx))
+ return false;
+
if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) {
int mntid;
st.nsx.stx_mask |= STATX_MNT_ID;
}
- r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx);
- if (r < 0)
- return r;
-
if (!FLAGS_SET(pst.nsx.stx_mask, STATX_MNT_ID)) {
int mntid;
pst.nsx.stx_mask |= STATX_MNT_ID;
}
- /* If the parent directory is the same inode, the fd points to the root directory "/". We also check
- * that the mount ids are the same. Otherwise, a construct like the following could be used to trick
- * us:
+ /* Even if the parent directory has the same inode, the fd may not point to the root directory "/",
+ * and we also need to check that the mount ids are the same. Otherwise, a construct like the
+ * following could be used to trick us:
*
* $ mkdir /tmp/x /tmp/x/y
* $ mount --bind /tmp/x /tmp/x/y
*/
- return statx_inode_same(&st.sx, &pst.sx) && statx_mount_same(&st.nsx, &pst.nsx);
+ return statx_mount_same(&st.nsx, &pst.nsx);
}
const char *accmode_to_string(int flags) {
* can detect EOFs. */
#define READ_VIRTUAL_BYTES_MAX (4U*1024U*1024U - 2U)
-int fopen_unlocked_at(int dir_fd, const char *path, const char *options, int flags, FILE **ret) {
- int r;
-
- assert(ret);
-
- r = xfopenat(dir_fd, path, options, flags, ret);
- if (r < 0)
- return r;
-
- (void) __fsetlocking(*ret, FSETLOCKING_BYCALLER);
-
- return 0;
-}
-
int fdopen_unlocked(int fd, const char *options, FILE **ret) {
assert(ret);
const struct timespec *ts) {
_cleanup_fclose_ FILE *f = NULL;
- int q, r, fd;
+ _cleanup_close_ int fd = -EBADF;
+ int q, r;
assert(fn);
assert(line);
goto fail;
}
- r = fdopen_unlocked(fd, "w", &f);
- if (r < 0) {
- safe_close(fd);
+ r = take_fdopen_unlocked(&fd, "w", &f);
+ if (r < 0)
goto fail;
- }
if (flags & WRITE_STRING_FILE_DISABLE_BUFFER)
setvbuf(f, NULL, _IONBF, 0);
return write_string_file(fn, p, flags);
}
-int read_one_line_file(const char *fn, char **line) {
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret) {
_cleanup_fclose_ FILE *f = NULL;
int r;
- assert(fn);
- assert(line);
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(filename);
+ assert(ret);
- r = fopen_unlocked(fn, "re", &f);
+ r = fopen_unlocked_at(dir_fd, filename, "re", 0, &f);
if (r < 0)
return r;
- return read_line(f, LONG_LINE_MAX, line);
+ return read_line(f, LONG_LINE_MAX, ret);
}
int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl) {
size_t *ret_size) {
_cleanup_fclose_ FILE *f = NULL;
+ XfopenFlags xflags = XFOPEN_UNLOCKED;
int r;
assert(filename);
assert(ret_contents);
- r = xfopenat(dir_fd, filename, "re", 0, &f);
- if (r < 0) {
- _cleanup_close_ int sk = -EBADF;
-
- /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
- if (r != -ENXIO)
- return r;
-
- /* If this is enabled, let's try to connect to it */
- if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET))
- return -ENXIO;
+ if (FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET) && /* If this is enabled, let's try to connect to it */
+ offset == UINT64_MAX) /* Seeking is not supported on AF_UNIX sockets */
+ xflags |= XFOPEN_SOCKET;
- /* Seeking is not supported on AF_UNIX sockets */
- if (offset != UINT64_MAX)
- return -ENXIO;
-
- sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
- if (sk < 0)
- return -errno;
-
- if (bind_name) {
- /* If the caller specified a socket name to bind to, do so before connecting. This is
- * useful to communicate some minor, short meta-information token from the client to
- * the server. */
- union sockaddr_union bsa;
-
- r = sockaddr_un_set_path(&bsa.un, bind_name);
- if (r < 0)
- return r;
-
- if (bind(sk, &bsa.sa, r) < 0)
- return -errno;
- }
-
- r = connect_unix_path(sk, dir_fd, filename);
- if (IN_SET(r, -ENOTSOCK, -EINVAL)) /* propagate original error if this is not a socket after all */
- return -ENXIO;
- if (r < 0)
- return r;
-
- if (shutdown(sk, SHUT_WR) < 0)
- return -errno;
-
- f = fdopen(sk, "r");
- if (!f)
- return -errno;
-
- TAKE_FD(sk);
- }
-
- (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ r = xfopenat_full(dir_fd, filename, "re", 0, xflags, bind_name, &f);
+ if (r < 0)
+ return r;
return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size);
}
}
DIR *xopendirat(int fd, const char *name, int flags) {
- int nfd;
- DIR *d;
+ _cleanup_close_ int nfd = -EBADF;
assert(!(flags & O_CREAT));
if (nfd < 0)
return NULL;
- d = fdopendir(nfd);
- if (!d) {
- safe_close(nfd);
- return NULL;
- }
-
- return d;
+ return take_fdopendir(&nfd);
}
int fopen_mode_to_flags(const char *mode) {
return flags;
}
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) {
+static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
FILE *f;
/* A combination of fopen() with openat() */
- if (dir_fd == AT_FDCWD && flags == 0) {
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(mode);
+ assert(ret);
+
+ if (dir_fd == AT_FDCWD && open_flags == 0)
f = fopen(path, mode);
- if (!f)
- return -errno;
- } else {
- int fd, mode_flags;
+ else {
+ _cleanup_close_ int fd = -EBADF;
+ int mode_flags;
mode_flags = fopen_mode_to_flags(mode);
if (mode_flags < 0)
return mode_flags;
- fd = openat(dir_fd, path, mode_flags | flags);
+ fd = openat(dir_fd, path, mode_flags | open_flags);
if (fd < 0)
return -errno;
- f = fdopen(fd, mode);
- if (!f) {
- safe_close(fd);
+ f = take_fdopen(&fd, mode);
+ }
+ if (!f)
+ return -errno;
+
+ *ret = f;
+ return 0;
+}
+
+static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_name, FILE **ret) {
+ _cleanup_close_ int sk = -EBADF;
+ FILE *f;
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(ret);
+
+ sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+ if (sk < 0)
+ return -errno;
+
+ if (bind_name) {
+ /* If the caller specified a socket name to bind to, do so before connecting. This is
+ * useful to communicate some minor, short meta-information token from the client to
+ * the server. */
+ union sockaddr_union bsa;
+
+ r = sockaddr_un_set_path(&bsa.un, bind_name);
+ if (r < 0)
+ return r;
+
+ if (bind(sk, &bsa.sa, r) < 0)
return -errno;
- }
}
+ r = connect_unix_path(sk, dir_fd, path);
+ if (r < 0)
+ return r;
+
+ if (shutdown(sk, SHUT_WR) < 0)
+ return -errno;
+
+ f = take_fdopen(&sk, "r");
+ if (!f)
+ return -errno;
+
+ *ret = f;
+ return 0;
+}
+
+int xfopenat_full(
+ int dir_fd,
+ const char *path,
+ const char *mode,
+ int open_flags,
+ XfopenFlags flags,
+ const char *bind_name,
+ FILE **ret) {
+
+ FILE *f = NULL; /* avoid false maybe-uninitialized warning */
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(mode);
+ assert(ret);
+
+ r = xfopenat_regular(dir_fd, path, mode, open_flags, &f);
+ if (r == -ENXIO && FLAGS_SET(flags, XFOPEN_SOCKET)) {
+ /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
+ r = xfopenat_unix_socket(dir_fd, path, bind_name, &f);
+ if (IN_SET(r, -ENOTSOCK, -EINVAL))
+ return -ENXIO; /* propagate original error if this is not a socket after all */
+ }
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(flags, XFOPEN_UNLOCKED))
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
*ret = f;
return 0;
}
if (copy_fd < 0)
return copy_fd;
- f = fdopen(copy_fd, mode);
+ f = take_fdopen(©_fd, mode);
if (!f)
return -errno;
- TAKE_FD(copy_fd);
*ret = TAKE_PTR(f);
return 0;
}
READ_FULL_FILE_FAIL_WHEN_LARGER = 1 << 5, /* fail loading if file is larger than specified size */
} ReadFullFileFlags;
-int fopen_unlocked_at(int dir_fd, const char *path, const char *options, int flags, FILE **ret);
-static inline int fopen_unlocked(const char *path, const char *options, FILE **ret) {
- return fopen_unlocked_at(AT_FDCWD, path, options, 0, ret);
-}
int fdopen_unlocked(int fd, const char *options, FILE **ret);
int take_fdopen_unlocked(int *fd, const char *options, FILE **ret);
FILE* take_fdopen(int *fd, const char *options);
int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4);
-int read_one_line_file(const char *filename, char **line);
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret);
+static inline int read_one_line_file(const char *filename, char **ret) {
+ return read_one_line_file_at(AT_FDCWD, filename, ret);
+}
int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size);
static inline int read_full_file_at(int dir_fd, const char *filename, char **ret_contents, size_t *ret_size) {
return read_full_file_full(dir_fd, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size);
int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field);
DIR *xopendirat(int dirfd, const char *name, int flags);
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret);
+
+typedef enum XfopenFlags {
+ XFOPEN_UNLOCKED = 1 << 0, /* call __fsetlocking(FSETLOCKING_BYCALLER) after opened */
+ XFOPEN_SOCKET = 1 << 1, /* also try to open unix socket */
+} XfopenFlags;
+
+int xfopenat_full(
+ int dir_fd,
+ const char *path,
+ const char *mode,
+ int open_flags,
+ XfopenFlags flags,
+ const char *bind_name,
+ FILE **ret);
+static inline int xfopenat(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+ return xfopenat_full(dir_fd, path, mode, open_flags, 0, NULL, ret);
+}
+static inline int fopen_unlocked_at(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+ return xfopenat_full(dir_fd, path, mode, open_flags, XFOPEN_UNLOCKED, NULL, ret);
+}
+static inline int fopen_unlocked(const char *path, const char *mode, FILE **ret) {
+ return fopen_unlocked_at(AT_FDCWD, path, mode, 0, ret);
+}
int fdopen_independent(int fd, const char *mode, FILE **ret);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <getopt.h>
+
+#define SYSTEMD_GETOPT_SHORT_OPTIONS "hDbsz:"
+
+#define COMMON_GETOPT_ARGS \
+ ARG_LOG_LEVEL = 0x100, \
+ ARG_LOG_TARGET, \
+ ARG_LOG_COLOR, \
+ ARG_LOG_LOCATION, \
+ ARG_LOG_TIME
+
+#define SYSTEMD_GETOPT_ARGS \
+ ARG_UNIT, \
+ ARG_SYSTEM, \
+ ARG_USER, \
+ ARG_TEST, \
+ ARG_NO_PAGER, \
+ ARG_VERSION, \
+ ARG_DUMP_CONFIGURATION_ITEMS, \
+ ARG_DUMP_BUS_PROPERTIES, \
+ ARG_BUS_INTROSPECT, \
+ ARG_DUMP_CORE, \
+ ARG_CRASH_CHVT, \
+ ARG_CRASH_SHELL, \
+ ARG_CRASH_REBOOT, \
+ ARG_CONFIRM_SPAWN, \
+ ARG_SHOW_STATUS, \
+ ARG_DESERIALIZE, \
+ ARG_SWITCHED_ROOT, \
+ ARG_DEFAULT_STD_OUTPUT, \
+ ARG_DEFAULT_STD_ERROR, \
+ ARG_MACHINE_ID, \
+ ARG_SERVICE_WATCHDOGS
+
+#define SHUTDOWN_GETOPT_ARGS \
+ ARG_EXIT_CODE, \
+ ARG_TIMEOUT
+
+#define COMMON_GETOPT_OPTIONS \
+ { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, \
+ { "log-target", required_argument, NULL, ARG_LOG_TARGET }, \
+ { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, \
+ { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, \
+ { "log-time", optional_argument, NULL, ARG_LOG_TIME }
+
+#define SYSTEMD_GETOPT_OPTIONS \
+ { "unit", required_argument, NULL, ARG_UNIT }, \
+ { "system", no_argument, NULL, ARG_SYSTEM }, \
+ { "user", no_argument, NULL, ARG_USER }, \
+ { "test", no_argument, NULL, ARG_TEST }, \
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER }, \
+ { "help", no_argument, NULL, 'h' }, \
+ { "version", no_argument, NULL, ARG_VERSION }, \
+ { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, \
+ { "dump-bus-properties", no_argument, NULL, ARG_DUMP_BUS_PROPERTIES }, \
+ { "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT }, \
+ { "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, \
+ { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, \
+ { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, \
+ { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, \
+ { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, \
+ { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, \
+ { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, \
+ { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, \
+ { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, \
+ { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, \
+ { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, \
+ { "service-watchdogs", required_argument, NULL, ARG_SERVICE_WATCHDOGS }
+
+#define SHUTDOWN_GETOPT_OPTIONS \
+ { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, \
+ { "timeout", required_argument, NULL, ARG_TIMEOUT }
#include "parse-util.h"
#include "path-util.h"
#include "stat-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "utf8.h"
#include "xattr-util.h"
+static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
+ [IMAGE_MACHINE] = "machine",
+ [IMAGE_PORTABLE] = "portable",
+ [IMAGE_SYSEXT] = "extension",
+ [IMAGE_CONFEXT] = "confext",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
+
+/* Helper struct for naming simplicity and reusability */
+static const struct {
+ const char *release_file_directory;
+ const char *release_file_path_prefix;
+} image_class_release_info[_IMAGE_CLASS_MAX] = {
+ [IMAGE_SYSEXT] = {
+ .release_file_directory = "/usr/lib/extension-release.d/",
+ .release_file_path_prefix = "/usr/lib/extension-release.d/extension-release.",
+ },
+ [IMAGE_CONFEXT] = {
+ .release_file_directory = "/etc/extension-release.d/",
+ .release_file_path_prefix = "/etc/extension-release.d/extension-release.",
+ }
+};
+
bool image_name_is_valid(const char *s) {
if (!filename_is_valid(s))
return false;
return true;
}
-int path_is_extension_tree(const char *path, const char *extension, bool relax_extension_release_check) {
+int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check) {
int r;
assert(path);
return -errno;
/* We use /usr/lib/extension-release.d/extension-release[.NAME] as flag for something being a system extension,
+ * /etc/extension-release.d/extension-release[.NAME] as flag for something being a system configuration, and finally,
* and {/etc|/usr/lib}/os-release as a flag for something being an OS (when not an extension). */
- r = open_extension_release(path, extension, relax_extension_release_check, NULL, NULL);
+ r = open_extension_release(path, image_class, extension, relax_extension_release_check, NULL, NULL);
if (r == -ENOENT) /* We got nothing */
return 0;
if (r < 0)
return false;
}
-int open_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd) {
- _cleanup_free_ char *q = NULL;
- int r, fd;
-
- if (extension) {
- const char *extension_full_path;
-
- if (!image_name_is_valid(extension))
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
- "The extension name %s is invalid.", extension);
-
- extension_full_path = strjoina("/usr/lib/extension-release.d/extension-release.", extension);
- r = chase(extension_full_path, root, CHASE_PREFIX_ROOT, ret_path ? &q : NULL, ret_fd ? &fd : NULL);
- log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", extension_full_path);
-
- /* Cannot find the expected extension-release file? The image filename might have been
- * mangled on deployment, so fallback to checking for any file in the extension-release.d
- * directory, and return the first one with a user.extension-release xattr instead.
- * The user.extension-release.strict xattr is checked to ensure the author of the image
- * considers it OK if names do not match. */
- if (r == -ENOENT) {
- _cleanup_free_ char *extension_release_dir_path = NULL;
- _cleanup_closedir_ DIR *extension_release_dir = NULL;
-
- r = chase_and_opendir("/usr/lib/extension-release.d/", root, CHASE_PREFIX_ROOT,
- &extension_release_dir_path, &extension_release_dir);
- if (r < 0)
- return log_debug_errno(r, "Cannot open %s/usr/lib/extension-release.d/, ignoring: %m", root);
-
- r = -ENOENT;
- FOREACH_DIRENT(de, extension_release_dir, return -errno) {
- int k;
-
- if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
- continue;
-
- const char *image_name = startswith(de->d_name, "extension-release.");
- if (!image_name)
- continue;
-
- if (!image_name_is_valid(image_name)) {
- log_debug("%s/%s is not a valid extension-release file name, ignoring.",
- extension_release_dir_path, de->d_name);
- continue;
- }
-
- /* We already chased the directory, and checked that
- * this is a real file, so we shouldn't fail to open it. */
- _cleanup_close_ int extension_release_fd = openat(dirfd(extension_release_dir),
- de->d_name,
- O_PATH|O_CLOEXEC|O_NOFOLLOW);
- if (extension_release_fd < 0)
- return log_debug_errno(errno,
- "Failed to open extension-release file %s/%s: %m",
- extension_release_dir_path,
- de->d_name);
-
- /* Really ensure it is a regular file after we open it. */
- if (fd_verify_regular(extension_release_fd) < 0) {
- log_debug("%s/%s is not a regular file, ignoring.", extension_release_dir_path, de->d_name);
- continue;
- }
-
- if (!relax_extension_release_check) {
- k = extension_release_strict_xattr_value(extension_release_fd,
- extension_release_dir_path,
- de->d_name);
- if (k != 0)
- continue;
- }
-
- /* We already found what we were looking for, but there's another candidate?
- * We treat this as an error, as we want to enforce that there are no ambiguities
- * in case we are in the fallback path. */
- if (r == 0) {
- r = -ENOTUNIQ;
- break;
- }
-
- r = 0; /* Found it! */
-
- if (ret_fd)
- fd = TAKE_FD(extension_release_fd);
-
- if (ret_path) {
- q = path_join(extension_release_dir_path, de->d_name);
- if (!q)
- return -ENOMEM;
- }
- }
- }
- } else {
- const char *var = secure_getenv("SYSTEMD_OS_RELEASE");
- if (var)
- r = chase(var, root, 0, ret_path ? &q : NULL, ret_fd ? &fd : NULL);
- else
- FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
- r = chase(path, root, CHASE_PREFIX_ROOT, ret_path ? &q : NULL, ret_fd ? &fd : NULL);
- if (r != -ENOENT)
- break;
- }
+int open_os_release_at(int rfd, char **ret_path, int *ret_fd) {
+ const char *e;
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
+ e = secure_getenv("SYSTEMD_OS_RELEASE");
+ if (e)
+ return chaseat(rfd, e, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+
+ FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
+ r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+ if (r != -ENOENT)
+ return r;
}
+
+ return -ENOENT;
+}
+
+int open_os_release(const char *root, char **ret_path, int *ret_fd) {
+ _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return -errno;
+
+ r = open_os_release_at(rfd, ret_path ? &p : NULL, ret_fd ? &fd : NULL);
if (r < 0)
return r;
- if (ret_fd) {
- int real_fd;
+ if (ret_path) {
+ r = path_prefix_root_cwd(p, root, ret_path);
+ if (r < 0)
+ return r;
+ }
+
+ if (ret_fd)
+ *ret_fd = TAKE_FD(fd);
+
+ return 0;
+}
+
+int open_extension_release_at(
+ int rfd,
+ ImageClass image_class,
+ const char *extension,
+ bool relax_extension_release_check,
+ char **ret_path,
+ int *ret_fd) {
+
+ _cleanup_free_ char *dir_path = NULL, *path_found = NULL;
+ _cleanup_close_ int fd_found = -EBADF;
+ _cleanup_closedir_ DIR *dir = NULL;
+ bool found = false;
+ const char *p;
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(!extension || (image_class >= 0 && image_class < _IMAGE_CLASS_MAX));
+
+ if (!extension)
+ return open_os_release_at(rfd, ret_path, ret_fd);
+
+ if (!IN_SET(image_class, IMAGE_SYSEXT, IMAGE_CONFEXT))
+ return -EINVAL;
+
+ if (!image_name_is_valid(extension))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The extension name %s is invalid.", extension);
+
+ p = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension);
+ r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+ log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p);
+ if (r != -ENOENT)
+ return r;
+
+ /* Cannot find the expected extension-release file? The image filename might have been mangled on
+ * deployment, so fallback to checking for any file in the extension-release.d directory, and return
+ * the first one with a user.extension-release xattr instead. The user.extension-release.strict
+ * xattr is checked to ensure the author of the image considers it OK if names do not match. */
+
+ p = image_class_release_info[image_class].release_file_directory;
+ r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir);
+ if (r < 0)
+ return log_debug_errno(r, "Cannot open %s, ignoring: %m", p);
+
+ FOREACH_DIRENT(de, dir, return -errno) {
+ _cleanup_close_ int fd = -EBADF;
+ const char *image_name;
- /* Convert the O_PATH fd into a proper, readable one */
- real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY);
- safe_close(fd);
- if (real_fd < 0)
- return real_fd;
+ if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
+ continue;
+
+ image_name = startswith(de->d_name, "extension-release.");
+ if (!image_name)
+ continue;
- *ret_fd = real_fd;
+ if (!image_name_is_valid(image_name)) {
+ log_debug("%s/%s is not a valid release file name, ignoring.", dir_path, de->d_name);
+ continue;
+ }
+
+ /* We already chased the directory, and checked that this is a real file, so we shouldn't
+ * fail to open it. */
+ fd = openat(dirfd(dir), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
+ if (fd < 0)
+ return log_debug_errno(errno, "Failed to open release file %s/%s: %m", dir_path, de->d_name);
+
+ /* Really ensure it is a regular file after we open it. */
+ r = fd_verify_regular(fd);
+ if (r < 0) {
+ log_debug_errno(r, "%s/%s is not a regular file, ignoring: %m", dir_path, de->d_name);
+ continue;
+ }
+
+ if (!relax_extension_release_check &&
+ extension_release_strict_xattr_value(fd, dir_path, de->d_name) != 0)
+ continue;
+
+ /* We already found what we were looking for, but there's another candidate? We treat this as
+ * an error, as we want to enforce that there are no ambiguities in case we are in the
+ * fallback path. */
+ if (found)
+ return -ENOTUNIQ;
+
+ found = true;
+
+ if (ret_fd)
+ fd_found = TAKE_FD(fd);
+
+ if (ret_path) {
+ path_found = path_join(dir_path, de->d_name);
+ if (!path_found)
+ return -ENOMEM;
+ }
}
+ if (!found)
+ return -ENOENT;
+ if (ret_fd)
+ *ret_fd = TAKE_FD(fd_found);
if (ret_path)
- *ret_path = TAKE_PTR(q);
+ *ret_path = TAKE_PTR(path_found);
return 0;
}
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file) {
+int open_extension_release(
+ const char *root,
+ ImageClass image_class,
+ const char *extension,
+ bool relax_extension_release_check,
+ char **ret_path,
+ int *ret_fd) {
+
+ _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
_cleanup_free_ char *p = NULL;
- _cleanup_close_ int fd = -EBADF;
- FILE *f;
int r;
- if (!ret_file)
- return open_extension_release(root, extension, relax_extension_release_check, ret_path, NULL);
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return -errno;
- r = open_extension_release(root, extension, relax_extension_release_check, ret_path ? &p : NULL, &fd);
+ r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check,
+ ret_path ? &p : NULL, ret_fd ? &fd : NULL);
if (r < 0)
return r;
- f = take_fdopen(&fd, "r");
- if (!f)
- return -errno;
+ if (ret_path) {
+ r = path_prefix_root_cwd(p, root, ret_path);
+ if (r < 0)
+ return r;
+ }
- if (ret_path)
- *ret_path = TAKE_PTR(p);
- *ret_file = f;
+ if (ret_fd)
+ *ret_fd = TAKE_FD(fd);
return 0;
}
-static int parse_release_internal(const char *root, bool relax_extension_release_check, const char *extension, va_list ap) {
- _cleanup_fclose_ FILE *f = NULL;
+static int parse_extension_release_atv(
+ int rfd,
+ ImageClass image_class,
+ const char *extension,
+ bool relax_extension_release_check,
+ va_list ap) {
+
+ _cleanup_close_ int fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
- r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
+ r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check, &p, &fd);
if (r < 0)
return r;
- return parse_env_filev(f, p, ap);
+ return parse_env_file_fdv(fd, p, ap);
}
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) {
+int parse_extension_release_at_sentinel(
+ int rfd,
+ ImageClass image_class,
+ bool relax_extension_release_check,
+ const char *extension,
+ ...) {
+
va_list ap;
int r;
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
va_start(ap, extension);
- r = parse_release_internal(root, relax_extension_release_check, extension, ap);
+ r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
va_end(ap);
-
return r;
}
-int _parse_os_release(const char *root, ...) {
+int parse_extension_release_sentinel(
+ const char *root,
+ ImageClass image_class,
+ bool relax_extension_release_check,
+ const char *extension,
+ ...) {
+
+ _cleanup_close_ int rfd = -EBADF;
va_list ap;
int r;
- va_start(ap, root);
- r = parse_release_internal(root, /* relax_extension_release_check= */ false, NULL, ap);
- va_end(ap);
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return -errno;
+ va_start(ap, extension);
+ r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
+ va_end(ap);
return r;
}
-int load_os_release_pairs(const char *root, char ***ret) {
- _cleanup_fclose_ FILE *f = NULL;
- _cleanup_free_ char *p = NULL;
- int r;
-
- r = fopen_os_release(root, &p, &f);
- if (r < 0)
- return r;
-
- return load_env_file_pairs(f, p, ret);
-}
-
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret) {
_cleanup_strv_free_ char **os_release_pairs = NULL, **os_release_pairs_prefixed = NULL;
int r;
return 0;
}
-int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret) {
- _cleanup_fclose_ FILE *f = NULL;
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret) {
+ _cleanup_close_ int fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
- r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+ r = open_extension_release(root, image_class, extension, relax_extension_release_check, &p, &fd);
if (r < 0)
return r;
- return load_env_file_pairs(f, p, ret);
+ return load_env_file_pairs_fd(fd, p, ret);
}
int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol) {
#include "time-util.h"
+typedef enum ImageClass {
+ IMAGE_MACHINE,
+ IMAGE_PORTABLE,
+ IMAGE_SYSEXT,
+ IMAGE_CONFEXT,
+ _IMAGE_CLASS_MAX,
+ _IMAGE_CLASS_INVALID = -EINVAL,
+} ImageClass;
+
+const char* image_class_to_string(ImageClass cl) _const_;
+ImageClass image_class_from_string(const char *s) _pure_;
+
/* The *_extension_release flavours will look for /usr/lib/extension-release/extension-release.NAME
* in accordance with the OS extension specification, rather than for /usr/lib/ or /etc/os-release. */
bool image_name_is_valid(const char *s) _pure_;
-int path_is_extension_tree(const char *path, const char *extension, bool relax_extension_release_check);
+int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check);
static inline int path_is_os_tree(const char *path) {
- return path_is_extension_tree(path, NULL, false);
-}
-
-int open_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
-static inline int open_os_release(const char *root, char **ret_path, int *ret_fd) {
- return open_extension_release(root, NULL, false, ret_path, ret_fd);
+ return path_is_extension_tree(_IMAGE_CLASS_INVALID, path, NULL, false);
}
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file);
-static inline int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
- return fopen_extension_release(root, NULL, false, ret_path, ret_file);
+int open_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
+int open_extension_release_at(int rfd, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
+int open_os_release(const char *root, char **ret_path, int *ret_fd);
+int open_os_release_at(int rfd, char **ret_path, int *ret_fd);
+
+int parse_extension_release_sentinel(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
+#define parse_extension_release(root, image_class, extension, relax_extension_release_check, ...) \
+ parse_extension_release_sentinel(root, image_class, relax_extension_release_check, extension, __VA_ARGS__, NULL)
+#define parse_os_release(root, ...) \
+ parse_extension_release_sentinel(root, _IMAGE_CLASS_INVALID, false, NULL, __VA_ARGS__, NULL)
+
+int parse_extension_release_at_sentinel(int rfd, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
+#define parse_extension_release_at(rfd, image_class, extension, relax_extension_release_check, ...) \
+ parse_extension_release_at_sentinel(rfd, image_class, relax_extension_release_check, extension, __VA_ARGS__, NULL)
+#define parse_os_release_at(rfd, ...) \
+ parse_extension_release_at_sentinel(rfd, _IMAGE_CLASS_INVALID, false, NULL, __VA_ARGS__, NULL)
+
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret);
+static inline int load_os_release_pairs(const char *root, char ***ret) {
+ return load_extension_release_pairs(root, _IMAGE_CLASS_INVALID, NULL, false, ret);
}
-
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
-int _parse_os_release(const char *root, ...) _sentinel_;
-#define parse_extension_release(root, relax_extension_release_check, extension, ...) _parse_extension_release(root, relax_extension_release_check, extension, __VA_ARGS__, NULL)
-#define parse_os_release(root, ...) _parse_os_release(root, __VA_ARGS__, NULL)
-
-int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret);
-int load_os_release_pairs(const char *root, char ***ret);
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret);
int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol);
return 0;
}
+int path_prefix_root_cwd(const char *p, const char *root, char **ret) {
+ _cleanup_free_ char *root_abs = NULL;
+ char *c;
+ int r;
+
+ assert(p);
+ assert(ret);
+
+ /* Unlike path_make_absolute(), this always prefixes root path if specified.
+ * The root path is always simplified, but the provided path will not.
+ * This is useful for prefixing the result of chaseat(). */
+
+ if (empty_or_root(root))
+ return path_make_absolute_cwd(p, ret);
+
+ r = path_make_absolute_cwd(root, &root_abs);
+ if (r < 0)
+ return r;
+
+ path_simplify(root_abs);
+ c = path_join(root_abs, p);
+ if (!c)
+ return -ENOMEM;
+
+ *ret = c;
+ return 0;
+}
+
int path_make_relative(const char *from, const char *to, char **ret) {
_cleanup_free_ char *result = NULL;
unsigned n_parents;
return path_equal(a, b) || files_same(a, b, flags) > 0;
}
-bool path_equal_filename(const char *a, const char *b) {
- _cleanup_free_ char *a_basename = NULL, *b_basename = NULL;
- int r;
+int path_compare_filename(const char *a, const char *b) {
+ _cleanup_free_ char *fa = NULL, *fb = NULL;
+ int r, j, k;
- assert(a);
- assert(b);
+ /* Order NULL before non-NULL */
+ r = CMP(!!a, !!b);
+ if (r != 0)
+ return r;
- r = path_extract_filename(a, &a_basename);
- if (r < 0) {
- log_debug_errno(r, "Failed to parse basename of %s: %m", a);
- return false;
- }
- r = path_extract_filename(b, &b_basename);
- if (r < 0) {
- log_debug_errno(r, "Failed to parse basename of %s: %m", b);
- return false;
- }
+ j = path_extract_filename(a, &fa);
+ k = path_extract_filename(b, &fb);
- return path_equal(a_basename, b_basename);
+ /* When one of paths is "." or root, then order it earlier. */
+ r = CMP(j != -EADDRNOTAVAIL, k != -EADDRNOTAVAIL);
+ if (r != 0)
+ return r;
+
+ /* When one of paths is invalid (or we get OOM), order invalid path after valid one. */
+ r = CMP(j < 0, k < 0);
+ if (r != 0)
+ return r;
+
+ /* fallback to use strcmp() if both paths are invalid. */
+ if (j < 0)
+ return strcmp(a, b);
+
+ return strcmp(fa, fb);
}
char* path_extend_internal(char **x, ...) {
continue;
if (q > path && strneq(q - 1, "/.", 2))
continue;
+ if (q == path && *q == '.')
+ continue;
break;
}
return q;
* ret: "bbbbb/cc//././"
* return value: 5 (== strlen("bbbbb"))
*
+ * Input: path: "//.//aaa///bbbbb/cc//././"
+ * next: "///bbbbb/cc//././"
+ * Output: next: "//.//aaa///bbbbb/cc//././" (next == path)
+ * ret: "aaa///bbbbb/cc//././"
+ * return value: 3 (== strlen("aaa"))
+ *
* Input: path: "/", ".", "", or NULL
* Output: next: equivalent to path
* ret: NULL
char* path_make_absolute(const char *p, const char *prefix);
int safe_getcwd(char **ret);
int path_make_absolute_cwd(const char *p, char **ret);
+int path_prefix_root_cwd(const char *p, const char *root, char **ret);
int path_make_relative(const char *from, const char *to, char **ret);
int path_make_relative_parent(const char *from_child, const char *to, char **ret);
char *path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_;
static inline char* path_startswith(const char *path, const char *prefix) {
return path_startswith_full(path, prefix, true);
}
-int path_compare(const char *a, const char *b) _pure_;
+int path_compare(const char *a, const char *b) _pure_;
static inline bool path_equal(const char *a, const char *b) {
return path_compare(a, b) == 0;
}
+int path_compare_filename(const char *a, const char *b);
+static inline bool path_equal_filename(const char *a, const char *b) {
+ return path_compare_filename(a, b) == 0;
+}
+
bool path_equal_or_files_same(const char *a, const char *b, int flags);
-/* Compares only the last portion of the input paths, ie: the filenames */
-bool path_equal_filename(const char *a, const char *b);
char* path_extend_internal(char **x, ...);
#define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX)
#include "efivars.h"
#include "extract-word.h"
#include "fileio.h"
+#include "getopt-defs.h"
#include "initrd-util.h"
#include "macro.h"
#include "parse-util.h"
#include "proc-cmdline.h"
#include "process-util.h"
-#include "special.h"
#include "string-util.h"
+#include "strv.h"
#include "virt.h"
+int proc_cmdline_filter_pid1_args(
+ char **argv, /* input, may be reordered by this function. */
+ char ***ret) {
+
+ enum {
+ COMMON_GETOPT_ARGS,
+ SYSTEMD_GETOPT_ARGS,
+ SHUTDOWN_GETOPT_ARGS,
+ };
+
+ static const struct option options[] = {
+ COMMON_GETOPT_OPTIONS,
+ SYSTEMD_GETOPT_OPTIONS,
+ SHUTDOWN_GETOPT_OPTIONS,
+ {}
+ };
+
+ int saved_optind, saved_opterr, saved_optopt, argc;
+ char *saved_optarg;
+ char **filtered;
+ size_t idx;
+
+ assert(argv);
+ assert(ret);
+
+ /* Backup global variables. */
+ saved_optind = optind;
+ saved_opterr = opterr;
+ saved_optopt = optopt;
+ saved_optarg = optarg;
+
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). Here, we do not use
+ * the GNU extensions, but might be used previously. Hence, we need to always reset it. */
+ optind = 0;
+
+ /* Do not print an error message. */
+ opterr = 0;
+
+ /* Filter out all known options. */
+ argc = strv_length(argv);
+ while (getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL) >= 0)
+ ;
+
+ idx = optind;
+
+ /* Restore global variables. */
+ optind = saved_optind;
+ opterr = saved_opterr;
+ optopt = saved_optopt;
+ optarg = saved_optarg;
+
+ filtered = strv_copy(strv_skip(argv, idx));
+ if (!filtered)
+ return -ENOMEM;
+
+ *ret = filtered;
+ return 0;
+}
+
int proc_cmdline(char **ret) {
const char *e;
return read_one_line_file("/proc/cmdline", ret);
}
-static int proc_cmdline_extract_first(const char **p, char **ret_word, ProcCmdlineFlags flags) {
- const char *q = *p;
+static int proc_cmdline_strv_internal(char ***ret, bool filter_pid1_args) {
+ const char *e;
int r;
- for (;;) {
- _cleanup_free_ char *word = NULL;
- const char *c;
+ assert(ret);
- r = extract_first_word(&q, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+ /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
+ e = secure_getenv("SYSTEMD_PROC_CMDLINE");
+ if (e)
+ return strv_split_full(ret, e, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+
+ if (detect_container() > 0) {
+ _cleanup_strv_free_ char **args = NULL;
+
+ r = get_process_cmdline_strv(1, /* flags = */ 0, &args);
if (r < 0)
return r;
- if (r == 0)
- break;
- /* Filter out arguments that are intended only for the initrd */
- c = startswith(word, "rd.");
- if (c) {
- if (!in_initrd())
- continue;
+ if (filter_pid1_args)
+ return proc_cmdline_filter_pid1_args(args, ret);
- if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX)) {
- r = free_and_strdup(&word, c);
- if (r < 0)
- return r;
- }
+ *ret = TAKE_PTR(args);
+ return 0;
- } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
- continue; /* And optionally filter out arguments that are intended only for the host */
+ } else {
+ _cleanup_free_ char *s = NULL;
+
+ r = read_one_line_file("/proc/cmdline", &s);
+ if (r < 0)
+ return r;
- *p = q;
- *ret_word = TAKE_PTR(word);
- return 1;
+ return strv_split_full(ret, s, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
}
+}
- *p = q;
- *ret_word = NULL;
- return 0;
+int proc_cmdline_strv(char ***ret) {
+ return proc_cmdline_strv_internal(ret, /* filter_pid1_args = */ false);
+}
+
+static char *mangle_word(const char *word, ProcCmdlineFlags flags) {
+ char *c;
+
+ c = startswith(word, "rd.");
+ if (c) {
+ /* Filter out arguments that are intended only for the initrd */
+
+ if (!in_initrd())
+ return NULL;
+
+ if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX))
+ return c;
+
+ } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
+ /* And optionally filter out arguments that are intended only for the host */
+ return NULL;
+
+ return (char*) word;
}
-static int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
- const char *p;
+static int proc_cmdline_parse_strv(char **args, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
int r;
assert(parse_item);
- /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's make this
- * clear. */
+ /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's
+ * make this clear. */
assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
- p = line;
- for (;;) {
- _cleanup_free_ char *word = NULL;
- char *value;
+ STRV_FOREACH(word, args) {
+ char *key, *value;
- r = proc_cmdline_extract_first(&p, &word, flags);
- if (r < 0)
- return r;
- if (r == 0)
- break;
+ key = mangle_word(*word, flags);
+ if (!key)
+ continue;
- value = strchr(word, '=');
+ value = strchr(key, '=');
if (value)
- *(value++) = 0;
+ *(value++) = '\0';
- r = parse_item(word, value, data);
+ r = parse_item(key, value, data);
if (r < 0)
return r;
}
}
int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
- _cleanup_free_ char *line = NULL;
+ _cleanup_strv_free_ char **args = NULL;
int r;
assert(parse_item);
/* We parse the EFI variable first, because later settings have higher priority. */
if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
+ _cleanup_free_ char *line = NULL;
+
r = systemd_efi_options_variable(&line);
if (r < 0) {
if (r != -ENODATA)
log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
} else {
- r = proc_cmdline_parse_given(line, parse_item, data, flags);
+ r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
return r;
- line = mfree(line);
+ r = proc_cmdline_parse_strv(args, parse_item, data, flags);
+ if (r < 0)
+ return r;
+
+ args = strv_free(args);
}
}
- r = proc_cmdline(&line);
+ r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
if (r < 0)
return r;
- return proc_cmdline_parse_given(line, parse_item, data, flags);
+ return proc_cmdline_parse_strv(args, parse_item, data, flags);
}
static bool relaxed_equal_char(char a, char b) {
return true;
}
-static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags flags, char **ret_value) {
+static int cmdline_get_key(char **args, const char *key, ProcCmdlineFlags flags, char **ret_value) {
_cleanup_free_ char *v = NULL;
bool found = false;
- const char *p;
int r;
- assert(line);
assert(key);
- p = line;
- for (;;) {
- _cleanup_free_ char *word = NULL;
+ STRV_FOREACH(p, args) {
+ const char *word;
- r = proc_cmdline_extract_first(&p, &word, flags);
- if (r < 0)
- return r;
- if (r == 0)
- break;
+ word = mangle_word(*p, flags);
+ if (!word)
+ continue;
if (ret_value) {
const char *e;
}
int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) {
+ _cleanup_strv_free_ char **args = NULL;
_cleanup_free_ char *line = NULL, *v = NULL;
int r;
if (FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL) && !ret_value)
return -EINVAL;
- r = proc_cmdline(&line);
+ r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
if (r < 0)
return r;
if (FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) /* Shortcut */
- return cmdline_get_key(line, key, flags, ret_value);
+ return cmdline_get_key(args, key, flags, ret_value);
- r = cmdline_get_key(line, key, flags, ret_value ? &v : NULL);
+ r = cmdline_get_key(args, key, flags, ret_value ? &v : NULL);
if (r < 0)
return r;
if (r > 0) {
return r;
}
- line = mfree(line);
r = systemd_efi_options_variable(&line);
if (r == -ENODATA) {
if (ret_value)
if (r < 0)
return r;
- return cmdline_get_key(line, key, flags, ret_value);
+ args = strv_free(args);
+ r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+ if (r < 0)
+ return r;
+
+ return cmdline_get_key(args, key, flags, ret_value);
}
int proc_cmdline_get_bool(const char *key, bool *ret) {
return 1;
}
-int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
- _cleanup_free_ char *line = NULL;
- bool processing_efi = true;
- const char *p;
- va_list ap;
+static int cmdline_get_key_ap(ProcCmdlineFlags flags, char* const* args, va_list ap) {
int r, ret = 0;
- /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
- * this clear. */
- assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
+ for (;;) {
+ char **v;
+ const char *k, *e;
- /* This call may clobber arguments on failure! */
+ k = va_arg(ap, const char*);
+ if (!k)
+ break;
- if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
- r = systemd_efi_options_variable(&line);
- if (r < 0 && r != -ENODATA)
- log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
- }
+ assert_se(v = va_arg(ap, char**));
- p = line;
- for (;;) {
- _cleanup_free_ char *word = NULL;
+ STRV_FOREACH(p, args) {
+ const char *word;
- r = proc_cmdline_extract_first(&p, &word, flags);
- if (r < 0)
- return r;
- if (r == 0) {
- /* We finished with this command line. If this was the EFI one, then let's proceed with the regular one */
- if (processing_efi) {
- processing_efi = false;
+ word = mangle_word(*p, flags);
+ if (!word)
+ continue;
- line = mfree(line);
- r = proc_cmdline(&line);
+ e = proc_cmdline_key_startswith(word, k);
+ if (e && *e == '=') {
+ r = free_and_strdup(v, e + 1);
if (r < 0)
return r;
- p = line;
- continue;
+ ret++;
}
-
- break;
}
+ }
+
+ return ret;
+}
+
+int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
+ _cleanup_strv_free_ char **args = NULL;
+ int r, ret = 0;
+ va_list ap;
- va_start(ap, flags);
+ /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
+ * this clear. */
+ assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
- for (;;) {
- char **v;
- const char *k, *e;
+ /* This call may clobber arguments on failure! */
- k = va_arg(ap, const char*);
- if (!k)
- break;
+ if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
+ _cleanup_free_ char *line = NULL;
- assert_se(v = va_arg(ap, char**));
+ r = systemd_efi_options_variable(&line);
+ if (r < 0 && r != -ENODATA)
+ log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
+ if (r >= 0) {
+ r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+ if (r < 0)
+ return r;
- e = proc_cmdline_key_startswith(word, k);
- if (e && *e == '=') {
- r = free_and_strdup(v, e + 1);
- if (r < 0) {
- va_end(ap);
- return r;
- }
+ va_start(ap, flags);
+ r = cmdline_get_key_ap(flags, args, ap);
+ va_end(ap);
+ if (r < 0)
+ return r;
- ret++;
- }
+ ret = r;
+ args = strv_free(args);
}
-
- va_end(ap);
}
- return ret;
+ r = proc_cmdline_strv(&args);
+ if (r < 0)
+ return r;
+
+ va_start(ap, flags);
+ r = cmdline_get_key_ap(flags, args, ap);
+ va_end(ap);
+ if (r < 0)
+ return r;
+
+ return ret + r;
}
typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *data);
+int proc_cmdline_filter_pid1_args(char **argv, char ***ret);
+
int proc_cmdline(char **ret);
+int proc_cmdline_strv(char ***ret);
int proc_cmdline_parse(const proc_cmdline_parse_t parse, void *userdata, ProcCmdlineFlags flags);
"/usr/bin/true");
}
-const char* default_root_shell(const char *root) {
+const char* default_root_shell_at(int rfd) {
/* We want to use the preferred shell, i.e. DEFAULT_USER_SHELL, which usually
* will be /bin/bash. Fall back to /bin/sh if DEFAULT_USER_SHELL is not found,
* or any access errors. */
- int r = chase(DEFAULT_USER_SHELL, root, CHASE_PREFIX_ROOT, NULL, NULL);
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
+ int r = chaseat(rfd, DEFAULT_USER_SHELL, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL);
if (r < 0 && r != -ENOENT)
- log_debug_errno(r, "Failed to look up shell '%s%s%s': %m",
- strempty(root), root ? "/" : "", DEFAULT_USER_SHELL);
+ log_debug_errno(r, "Failed to look up shell '%s': %m", DEFAULT_USER_SHELL);
if (r > 0)
return DEFAULT_USER_SHELL;
return "/bin/sh";
}
+const char *default_root_shell(const char *root) {
+ _cleanup_close_ int rfd = -EBADF;
+
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return "/bin/sh";
+
+ return default_root_shell_at(rfd);
+}
+
static int synthesize_user_creds(
const char **username,
uid_t *uid, gid_t *gid,
#endif
bool is_nologin_shell(const char *shell);
+const char* default_root_shell_at(int rfd);
const char* default_root_shell(const char *root);
int is_this_me(const char *username);
/* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO)
r = chase_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
+ if (r == -ENOENT && arg_graceful) {
+ log_debug("Source directory does not exist, ignoring.");
+ return 0;
+ }
if (r < 0)
return log_error_errno(r, "Failed to open boot loader directory %s%s: %m", strempty(root), BOOTLIBDIR);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <fcntl.h>
+
#include "alloc-util.h"
#include "bootctl-uki.h"
#include "kernel-image.h"
KernelImageType t;
int r;
- r = inspect_kernel(argv[1], &t, NULL, NULL, NULL);
+ r = inspect_kernel(AT_FDCWD, argv[1], &t, NULL, NULL, NULL);
if (r < 0)
return r;
KernelImageType t;
int r;
- r = inspect_kernel(argv[1], &t, &cmdline, &uname, &pname);
+ r = inspect_kernel(AT_FDCWD, argv[1], &t, &cmdline, &uname, &pname);
if (r < 0)
return r;
efi_c_ld_args_primary = efi_c_ld_args
efi_c_ld_args_alt = efi_c_ld_args
-efi_c_args_primary += {
+efi_arch_c_args = {
'aarch64' : ['-mgeneral-regs-only'],
'arm' : ['-mgeneral-regs-only'],
'x86_64' : ['-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'],
- 'x86' : ['-march=i686', '-mgeneral-regs-only'],
-}.get(host_machine.cpu_family(), [])
+ 'x86' : ['-march=i686', '-mgeneral-regs-only', '-malign-double'],
+}
+efi_c_args_primary += efi_arch_c_args.get(host_machine.cpu_family(), [])
if efi_arch_alt == 'ia32'
- efi_c_args_alt += ['-m32', '-march=i686', '-mgeneral-regs-only']
+ efi_c_args_alt += ['-m32', efi_arch_c_args['x86']]
efi_c_ld_args_alt += '-m32'
endif
# define notify_debugger(i, w)
#endif
+/* On x86 the compiler assumes a different incoming stack alignment than what we get.
+ * This will cause long long variables to be misaligned when building with
+ * '-mlong-double' (for correct struct layouts). Normally, the compiler realigns the
+ * stack itself on entry, but we have to do this ourselves here as the compiler does
+ * not know that this is our entry point. */
+#ifdef __i386__
+# define _realign_stack_ __attribute__((force_align_arg_pointer))
+#else
+# define _realign_stack_
+#endif
+
#define DEFINE_EFI_MAIN_FUNCTION(func, identity, wait_for_debugger) \
EFI_SYSTEM_TABLE *ST; \
EFI_BOOT_SERVICES *BS; \
EFI_RUNTIME_SERVICES *RT; \
+ _realign_stack_ \
EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table); \
EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) { \
ST = system_table; \
" --verbose Show result values in long format\n"
" --json=MODE Output as JSON\n"
" -j Same as --json=pretty on tty, --json=short otherwise\n"
+ " --xml-interface Dump the XML description in introspect command\n"
" --expect-reply=BOOL Expect a method call reply\n"
" --auto-start=BOOL Auto-start destination service\n"
" --allow-interactive-authorization=BOOL\n"
assert(argc >= 1);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "-hkalM:u::xc", options, NULL)) >= 0)
switch (c) {
InstallChange *changes,
size_t n_changes) {
- int r;
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
for (size_t i = 0; i < n_changes; i++)
case -EEXIST:
if (changes[i].source)
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
- "File %s already exists and is a symlink to %s.",
- changes[i].path, changes[i].source);
- else
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
- "File %s already exists.",
- changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
+ "File %s already exists and is a symlink to %s.",
+ changes[i].path, changes[i].source);
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
+ "File %s already exists.",
+ changes[i].path);
case -ERFKILL:
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED,
- "Unit file %s is masked.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED,
+ "Unit file %s is masked.", changes[i].path);
case -EADDRNOTAVAIL:
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_GENERATED,
- "Unit %s is transient or generated.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_GENERATED,
+ "Unit %s is transient or generated.", changes[i].path);
case -ETXTBSY:
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_BAD_PATH,
- "File %s is under the systemd unit hierarchy already.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_BAD_PATH,
+ "File %s is under the systemd unit hierarchy already.", changes[i].path);
case -EBADSLT:
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "Invalid specifier in %s.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "Invalid specifier in %s.", changes[i].path);
case -EIDRM:
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "Destination unit %s is a non-template unit.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "Destination unit %s is a non-template unit.", changes[i].path);
case -EUCLEAN:
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "\"%s\" is not a valid unit name.",
- changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "\"%s\" is not a valid unit name.",
+ changes[i].path);
case -ELOOP:
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_LINKED,
- "Refusing to operate on alias name or linked unit file: %s",
- changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_LINKED,
+ "Refusing to operate on alias name or linked unit file: %s",
+ changes[i].path);
case -EXDEV:
if (changes[i].source)
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "Cannot alias %s as %s.",
- changes[i].source, changes[i].path);
- else
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "Invalid unit reference %s.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "Cannot alias %s as %s.",
+ changes[i].source, changes[i].path);
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "Invalid unit reference %s.", changes[i].path);
case -ENOENT:
- r = sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT,
- "Unit file %s does not exist.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT,
+ "Unit file %s does not exist.", changes[i].path);
case -EUNATCH:
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "Cannot resolve specifiers in %s.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "Cannot resolve specifiers in %s.", changes[i].path);
default:
assert(changes[i].type < 0); /* other errors */
- r = sd_bus_error_set_errnof(error, changes[i].type, "File %s: %m", changes[i].path);
- goto found;
+ return sd_bus_error_set_errnof(error, changes[i].type, "File %s: %m", changes[i].path);
}
- r = c < 0 ? c : -EINVAL;
-
- found:
- install_changes_free(changes, n_changes);
- return r;
+ return c < 0 ? c : -EINVAL;
}
static int reply_install_changes_and_free(
const char *name;
int runtime, r;
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
r = sd_bus_message_read(message, "sb", &name, &runtime);
if (r < 0)
return r;
r = unit_file_disable(m->runtime_scope,
UNIT_FILE_DRY_RUN | (runtime ? UNIT_FILE_RUNTIME : 0),
NULL, STRV_MAKE(name), &changes, &n_changes);
- if (r < 0) {
- log_error_errno(r, "Failed to get file links for %s: %m", name);
- goto finish;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to get file links for %s: %m", name);
for (i = 0; i < n_changes; i++)
if (changes[i].type == INSTALL_CHANGE_UNLINK) {
r = sd_bus_message_append(reply, "s", changes[i].path);
if (r < 0)
- goto finish;
+ return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
- goto finish;
-
- r = sd_bus_send(NULL, reply, NULL);
+ return r;
-finish:
- install_changes_free(changes, n_changes);
- return r;
+ return sd_bus_send(NULL, reply, NULL);
}
static int method_get_job_waiting(sd_bus_message *message, void *userdata, sd_bus_error *error) {
char ***ret_empty_directories) {
_cleanup_strv_free_ char **empty_directories = NULL;
- BindMount *bind_mounts;
+ BindMount *bind_mounts = NULL;
size_t n, h = 0;
int r;
assert(ret_n_bind_mounts);
assert(ret_empty_directories);
+ CLEANUP_ARRAY(bind_mounts, h, bind_mount_free_many);
+
n = context->n_bind_mounts;
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
if (!params->prefix[t])
for (size_t i = 0; i < context->n_bind_mounts; i++) {
BindMount *item = context->bind_mounts + i;
- char *s, *d;
+ _cleanup_free_ char *s = NULL, *d = NULL;
s = strdup(item->source);
- if (!s) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!s)
+ return -ENOMEM;
d = strdup(item->destination);
- if (!d) {
- free(s);
- r = -ENOMEM;
- goto finish;
- }
+ if (!d)
+ return -ENOMEM;
bind_mounts[h++] = (BindMount) {
- .source = s,
- .destination = d,
+ .source = TAKE_PTR(s),
+ .destination = TAKE_PTR(d),
.read_only = item->read_only,
.recursive = item->recursive,
.ignore_enoent = item->ignore_enoent,
* tmpfs that makes it accessible and is empty except for the submounts we do this for. */
private_root = path_join(params->prefix[t], "private");
- if (!private_root) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!private_root)
+ return -ENOMEM;
r = strv_consume(&empty_directories, private_root);
if (r < 0)
- goto finish;
+ return r;
}
for (size_t i = 0; i < context->directories[t].n_items; i++) {
- char *s, *d;
+ _cleanup_free_ char *s = NULL, *d = NULL;
/* When one of the parent directories is in the list, we cannot create the symlink
* for the child directory. See also the comments in setup_exec_directory(). */
s = path_join(params->prefix[t], "private", context->directories[t].items[i].path);
else
s = path_join(params->prefix[t], context->directories[t].items[i].path);
- if (!s) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!s)
+ return -ENOMEM;
if (exec_directory_is_private(context, t) &&
exec_context_with_rootfs(context))
d = path_join(params->prefix[t], context->directories[t].items[i].path);
else
d = strdup(s);
- if (!d) {
- free(s);
- r = -ENOMEM;
- goto finish;
- }
+ if (!d)
+ return -ENOMEM;
bind_mounts[h++] = (BindMount) {
- .source = s,
- .destination = d,
+ .source = TAKE_PTR(s),
+ .destination = TAKE_PTR(d),
.read_only = false,
.nosuid = context->dynamic_user, /* don't allow suid/sgid when DynamicUser= is on */
.recursive = true,
assert(h == n);
- *ret_bind_mounts = bind_mounts;
+ *ret_bind_mounts = TAKE_PTR(bind_mounts);
*ret_n_bind_mounts = n;
*ret_empty_directories = TAKE_PTR(empty_directories);
return (int) n;
-
-finish:
- bind_mount_free_many(bind_mounts, h);
- return r;
}
/* ret_symlinks will contain a list of pairs src:dest that describes
assert(context);
+ CLEANUP_ARRAY(bind_mounts, n_bind_mounts, bind_mount_free_many);
+
if (params->flags & EXEC_APPLY_CHROOT) {
root_image = context->root_image;
/* Symlinks for exec dirs are set up after other mounts, before they are made read-only. */
r = compile_symlinks(context, params, &symlinks);
if (r < 0)
- goto finalize;
+ return r;
/* We need to make the pressure path writable even if /sys/fs/cgroups is made read-only, as the
* service will need to write to it in order to start the notifications. */
if (context->protect_control_groups && memory_pressure_path && !streq(memory_pressure_path, "/dev/null")) {
read_write_paths_cleanup = strv_copy(context->read_write_paths);
- if (!read_write_paths_cleanup) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (!read_write_paths_cleanup)
+ return -ENOMEM;
r = strv_extend(&read_write_paths_cleanup, memory_pressure_path);
if (r < 0)
- goto finalize;
+ return r;
read_write_paths = read_write_paths_cleanup;
} else
params->prefix[EXEC_DIRECTORY_RUNTIME] &&
FLAGS_SET(params->flags, EXEC_WRITE_CREDENTIALS)) {
creds_path = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials", u->id);
- if (!creds_path) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (!creds_path)
+ return -ENOMEM;
}
if (MANAGER_IS_SYSTEM(u->manager)) {
propagate_dir = path_join("/run/systemd/propagate/", u->id);
- if (!propagate_dir) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (!propagate_dir)
+ return -ENOMEM;
incoming_dir = strdup("/run/systemd/incoming");
- if (!incoming_dir) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (!incoming_dir)
+ return -ENOMEM;
extension_dir = strdup("/run/systemd/unit-extensions");
- if (!extension_dir) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (!extension_dir)
+ return -ENOMEM;
} else
- if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0)
+ return -ENOMEM;
r = setup_namespace(
root_dir,
context,
root_dir, root_image,
bind_mounts,
- n_bind_mounts)) {
- log_unit_debug(u, "Failed to set up namespace, and refusing to continue since the selected namespacing options alter mount environment non-trivially.\n"
- "Bind mounts: %zu, temporary filesystems: %zu, root directory: %s, root image: %s, dynamic user: %s",
- n_bind_mounts, context->n_temporary_filesystems, yes_no(root_dir), yes_no(root_image), yes_no(context->dynamic_user));
-
- r = -EOPNOTSUPP;
- } else {
- log_unit_debug(u, "Failed to set up namespace, assuming containerized execution and ignoring.");
- r = 0;
- }
+ n_bind_mounts))
+ return log_unit_debug_errno(u,
+ SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Failed to set up namespace, and refusing to continue since "
+ "the selected namespacing options alter mount environment non-trivially.\n"
+ "Bind mounts: %zu, temporary filesystems: %zu, root directory: %s, root image: %s, dynamic user: %s",
+ n_bind_mounts,
+ context->n_temporary_filesystems,
+ yes_no(root_dir),
+ yes_no(root_image),
+ yes_no(context->dynamic_user));
+
+ log_unit_debug(u, "Failed to set up namespace, assuming containerized execution and ignoring.");
+ return 0;
}
-finalize:
- bind_mount_free_many(bind_mounts, n_bind_mounts);
return r;
}
#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
+#include "getopt-defs.h"
#include "hexdecoct.h"
#include "hostname-setup.h"
#include "ima-setup.h"
static int parse_argv(int argc, char *argv[]) {
enum {
- ARG_LOG_LEVEL = 0x100,
- ARG_LOG_TARGET,
- ARG_LOG_COLOR,
- ARG_LOG_LOCATION,
- ARG_LOG_TIME,
- ARG_UNIT,
- ARG_SYSTEM,
- ARG_USER,
- ARG_TEST,
- ARG_NO_PAGER,
- ARG_VERSION,
- ARG_DUMP_CONFIGURATION_ITEMS,
- ARG_DUMP_BUS_PROPERTIES,
- ARG_BUS_INTROSPECT,
- ARG_DUMP_CORE,
- ARG_CRASH_CHVT,
- ARG_CRASH_SHELL,
- ARG_CRASH_REBOOT,
- ARG_CONFIRM_SPAWN,
- ARG_SHOW_STATUS,
- ARG_DESERIALIZE,
- ARG_SWITCHED_ROOT,
- ARG_DEFAULT_STD_OUTPUT,
- ARG_DEFAULT_STD_ERROR,
- ARG_MACHINE_ID,
- ARG_SERVICE_WATCHDOGS,
+ COMMON_GETOPT_ARGS,
+ SYSTEMD_GETOPT_ARGS,
};
static const struct option options[] = {
- { "log-level", required_argument, NULL, ARG_LOG_LEVEL },
- { "log-target", required_argument, NULL, ARG_LOG_TARGET },
- { "log-color", optional_argument, NULL, ARG_LOG_COLOR },
- { "log-location", optional_argument, NULL, ARG_LOG_LOCATION },
- { "log-time", optional_argument, NULL, ARG_LOG_TIME },
- { "unit", required_argument, NULL, ARG_UNIT },
- { "system", no_argument, NULL, ARG_SYSTEM },
- { "user", no_argument, NULL, ARG_USER },
- { "test", no_argument, NULL, ARG_TEST },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS },
- { "dump-bus-properties", no_argument, NULL, ARG_DUMP_BUS_PROPERTIES },
- { "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT },
- { "dump-core", optional_argument, NULL, ARG_DUMP_CORE },
- { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT },
- { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL },
- { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT },
- { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN },
- { "show-status", optional_argument, NULL, ARG_SHOW_STATUS },
- { "deserialize", required_argument, NULL, ARG_DESERIALIZE },
- { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT },
- { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, },
- { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, },
- { "machine-id", required_argument, NULL, ARG_MACHINE_ID },
- { "service-watchdogs", required_argument, NULL, ARG_SERVICE_WATCHDOGS },
+ COMMON_GETOPT_OPTIONS,
+ SYSTEMD_GETOPT_OPTIONS,
{}
};
if (getpid_cached() == 1)
opterr = 0;
- while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL)) >= 0)
switch (c) {
if (isempty(host_os_release_id))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'ID' field not found or empty in 'os-release' data of OS tree '%s': %m", empty_to_root(root_directory));
- r = load_extension_release_pairs(mount_entry_source(m), extension_name, /* relax_extension_release_check= */ false, &extension_release);
+ r = load_extension_release_pairs(mount_entry_source(m), IMAGE_SYSEXT, extension_name, /* relax_extension_release_check= */ false, &extension_release);
if (r == -ENOENT && m->ignore)
return 0;
if (r < 0)
host_os_release_version_id,
host_os_release_sysext_level,
/* host_sysext_scope */ NULL, /* Leave empty, we need to accept both system and portable */
- extension_release);
+ extension_release,
+ IMAGE_SYSEXT);
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Directory %s extension-release metadata does not match the root's", extension_name);
if (r < 0)
}
if (n_extension_images > 0 || !strv_isempty(extension_directories)) {
- r = parse_env_extension_hierarchies(&hierarchies);
+ r = parse_env_extension_hierarchies(&hierarchies, "SYSTEMD_SYSEXT_HIERARCHIES");
if (r < 0)
return r;
}
if (r < 0)
return r;
- our_env = new0(char*, 12);
+ our_env = new0(char*, 13);
if (!our_env)
return -ENOMEM;
return -ENOMEM;
exec_params.notify_socket = UNIT(s)->manager->notify_socket;
+
+ if (s->n_fd_store_max > 0)
+ if (asprintf(our_env + n_env++, "FDSTORE=%u", s->n_fd_store_max) < 0)
+ return -ENOMEM;
}
if (s->main_pid > 0)
if (UNIT_VTABLE(u)->once_only && dual_timestamp_is_set(&u->inactive_enter_timestamp))
return -ESTALE;
- /* If the conditions failed, don't do anything at all. If we already are activating this call might
+ /* If the conditions were unmet, don't do anything at all. If we already are activating this call might
* still be useful to speed up activation in case there is some hold-off time, but we don't want to
* recheck the condition in that case. */
if (state != UNIT_ACTIVATING &&
!unit_test_condition(u))
- return log_unit_debug_errno(u, SYNTHETIC_ERRNO(ECOMM), "Starting requested but condition failed. Not starting unit.");
+ return log_unit_debug_errno(u, SYNTHETIC_ERRNO(ECOMM), "Starting requested but condition not met. Not starting unit.");
/* If the asserts failed, fail the entire job */
if (state != UNIT_ACTIVATING &&
return k != 'q';
}
-static void print_welcome(void) {
+static void print_welcome(int rfd) {
_cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL;
static bool done = false;
const char *pn, *ac;
int r;
+ assert(rfd >= 0);
+
if (!arg_welcome)
return;
if (done)
return;
- r = parse_os_release(
- arg_root,
- "PRETTY_NAME", &pretty_name,
- "NAME", &os_name,
- "ANSI_COLOR", &ansi_color);
+ r = parse_os_release_at(rfd,
+ "PRETTY_NAME", &pretty_name,
+ "NAME", &os_name,
+ "ANSI_COLOR", &ansi_color);
if (r < 0)
log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
"Failed to read os-release file, ignoring: %m");
return arg_force; /* exists, but if --force was given we should still configure the file. */
}
-static bool locale_is_ok(const char *name) {
+static bool locale_is_installed_bool(const char *name) {
+ return locale_is_installed(name) > 0;
+}
- if (arg_root)
- return locale_is_valid(name);
+static bool locale_is_ok(int rfd, const char *name) {
+ assert(rfd >= 0);
- return locale_is_installed(name) > 0;
+ return dir_fd_is_root(rfd) ? locale_is_installed_bool(name) : locale_is_valid(name);
}
-static int prompt_locale(void) {
+static int prompt_locale(int rfd) {
_cleanup_strv_free_ char **locales = NULL;
bool acquired_from_creds = false;
int r;
+ assert(rfd >= 0);
+
if (arg_locale || arg_locale_messages)
return 0;
/* Not setting arg_locale_message here, since it defaults to LANG anyway */
}
} else {
- print_welcome();
+ bool (*is_valid)(const char *name) = dir_fd_is_root(rfd) ? locale_is_installed_bool
+ : locale_is_valid;
+
+ print_welcome(rfd);
r = prompt_loop("Please enter system locale name or number",
- locales, 60, locale_is_ok, &arg_locale);
+ locales, 60, is_valid, &arg_locale);
if (r < 0)
return r;
return 0;
r = prompt_loop("Please enter system message locale name or number",
- locales, 60, locale_is_ok, &arg_locale_messages);
+ locales, 60, is_valid, &arg_locale_messages);
if (r < 0)
return r;
}
}
- r = prompt_locale();
+ r = prompt_locale(rfd);
if (r < 0)
return r;
return 0;
}
-static int prompt_keymap(void) {
+static int prompt_keymap(int rfd) {
_cleanup_strv_free_ char **kmaps = NULL;
int r;
+ assert(rfd >= 0);
+
if (arg_keymap)
return 0;
if (r < 0)
return log_error_errno(r, "Failed to read keymaps: %m");
- print_welcome();
+ print_welcome(rfd);
return prompt_loop("Please enter system keymap name or number",
kmaps, 60, keymap_is_valid, &arg_keymap);
}
}
- r = prompt_keymap();
+ r = prompt_keymap(rfd);
if (r == -ENOENT)
return 0; /* don't fail if no keymaps are installed */
if (r < 0)
return timezone_is_valid(name, LOG_ERR);
}
-static int prompt_timezone(void) {
+static int prompt_timezone(int rfd) {
_cleanup_strv_free_ char **zones = NULL;
int r;
+ assert(rfd >= 0);
+
if (arg_timezone)
return 0;
if (r < 0)
return log_error_errno(r, "Cannot query timezone list: %m");
- print_welcome();
+ print_welcome(rfd);
r = prompt_loop("Please enter timezone name or number",
zones, 30, timezone_is_valid_log_error, &arg_timezone);
}
}
- r = prompt_timezone();
+ r = prompt_timezone(rfd);
if (r < 0)
return r;
return 0;
}
-static int prompt_hostname(void) {
+static int prompt_hostname(int rfd) {
int r;
+ assert(rfd >= 0);
+
if (arg_hostname)
return 0;
return 0;
}
- print_welcome();
+ print_welcome(rfd);
putchar('\n');
for (;;) {
if (r <= 0)
return r;
- r = prompt_hostname();
+ r = prompt_hostname(rfd);
if (r < 0)
return r;
return 0;
}
-static int prompt_root_password(void) {
+static int prompt_root_password(int rfd) {
const char *msg1, *msg2;
int r;
+ assert(rfd >= 0);
+
if (arg_root_password)
return 0;
return 0;
}
- print_welcome();
+ print_welcome(rfd);
putchar('\n');
msg1 = strjoina(special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), " Please enter a new root password (empty to skip):");
static int prompt_root_shell(int rfd) {
int r;
+ assert(rfd >= 0);
+
if (arg_root_shell)
return 0;
return 0;
}
- print_welcome();
+ print_welcome(rfd);
putchar('\n');
for (;;) {
return 0;
}
-static int write_root_passwd(int etc_fd, const char *password, const char *shell) {
+static int write_root_passwd(int rfd, int etc_fd, const char *password, const char *shell) {
_cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
_cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
int r;
.pw_gid = 0,
.pw_gecos = (char *) "Super User",
.pw_dir = (char *) "/root",
- .pw_shell = (char *) (shell ?: default_root_shell(arg_root)),
+ .pw_shell = (char *) (shell ?: default_root_shell_at(rfd)),
};
if (errno != ENOENT)
arg_root_password_is_hashed = true;
}
- r = prompt_root_password();
+ r = prompt_root_password(rfd);
if (r < 0)
return r;
else
password = hashed_password = PASSWORD_LOCKED_AND_INVALID;
- r = write_root_passwd(pfd, password, arg_root_shell);
+ r = write_root_passwd(rfd, pfd, password, arg_root_shell);
if (r < 0)
return log_error_errno(r, "Failed to write /etc/passwd: %m");
assert_not_reached();
}
- /* We check if the specified locale strings are valid down here, so that we can take --root= into
- * account when looking for the locale files. */
-
- if (arg_locale && !locale_is_ok(arg_locale))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale);
- if (arg_locale_messages && !locale_is_ok(arg_locale_messages))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages);
-
if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--delete-root-password cannot be combined with other root password options");
LOG_SET_PREFIX(arg_image ?: arg_root);
+ /* We check these conditions here instead of in parse_argv() so that we can take the root directory
+ * into account. */
+
+ if (arg_locale && !locale_is_ok(rfd, arg_locale))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale);
+ if (arg_locale_messages && !locale_is_ok(rfd, arg_locale_messages))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages);
+
if (arg_root_shell) {
r = find_shell(rfd, arg_root_shell);
if (r < 0)
}
if (sysfs_check < 0) {
- r = getenv_bool_secure("SYSTEMD_SYSFS_CHECK");
- if (r < 0 && r != -ENXIO)
- log_debug_errno(r, "Failed to parse $SYSTEMD_SYSFS_CHECK, ignoring: %m");
- sysfs_check = r != 0;
+ k = getenv_bool_secure("SYSTEMD_SYSFS_CHECK");
+ if (k < 0 && k != -ENXIO)
+ log_debug_errno(k, "Failed to parse $SYSTEMD_SYSFS_CHECK, ignoring: %m");
+ sysfs_check = k != 0;
}
if (sysfs_check && is_device_path(what)) {
}
size_t csize;
- r = compress_blob_explicit(alg, h->data, data_len, buf, size, &csize);
+ r = compress_blob(alg, h->data, data_len, buf, size, &csize);
if (r < 0) {
log_error_errno(r, "Compression failed: %m");
return 0;
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0)
switch (c) {
if (json_variant_elements(parameters) > 0)
return varlink_error_invalid_parameter(link, parameters);
- log_info("Received client request to rotate journal.");
+ log_info("Received client request to sync journal.");
/* We don't do the main work now, but instead enqueue a deferred event loop job which will do
* it. That job is scheduled at low priority, so that we return from this method call only after all
struct bus_match_component *components = NULL;
size_t n_components = 0;
- sd_bus_slot *s = NULL;
- int r = 0;
+ _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *s = NULL;
+ int r;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(match, -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
+ CLEANUP_ARRAY(components, n_components, bus_match_parse_free);
+
r = bus_match_parse(match, &components, &n_components);
if (r < 0)
- goto finish;
+ return r;
s = bus_slot_allocate(bus, !slot, BUS_MATCH_CALLBACK, sizeof(struct match_callback), userdata);
- if (!s) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!s)
+ return -ENOMEM;
s->match_callback.callback = callback;
s->match_callback.install_callback = install_callback;
/* We store the original match string, so that we can use it to remove the match again. */
s->match_callback.match_string = strdup(match);
- if (!s->match_callback.match_string) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!s->match_callback.match_string)
+ return -ENOMEM;
if (asynchronous) {
r = bus_add_match_internal_async(bus,
s);
if (r < 0)
- goto finish;
+ return r;
/* Make the slot of the match call floating now. We need the reference, but we don't
* want that this match pins the bus object, hence we first create it non-floating, but
} else
r = bus_add_match_internal(bus, s->match_callback.match_string, &s->match_callback.after);
if (r < 0)
- goto finish;
+ return r;
s->match_added = true;
}
bus->match_callbacks_modified = true;
r = bus_match_add(&bus->match_callbacks, components, n_components, &s->match_callback);
if (r < 0)
- goto finish;
+ return r;
if (slot)
*slot = s;
s = NULL;
-finish:
- bus_match_parse_free(components, n_components);
- sd_bus_slot_unref(s);
-
- return r;
+ return 0;
}
_public_ int sd_bus_add_match(
return r;
if (r == 0)
break;
+ if (isempty(word))
+ continue;
r = device_add_tag(device, word, streq(key, "CURRENT_TAGS"));
if (r < 0)
void device_set_devlink_priority(sd_device *device, int priority);
int device_ensure_usec_initialized(sd_device *device, sd_device *device_old);
int device_add_devlink(sd_device *device, const char *devlink);
-void device_remove_devlink(sd_device *device, const char *devlink);
+int device_remove_devlink(sd_device *device, const char *devlink);
bool device_has_devlink(sd_device *device, const char *devlink);
int device_add_property(sd_device *device, const char *property, const char *value);
int device_add_propertyf(sd_device *device, const char *key, const char *format, ...) _printf_(3, 4);
return 0;
}
+static int mangle_devname(const char *p, char **ret) {
+ char *q;
+
+ assert(p);
+ assert(ret);
+
+ if (!path_is_safe(p))
+ return -EINVAL;
+
+ /* When the path is absolute, it must start with "/dev/", but ignore "/dev/" itself. */
+ if (path_is_absolute(p)) {
+ if (isempty(path_startswith(p, "/dev/")))
+ return -EINVAL;
+
+ q = strdup(p);
+ } else
+ q = path_join("/dev/", p);
+ if (!q)
+ return -ENOMEM;
+
+ path_simplify(q);
+
+ *ret = q;
+ return 0;
+}
+
int device_set_devname(sd_device *device, const char *devname) {
_cleanup_free_ char *t = NULL;
int r;
assert(device);
assert(devname);
- if (devname[0] != '/')
- t = strjoin("/dev/", devname);
- else
- t = strdup(devname);
- if (!t)
- return -ENOMEM;
+ r = mangle_devname(devname, &t);
+ if (r < 0)
+ return r;
r = device_add_property_internal(device, "DEVNAME", t);
if (r < 0)
if (!device->devname)
return -ENOENT;
- assert(path_startswith(device->devname, "/dev/"));
+ assert(!isempty(path_startswith(device->devname, "/dev/")));
if (devname)
*devname = device->devname;
static bool is_valid_tag(const char *tag) {
assert(tag);
- return !strchr(tag, ':') && !strchr(tag, ' ');
+ return in_charset(tag, ALPHANUMERICAL "-_") && filename_is_valid(tag);
}
int device_add_tag(sd_device *device, const char *tag, bool both) {
}
int device_add_devlink(sd_device *device, const char *devlink) {
+ char *p;
int r;
assert(device);
assert(devlink);
- r = set_put_strdup(&device->devlinks, devlink);
+ r = mangle_devname(devlink, &p);
+ if (r < 0)
+ return r;
+
+ r = set_ensure_consume(&device->devlinks, &path_hash_ops_free, p);
if (r < 0)
return r;
device->devlinks_generation++;
device->property_devlinks_outdated = true;
- return 0;
+ return r; /* return 1 when newly added, 0 when already exists */
}
-void device_remove_devlink(sd_device *device, const char *devlink) {
- _cleanup_free_ char *s = NULL;
+int device_remove_devlink(sd_device *device, const char *devlink) {
+ _cleanup_free_ char *p = NULL, *s = NULL;
+ int r;
assert(device);
assert(devlink);
- s = set_remove(device->devlinks, devlink);
+ r = mangle_devname(devlink, &p);
+ if (r < 0)
+ return r;
+
+ s = set_remove(device->devlinks, p);
if (!s)
- return;
+ return 0; /* does not exist */
device->devlinks_generation++;
device->property_devlinks_outdated = true;
+ return 1; /* removed */
}
bool device_has_devlink(sd_device *device, const char *devlink) {
return -ENOMEM;
}
- r = hashmap_ensure_put(&device->sysattr_values, &string_hash_ops_free_free, new_key, value);
+ r = hashmap_ensure_put(&device->sysattr_values, &path_hash_ops_free_free, new_key, value);
if (r < 0)
return r;
#include "journal-internal.h"
#include "lookup3.h"
#include "memory-util.h"
+#include "missing_threads.h"
#include "path-util.h"
#include "prioq.h"
#include "random-util.h"
}
static bool keyed_hash_requested(void) {
+ static thread_local int cached = -1;
int r;
- r = getenv_bool("SYSTEMD_JOURNAL_KEYED_HASH");
- if (r >= 0)
- return r;
- if (r != -ENXIO)
- log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_KEYED_HASH environment variable, ignoring: %m");
+ if (cached < 0) {
+ r = getenv_bool("SYSTEMD_JOURNAL_KEYED_HASH");
+ if (r < 0) {
+ if (r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_KEYED_HASH environment variable, ignoring: %m");
+ cached = true;
+ } else
+ cached = r;
+ }
- return true;
+ return cached;
}
static bool compact_mode_requested(void) {
+ static thread_local int cached = -1;
+ int r;
+
+ if (cached < 0) {
+ r = getenv_bool("SYSTEMD_JOURNAL_COMPACT");
+ if (r < 0) {
+ if (r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_COMPACT environment variable, ignoring: %m");
+ cached = true;
+ } else
+ cached = r;
+ }
+
+ return cached;
+}
+
+#if HAVE_COMPRESSION
+static Compression getenv_compression(void) {
+ Compression c;
+ const char *e;
int r;
- r = getenv_bool("SYSTEMD_JOURNAL_COMPACT");
+ e = getenv("SYSTEMD_JOURNAL_COMPRESS");
+ if (!e)
+ return DEFAULT_COMPRESSION;
+
+ r = parse_boolean(e);
if (r >= 0)
- return r;
- if (r != -ENXIO)
- log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_COMPACT environment variable, ignoring: %m");
+ return r ? DEFAULT_COMPRESSION : COMPRESSION_NONE;
- return true;
+ c = compression_from_string(e);
+ if (c < 0) {
+ log_debug_errno(c, "Failed to parse SYSTEMD_JOURNAL_COMPRESS value, ignoring: %s", e);
+ return DEFAULT_COMPRESSION;
+ }
+
+ if (!compression_supported(c)) {
+ log_debug("Unsupported compression algorithm specified, ignoring: %s", e);
+ return DEFAULT_COMPRESSION;
+ }
+
+ return c;
+}
+#endif
+
+static Compression compression_requested(void) {
+#if HAVE_COMPRESSION
+ static thread_local Compression cached = _COMPRESSION_INVALID;
+
+ if (cached < 0)
+ cached = getenv_compression();
+
+ return cached;
+#else
+ return COMPRESSION_NONE;
+#endif
}
static int journal_file_init_header(
Header h = {
.header_size = htole64(ALIGN64(sizeof(h))),
.incompatible_flags = htole32(
- FLAGS_SET(file_flags, JOURNAL_COMPRESS) * COMPRESSION_TO_HEADER_INCOMPATIBLE_FLAG(DEFAULT_COMPRESSION) |
+ FLAGS_SET(file_flags, JOURNAL_COMPRESS) * COMPRESSION_TO_HEADER_INCOMPATIBLE_FLAG(compression_requested()) |
keyed_hash_requested() * HEADER_INCOMPATIBLE_KEYED_HASH |
compact_mode_requested() * HEADER_INCOMPATIBLE_COMPACT),
.compatible_flags = htole32(
return 0;
}
-static Compression maybe_compress_payload(JournalFile *f, uint8_t *dst, const uint8_t *src, uint64_t size, size_t *rsize) {
- Compression compression = COMPRESSION_NONE;
-
+static int maybe_compress_payload(JournalFile *f, uint8_t *dst, const uint8_t *src, uint64_t size, size_t *rsize) {
assert(f);
assert(f->header);
#if HAVE_COMPRESSION
- if (JOURNAL_FILE_COMPRESS(f) && size >= f->compress_threshold_bytes) {
- compression = compress_blob(src, size, dst, size - 1, rsize);
- if (compression > 0)
- log_debug("Compressed data object %"PRIu64" -> %zu using %s",
- size, *rsize, compression_to_string(compression));
- else
- /* Compression didn't work, we don't really care why, let's continue without compression */
- compression = COMPRESSION_NONE;
- }
-#endif
+ Compression c;
+ int r;
+
+ c = JOURNAL_FILE_COMPRESSION(f);
+ if (c == COMPRESSION_NONE || size < f->compress_threshold_bytes)
+ return 0;
+
+ r = compress_blob(c, src, size, dst, size - 1, rsize);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to compress data object using %s, ignoring: %m", compression_to_string(c));
- return compression;
+ log_debug("Compressed data object %"PRIu64" -> %zu using %s", size, *rsize, compression_to_string(c));
+
+ return 1; /* compressed */
+#else
+ return 0;
+#endif
}
static int journal_file_append_data(
uint64_t hash, p, osize;
Object *o, *fo;
size_t rsize = 0;
- Compression c;
const void *eq;
int r;
o->data.hash = htole64(hash);
- c = maybe_compress_payload(f, journal_file_data_payload_field(f, o), data, size, &rsize);
+ r = maybe_compress_payload(f, journal_file_data_payload_field(f, o), data, size, &rsize);
+ if (r <= 0)
+ /* We don't really care failures, let's continue without compression */
+ memcpy_safe(journal_file_data_payload_field(f, o), data, size);
+ else {
+ Compression c = JOURNAL_FILE_COMPRESSION(f);
+
+ assert(c >= 0 && c < _COMPRESSION_MAX && c != COMPRESSION_NONE);
- if (c != COMPRESSION_NONE) {
o->object.size = htole64(journal_file_data_payload_offset(f) + rsize);
o->object.flags |= COMPRESSION_TO_OBJECT_FLAG(c);
- } else
- memcpy_safe(journal_file_data_payload_field(f, o), data, size);
+ }
r = journal_file_link_data(f, o, p, hash);
if (r < 0)
f->close_fd = true;
if (DEBUG_LOGGING) {
- static int last_seal = -1, last_compress = -1, last_keyed_hash = -1;
+ static int last_seal = -1, last_keyed_hash = -1;
+ static Compression last_compression = _COMPRESSION_INVALID;
static uint64_t last_bytes = UINT64_MAX;
if (last_seal != JOURNAL_HEADER_SEALED(f->header) ||
last_keyed_hash != JOURNAL_HEADER_KEYED_HASH(f->header) ||
- last_compress != JOURNAL_FILE_COMPRESS(f) ||
+ last_compression != JOURNAL_FILE_COMPRESSION(f) ||
last_bytes != f->compress_threshold_bytes) {
log_debug("Journal effective settings seal=%s keyed_hash=%s compress=%s compress_threshold_bytes=%s",
yes_no(JOURNAL_HEADER_SEALED(f->header)), yes_no(JOURNAL_HEADER_KEYED_HASH(f->header)),
- yes_no(JOURNAL_FILE_COMPRESS(f)), FORMAT_BYTES(f->compress_threshold_bytes));
+ compression_to_string(JOURNAL_FILE_COMPRESSION(f)), FORMAT_BYTES(f->compress_threshold_bytes));
last_seal = JOURNAL_HEADER_SEALED(f->header);
last_keyed_hash = JOURNAL_HEADER_KEYED_HASH(f->header);
- last_compress = JOURNAL_FILE_COMPRESS(f);
+ last_compression = JOURNAL_FILE_COMPRESSION(f);
last_bytes = f->compress_threshold_bytes;
}
}
int journal_file_map_data_hash_table(JournalFile *f);
int journal_file_map_field_hash_table(JournalFile *f);
-static inline bool JOURNAL_FILE_COMPRESS(JournalFile *f) {
+static inline Compression JOURNAL_FILE_COMPRESSION(JournalFile *f) {
assert(f);
- return JOURNAL_HEADER_COMPRESSED_XZ(f->header) || JOURNAL_HEADER_COMPRESSED_LZ4(f->header) ||
- JOURNAL_HEADER_COMPRESSED_ZSTD(f->header);
+
+ if (JOURNAL_HEADER_COMPRESSED_XZ(f->header))
+ return COMPRESSION_XZ;
+ if (JOURNAL_HEADER_COMPRESSED_LZ4(f->header))
+ return COMPRESSION_LZ4;
+ if (JOURNAL_HEADER_COMPRESSED_ZSTD(f->header))
+ return COMPRESSION_ZSTD;
+ return COMPRESSION_NONE;
}
uint64_t journal_file_hash_data(JournalFile *f, const void *data, size_t sz);
for (;;) {
_cleanup_free_ char *line = NULL;
+ char *l;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
if (r == 0)
return 0;
- line = strstrip(line);
- if (strcaseeq_ptr(line, locale_entry))
+ l = strstrip(line);
+ if (strcaseeq_ptr(l, locale_entry))
return 1;
}
}
continue;
}
- line = strstrip(line);
- if (isempty(line)) {
+ line_locale = strstrip(line);
+ if (isempty(line_locale)) {
fputc('\n', fw);
first_line = false;
continue;
}
- line_locale = line;
if (line_locale[0] == '#')
line_locale = strstrip(line_locale + 1);
else if (strcaseeq_ptr(line_locale, locale_entry))
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+h", options, NULL)) >= 0)
switch (c) {
}
static int edit_settings(int argc, char *argv[], void *userdata) {
- _cleanup_(edit_file_context_done) EditFileContext context = {
- .remove_parent = false,
- };
+ _cleanup_(edit_file_context_done) EditFileContext context = {};
int r;
if (!on_tty())
static int enable_machine(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- InstallChange *changes = NULL;
- size_t n_changes = 0;
const char *method;
sd_bus *bus = ASSERT_PTR(userdata);
int r;
+ bool enable;
polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles";
+ enable = streq(argv[0], "enable");
+ method = enable ? "EnableUnitFiles" : "DisableUnitFiles";
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method);
if (r < 0)
if (r < 0)
return bus_log_create_error(r);
- if (streq(argv[0], "enable")) {
+ if (enable) {
r = sd_bus_message_append(m, "s", "machines.target");
if (r < 0)
return bus_log_create_error(r);
if (r < 0)
return bus_log_create_error(r);
- if (streq(argv[0], "enable"))
+ if (enable)
r = sd_bus_message_append(m, "bb", false, false);
else
r = sd_bus_message_append(m, "b", false);
if (r < 0)
return log_error_errno(r, "Failed to enable or disable unit: %s", bus_error_message(&error, r));
- if (streq(argv[0], "enable")) {
+ if (enable) {
r = sd_bus_message_read(reply, "b", NULL);
if (r < 0)
return bus_log_parse_error(r);
}
- r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+ r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
r = bus_call_method(bus, bus_systemd_mgr, "Reload", &error, NULL, NULL);
- if (r < 0) {
- log_error("Failed to reload daemon: %s", bus_error_message(&error, r));
- goto finish;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
if (arg_now) {
_cleanup_strv_free_ char **new_args = NULL;
- new_args = strv_new(streq(argv[0], "enable") ? "start" : "poweroff");
- if (!new_args) {
- r = log_oom();
- goto finish;
- }
+ new_args = strv_new(enable ? "start" : "poweroff");
+ if (!new_args)
+ return log_oom();
r = strv_extend_strv(&new_args, argv + 1, /* filter_duplicates = */ false);
- if (r < 0) {
- log_oom();
- goto finish;
- }
+ if (r < 0)
+ return log_oom();
- if (streq(argv[0], "enable"))
- r = start_machine(strv_length(new_args), new_args, userdata);
- else
- r = poweroff_machine(strv_length(new_args), new_args, userdata);
- }
+ if (enable)
+ return start_machine(strv_length(new_args), new_args, userdata);
-finish:
- install_changes_free(changes, n_changes);
+ return poweroff_machine(strv_length(new_args), new_args, userdata);
+ }
- return r;
+ return 0;
}
static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) {
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
+
for (;;) {
static const char option_string[] = "-hp:als:H:M:qn:o:E:";
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:nUE:P", options, NULL)) >= 0)
switch (c) {
assert(ret);
+ log_info("Populating %s filesystem.", p->format);
+
r = var_tmp_dir(&vt);
if (r < 0)
return log_error_errno(r, "Could not determine temporary directory: %m");
if (r < 0)
return r;
+ log_info("Successfully populated %s filesystem.", p->format);
+
*ret = TAKE_PTR(root);
return 0;
}
assert(p);
assert(node);
- log_info("Populating %s filesystem with files.", p->format);
+ log_info("Populating %s filesystem.", p->format);
/* We copy in a child process, since we have to mount the fs for that, and we don't want that fs to
* appear in the host namespace. Hence we fork a child that has its own file system namespace and
_exit(EXIT_SUCCESS);
}
- log_info("Successfully populated %s filesystem with files.", p->format);
+ log_info("Successfully populated %s filesystem.", p->format);
return 0;
}
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
_cleanup_strv_free_ char **extra_mkfs_options = NULL;
_cleanup_close_ int fd = -EBADF;
+ _cleanup_free_ char *hint = NULL;
sd_id128_t fs_uuid;
uint64_t fsz;
assert(!p->copy_blocks_path);
+ (void) partition_hint(p, context->node, &hint);
+
+ log_info("Pre-populating %s filesystem of partition %s twice to calculate minimal partition size",
+ p->format, strna(hint));
+
r = make_copy_files_denylist(context, p, &denylist);
if (r < 0)
return r;
/* Read-only filesystems are minimal from the first try because they create and size the
* loopback file for us. */
if (fstype_is_ro(p->format)) {
+ struct stat st;
+
+ if (stat(temp, &st) < 0)
+ return log_error_errno(errno, "Failed to stat temporary file: %m");
+
+ log_info("Minimal partition size of %s filesystem of partition %s is %s",
+ p->format, strna(hint), FORMAT_BYTES(st.st_size));
+
p->copy_blocks_path = TAKE_PTR(temp);
p->copy_blocks_path_is_our_file = true;
continue;
if (minimal_size_by_fs_name(p->format) != UINT64_MAX)
fsz = MAX(minimal_size_by_fs_name(p->format), fsz);
+ log_info("Minimal partition size of %s filesystem of partition %s is %s",
+ p->format, strna(hint), FORMAT_BYTES(fsz));
+
d = loop_device_unref(d);
/* Erase the previous filesystem first. */
/* First, find os-release/extension-release and send it upstream (or just save it). */
if (path_is_extension) {
os_release_id = strjoina("/usr/lib/extension-release.d/extension-release.", image_name);
- r = open_extension_release(where, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
+ r = open_extension_release(where, IMAGE_SYSEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
} else {
os_release_id = "/etc/os-release";
r = open_os_release(where, &os_release_path, &os_release_fd);
return r;
if (validate_sysext) {
- r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release);
+ r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release, IMAGE_SYSEXT);
if (r == 0)
return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
if (r < 0)
static const char *const field_versions[_IMAGE_CLASS_MAX][4]= {
[IMAGE_PORTABLE] = { "IMAGE_VERSION", "VERSION_ID", "BUILD_ID", NULL },
- [IMAGE_EXTENSION] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL },
+ [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL },
};
static const char *const field_ids[_IMAGE_CLASS_MAX][3]= {
[IMAGE_PORTABLE] = { "IMAGE_ID", "ID", NULL },
- [IMAGE_EXTENSION] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL },
+ [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL },
};
_cleanup_strv_free_ char **fields = NULL;
const char *id = NULL, *version = NULL;
int r;
- assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_EXTENSION));
+ assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_SYSEXT));
assert(!strv_isempty((char *const *)field_ids[type]));
assert(!strv_isempty((char *const *)field_versions[type]));
assert(field_name);
* still be able to identify what applies to what. */
r = append_release_log_fields(&text,
ordered_hashmap_get(extension_releases, ext->name),
- IMAGE_EXTENSION,
+ IMAGE_SYSEXT,
"PORTABLE_EXTENSION_NAME_AND_VERSION");
if (r < 0)
return r;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_strv_free_ char **names = NULL;
- InstallChange *changes = NULL;
const uint64_t flags = UNIT_FILE_PORTABLE | (arg_runtime ? UNIT_FILE_RUNTIME : 0);
- size_t n_changes = 0;
int r;
if (!arg_enable)
return bus_log_parse_error(r);
}
- (void) bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
- install_changes_free(changes, n_changes);
+ (void) bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
return 0;
}
assert(message);
+ CLEANUP_ARRAY(changes, n_changes, portable_changes_free);
+
/* Note that we do not redirect detaching to the image object here, because we want to allow that users can
* detach already deleted images too, in case the user already deleted an image before properly detaching
* it. */
&n_changes,
error);
if (r < 0)
- goto finish;
-
- r = reply_portable_changes(message, changes, n_changes);
+ return r;
-finish:
- portable_changes_free(changes, n_changes);
- return r;
+ return reply_portable_changes(message, changes, n_changes);
}
static int method_reattach_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
if (d) {
assert(d->fd >= 0);
- f = take_fdopen(&d->fd, "r");
- if (!f)
- return -errno;
+ r = fdopen_independent(d->fd, "r", &f);
+ if (r < 0)
+ return r;
r = read_full_stream(f, &buf, &n);
if (r < 0)
assert(message);
assert(name_or_path || image);
+ CLEANUP_ARRAY(changes, n_changes, portable_changes_free);
+
if (!m) {
assert(image);
m = image->userdata;
&n_changes,
error);
if (r < 0)
- goto finish;
-
- r = reply_portable_changes(message, changes, n_changes);
+ return r;
-finish:
- portable_changes_free(changes, n_changes);
- return r;
+ return reply_portable_changes(message, changes, n_changes);
}
static int bus_image_method_attach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
assert(message);
+ CLEANUP_ARRAY(changes, n_changes, portable_changes_free);
+
if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) {
r = sd_bus_message_read_strv(message, &extension_images);
if (r < 0)
&n_changes,
error);
if (r < 0)
- goto finish;
-
- r = reply_portable_changes(message, changes, n_changes);
+ return r;
-finish:
- portable_changes_free(changes, n_changes);
- return r;
+ return reply_portable_changes(message, changes, n_changes);
}
int bus_image_common_remove(
assert(message);
assert(name_or_path || image);
+ CLEANUP_ARRAY(changes_detached, n_changes_detached, portable_changes_free);
+ CLEANUP_ARRAY(changes_attached, n_changes_attached, portable_changes_free);
+ CLEANUP_ARRAY(changes_gone, n_changes_gone, portable_changes_free);
+
if (!m) {
assert(image);
m = image->userdata;
&n_changes_detached,
error);
if (r < 0)
- goto finish;
+ return r;
r = portable_attach(
sd_bus_message_get_bus(message),
&n_changes_attached,
error);
if (r < 0)
- goto finish;
+ return r;
/* We want to return the list of units really removed by the detach,
* and not added again by the attach */
changes_detached, n_changes_detached,
&changes_gone, &n_changes_gone);
if (r < 0)
- goto finish;
+ return r;
/* First, return the units that are gone (so that the caller can stop them)
* Then, return the units that are changed/added (so that the caller can
* start/restart/enable them) */
- r = reply_portable_changes_pair(message,
- changes_gone, n_changes_gone,
- changes_attached, n_changes_attached);
- if (r < 0)
- goto finish;
-
-finish:
- portable_changes_free(changes_detached, n_changes_detached);
- portable_changes_free(changes_attached, n_changes_attached);
- portable_changes_free(changes_gone, n_changes_gone);
- return r;
+ return reply_portable_changes_pair(message,
+ changes_gone, n_changes_gone,
+ changes_attached, n_changes_attached);
}
static int bus_image_method_reattach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* RFC 1035 say 512 is the maximum, for classic unicast DNS */
#define DNS_PACKET_UNICAST_SIZE_MAX 512u
-/* With EDNS0 we can use larger packets, default to 4096, which is what is commonly used */
-#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 4096u
+/* With EDNS0 we can use larger packets, default to 1232, which is what is commonly used */
+#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 1232u
struct DnsPacket {
unsigned n_ref;
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+hrH:M:E:p:tPqGdSu:", options, NULL)) >= 0)
switch (c) {
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type, BootEntryType);
+static const char* const boot_entry_type_json_table[_BOOT_ENTRY_TYPE_MAX] = {
+ [BOOT_ENTRY_CONF] = "type1",
+ [BOOT_ENTRY_UNIFIED] = "type2",
+ [BOOT_ENTRY_LOADER] = "loader",
+ [BOOT_ENTRY_LOADER_AUTO] = "auto",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_json, BootEntryType);
+
static void boot_entry_free(BootEntry *entry) {
assert(entry);
}
r = json_append(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("type", JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))),
JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)),
JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)),
JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)),
* arguments and trigger false positive warnings. Let's not add too many json objects
* at once. */
r = json_append(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("isReported", JSON_BUILD_BOOLEAN(e->reported_by_loader)),
JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", JSON_BUILD_UNSIGNED(e->tries_left)),
JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", JSON_BUILD_UNSIGNED(e->tries_done)),
JSON_BUILD_PAIR_CONDITION(config->default_entry >= 0, "isDefault", JSON_BUILD_BOOLEAN(i == (size_t) config->default_entry)),
}
const char* boot_entry_type_to_string(BootEntryType);
+const char* boot_entry_type_json_to_string(BootEntryType);
BootEntry* boot_config_find_entry(BootConfig *config, const char *id);
return 0;
}
-int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, InstallChange **changes, size_t *n_changes) {
+int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet) {
const char *type, *path, *source;
+ InstallChange *changes = NULL;
+ size_t n_changes = 0;
int r;
- /* changes is dereferenced when calling install_changes_dump() later,
- * so we have to make sure this is not NULL. */
- assert(changes);
- assert(n_changes);
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)");
if (r < 0)
continue;
}
- r = install_changes_add(changes, n_changes, t, path, source);
+ r = install_changes_add(&changes, &n_changes, t, path, source);
if (r < 0)
return r;
}
if (r < 0)
return bus_log_parse_error(r);
- install_changes_dump(0, NULL, *changes, *n_changes, quiet);
+ install_changes_dump(0, NULL, changes, n_changes, quiet);
+
return 0;
}
int bus_append_unit_property_assignment(sd_bus_message *m, UnitType t, const char *assignment);
int bus_append_unit_property_assignment_many(sd_bus_message *m, UnitType t, char **l);
-int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, InstallChange **changes, size_t *n_changes);
+int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet);
int unit_load_state(sd_bus *bus, const char *name, char **load_state);
}
static int condition_test_kernel_command_line(Condition *c, char **env) {
- _cleanup_free_ char *line = NULL;
+ _cleanup_strv_free_ char **args = NULL;
int r;
assert(c);
assert(c->parameter);
assert(c->type == CONDITION_KERNEL_COMMAND_LINE);
- r = proc_cmdline(&line);
+ r = proc_cmdline_strv(&args);
if (r < 0)
return r;
bool equal = strchr(c->parameter, '=');
- for (const char *p = line;;) {
- _cleanup_free_ char *word = NULL;
+ STRV_FOREACH(word, args) {
bool found;
- r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX);
- if (r < 0)
- return r;
- if (r == 0)
- break;
-
if (equal)
- found = streq(word, c->parameter);
+ found = streq(*word, c->parameter);
else {
const char *f;
- f = startswith(word, c->parameter);
+ f = startswith(*word, c->parameter);
found = f && IN_SET(*f, 0, '=');
}
copy_progress_bytes_t progress,
void *userdata) {
+ _cleanup_close_ int fdf_opened = -EBADF, fdt_opened = -EBADF;
bool try_cfr = true, try_sendfile = true, try_splice = true, copied_something = false;
int r, nonblock_pipe = -1;
size_t m = SSIZE_MAX; /* that is the maximum that sendfile and c_f_r accept */
if (ret_remains_size)
*ret_remains_size = 0;
+ fdf = fd_reopen_condition(fdf, O_CLOEXEC | O_NOCTTY | O_RDONLY, O_PATH, &fdf_opened);
+ if (fdf < 0)
+ return fdf;
+ fdt = fd_reopen_condition(fdt, O_CLOEXEC | O_NOCTTY | O_RDWR, O_PATH, &fdt_opened);
+ if (fdt < 0)
+ return fdt;
+
/* Try btrfs reflinks first. This only works on regular, seekable files, hence let's check the file offsets of
* source and destination first. */
if ((copy_flags & COPY_REFLINK)) {
#include "string-util.h"
#include "utf8.h"
-int allow_listed_char_for_devnode(char c, const char *white) {
+int allow_listed_char_for_devnode(char c, const char *additional) {
return
ascii_isdigit(c) ||
ascii_isalpha(c) ||
strchr("#+-.:=@_", c) ||
- (white && strchr(white, c));
+ (additional && strchr(additional, c));
}
int encode_devnode_name(const char *str, char *str_enc, size_t len) {
* because extension images are supposed to extend /usr/, so you get into recursive races, especially
* with directory-based extensions, as the kernel's OverlayFS explicitly checks for this and errors
* out with -ELOOP if it finds that a lowerdir= is a child of another lowerdir=. */
- [IMAGE_EXTENSION] = "/etc/extensions\0" /* only place symlinks here */
- "/run/extensions\0" /* and here too */
- "/var/lib/extensions\0", /* the main place for images */
+ [IMAGE_SYSEXT] = "/etc/extensions\0" /* only place symlinks here */
+ "/run/extensions\0" /* and here too */
+ "/var/lib/extensions\0", /* the main place for images */
+
+ [IMAGE_CONFEXT] = "/run/confexts\0" /* only place symlinks here */
+ "/var/lib/confexts\0" /* the main place for images */
+ "/usr/local/lib/confexts\0"
+ "/usr/lib/confexts\0",
};
/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext in extension
_cleanup_free_ char *hostname = NULL;
_cleanup_free_ char *path = NULL;
- if (i->class == IMAGE_EXTENSION) {
+ if (i->class == IMAGE_SYSEXT) {
r = extension_has_forbidden_content(i->path);
if (r < 0)
return r;
if (r < 0)
log_debug_errno(r, "Failed to read os-release in image, ignoring: %m");
- r = load_extension_release_pairs(i->path, i->name, /* relax_extension_release_check= */ false, &extension_release);
+ r = load_extension_release_pairs(i->path, i->class, i->name, /* relax_extension_release_check= */ false, &extension_release);
if (r < 0)
log_debug_errno(r, "Failed to read extension-release in image, ignoring: %m");
};
DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);
-
-static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
- [IMAGE_MACHINE] = "machine",
- [IMAGE_PORTABLE] = "portable",
- [IMAGE_EXTENSION] = "extension",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
#include "image-policy.h"
#include "lock-util.h"
#include "macro.h"
+#include "os-util.h"
#include "path-util.h"
#include "string-util.h"
#include "time-util.h"
-typedef enum ImageClass {
- IMAGE_MACHINE,
- IMAGE_PORTABLE,
- IMAGE_EXTENSION,
- _IMAGE_CLASS_MAX,
- _IMAGE_CLASS_INVALID = -EINVAL,
-} ImageClass;
-
typedef enum ImageType {
IMAGE_DIRECTORY,
IMAGE_SUBVOLUME,
const char* image_type_to_string(ImageType t) _const_;
ImageType image_type_from_string(const char *s) _pure_;
-const char* image_class_to_string(ImageClass cl) _const_;
-ImageClass image_class_from_string(const char *s) _pure_;
-
int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local);
int image_name_lock(const char *name, int operation, LockFile *ret);
if (r < 0)
return r;
if (r == 0) {
- r = path_is_extension_tree(where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_SYSEXT_CHECK));
+ r = path_is_extension_tree(IMAGE_SYSEXT, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_SYSEXT_CHECK));
if (r < 0)
return r;
if (r > 0)
* we allow a fallback that matches on the first extension-release
* file found in the directory, if one named after the image cannot
* be found first. */
- r = open_extension_release(t, m->image_name, /* relax_extension_release_check= */ false, NULL, &fd);
+ r = open_extension_release(t, IMAGE_SYSEXT, m->image_name, /* relax_extension_release_check= */ false, NULL, &fd);
if (r < 0)
fd = r; /* Propagate the error. */
break;
assert(!isempty(required_host_os_release_id));
- r = load_extension_release_pairs(dest, dissected_image->image_name, relax_extension_release_check, &extension_release);
+ r = load_extension_release_pairs(dest, IMAGE_SYSEXT, dissected_image->image_name, relax_extension_release_check, &extension_release);
if (r < 0)
return log_debug_errno(r, "Failed to parse image %s extension-release metadata: %m", dissected_image->image_name);
required_host_os_release_version_id,
required_host_os_release_sysext_level,
required_sysext_scope,
- extension_release);
+ extension_release,
+ IMAGE_SYSEXT);
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", dissected_image->image_name);
if (r < 0)
#include "mkdir-label.h"
#include "path-util.h"
#include "process-util.h"
-#include "selinux-util.h"
-#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
-#include "tmpfile-util.h"
+#include "tmpfile-util-label.h"
void edit_file_context_done(EditFileContext *context) {
int r;
static int create_edit_temp_file(EditFile *e) {
_cleanup_(unlink_and_freep) char *temp = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *source;
+ bool has_original, has_target;
unsigned line = 1;
int r;
if (e->temp)
return 0;
- r = tempfn_random(e->path, NULL, &temp);
+ r = mkdir_parents_label(e->path, 0755);
if (r < 0)
- return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", e->path);
+ return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path);
- r = mkdir_parents_label(e->path, 0755);
+ r = fopen_temporary_label(e->path, e->path, &f, &temp);
if (r < 0)
- return log_error_errno(r, "Failed to create parent directories for \"%s\": %m", e->path);
+ return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path);
- if (!e->original_path && !e->comment_paths) {
- r = mac_selinux_create_file_prepare(e->path, S_IFREG);
- if (r < 0)
- return r;
+ if (fchmod(fileno(f), 0644) < 0)
+ return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp);
- r = touch(temp);
- mac_selinux_create_file_clear();
- if (r < 0)
- return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp);
- }
+ has_original = e->original_path && access(e->original_path, F_OK) >= 0;
+ has_target = access(e->path, F_OK) >= 0;
- if (e->original_path) {
- r = mac_selinux_create_file_prepare(e->path, S_IFREG);
- if (r < 0)
- return r;
-
- r = copy_file(e->original_path, temp, 0, 0644, COPY_REFLINK);
- if (r == -ENOENT) {
- r = touch(temp);
- mac_selinux_create_file_clear();
- if (r < 0)
- return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp);
- } else {
- mac_selinux_create_file_clear();
- if (r < 0)
- return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", e->path);
- }
- }
+ if (has_original && (!has_target || e->context->overwrite_with_origin))
+ /* We are asked to overwrite target with original_path or target doesn't exist. */
+ source = e->original_path;
+ else if (has_target)
+ /* Target exists and shouldn't be overwritten. */
+ source = e->path;
+ else
+ source = NULL;
if (e->comment_paths) {
- _cleanup_free_ char *target_contents = NULL;
- _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *source_contents = NULL;
- r = mac_selinux_create_file_prepare(e->path, S_IFREG);
- if (r < 0)
- return r;
-
- f = fopen(temp, "we");
- mac_selinux_create_file_clear();
- if (!f)
- return log_error_errno(errno, "Failed to open temporary file \"%s\": %m", temp);
-
- if (fchmod(fileno(f), 0644) < 0)
- return log_error_errno(errno, "Failed to change mode of temporary file \"%s\": %m", temp);
-
- r = read_full_file(e->path, &target_contents, NULL);
- if (r < 0 && r != -ENOENT)
- return log_error_errno(r, "Failed to read target file \"%s\": %m", e->path);
+ if (source) {
+ r = read_full_file(source, &source_contents, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read source file '%s': %m", source);
+ }
fprintf(f,
"### Editing %s\n"
"%s\n",
e->path,
e->context->marker_start,
- strempty(target_contents),
- target_contents && endswith(target_contents, "\n") ? "" : "\n",
+ strempty(source_contents),
+ source_contents && endswith(source_contents, "\n") ? "" : "\n",
e->context->marker_end);
line = 4; /* Start editing at the contents area */
- /* Add a comment with the contents of the original files */
STRV_FOREACH(path, e->comment_paths) {
- _cleanup_free_ char *contents = NULL;
+ _cleanup_free_ char *comment = NULL;
- /* Skip the file that's being edited, already processed in above */
- if (path_equal(*path, e->path))
+ /* Skip the file which is being edited and the source file (can be the same) */
+ if (PATH_IN_SET(*path, e->path, source))
continue;
- r = read_full_file(*path, &contents, NULL);
+ r = read_full_file(*path, &comment, NULL);
if (r < 0)
- return log_error_errno(r, "Failed to read original file \"%s\": %m", *path);
+ return log_error_errno(r, "Failed to read comment file '%s': %m", *path);
fprintf(f, "\n\n### %s", *path);
- if (!isempty(contents)) {
- _cleanup_free_ char *commented_contents = NULL;
- commented_contents = strreplace(strstrip(contents), "\n", "\n# ");
- if (!commented_contents)
+ if (!isempty(comment)) {
+ _cleanup_free_ char *c = NULL;
+
+ c = strreplace(strstrip(comment), "\n", "\n# ");
+ if (!c)
return log_oom();
- fprintf(f, "\n# %s", commented_contents);
+ fprintf(f, "\n# %s", c);
}
}
-
- r = fflush_and_check(f);
- if (r < 0)
- return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp);
+ } else if (source) {
+ r = copy_file_fd(source, fileno(f), COPY_REFLINK);
+ if (r < 0) {
+ assert(r != -ENOENT);
+ return log_error_errno(r, "Failed to copy file '%s' to temporary file '%s': %m", source, temp);
+ }
}
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write to temporary file '%s': %m", temp);
+
e->temp = TAKE_PTR(temp);
e->line = line;
r = read_full_file(e->temp, &old_contents, NULL);
if (r < 0)
- return log_error_errno(r, "Failed to read temporary file \"%s\": %m", e->temp);
+ return log_error_errno(r, "Failed to read temporary file '%s': %m", e->temp);
if (e->context->marker_start) {
/* Trim out the lines between the two markers */
r = write_string_file(e->temp, new_contents, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE);
if (r < 0)
- return log_error_errno(r, "Failed to modify temporary file \"%s\": %m", e->temp);
+ return log_error_errno(r, "Failed to strip temporary file '%s': %m", e->temp);
return 1; /* Contents have real changes and are changed after stripping */
}
r = RET_NERRNO(rename(i->temp, i->path));
if (r < 0)
- return log_error_errno(r, "Failed to rename \"%s\" to \"%s\": %m", i->temp, i->path);
+ return log_error_errno(r,
+ "Failed to rename temporary file '%s' to target file '%s': %m",
+ i->temp,
+ i->path);
i->temp = mfree(i->temp);
log_info("Successfully installed edited file '%s'.", i->path);
#include <stdbool.h>
+#define DROPIN_MARKER_START "### Anything between here and the comment below will become the contents of the drop-in file"
+#define DROPIN_MARKER_END "### Edits below this comment will be discarded"
+
typedef struct EditFile EditFile;
typedef struct EditFileContext EditFileContext;
const char *marker_start;
const char *marker_end;
bool remove_parent;
+ bool overwrite_with_origin; /* whether to always overwrite target with original file */
};
void edit_file_context_done(EditFileContext *context);
const char *name,
const char *host_os_release_id,
const char *host_os_release_version_id,
- const char *host_os_release_sysext_level,
- const char *host_sysext_scope,
- char **extension_release) {
+ const char *host_os_extension_release_level,
+ const char *host_extension_scope,
+ char **extension_release,
+ ImageClass image_class) {
- const char *extension_release_id = NULL, *extension_release_sysext_level = NULL, *extension_architecture = NULL;
+ const char *extension_release_id = NULL, *extension_release_level = NULL, *extension_architecture = NULL;
+ const char *extension_level = image_class == IMAGE_CONFEXT ? "CONFEXT_LEVEL" : "SYSEXT_LEVEL";
+ const char *extension_scope = image_class == IMAGE_CONFEXT ? "CONFEXT_SCOPE" : "SYSEXT_SCOPE";
assert(name);
assert(!isempty(host_os_release_id));
- /* Now that we can look into the extension image, let's see if the OS version is compatible */
+ /* Now that we can look into the extension/confext image, let's see if the OS version is compatible */
if (strv_isempty(extension_release)) {
- log_debug("Extension '%s' carries no extension-release data, ignoring extension.", name);
+ log_debug("Extension '%s' carries no release data, ignoring.", name);
return 0;
}
- if (host_sysext_scope) {
- _cleanup_strv_free_ char **extension_sysext_scope_list = NULL;
- const char *extension_sysext_scope;
+ if (host_extension_scope) {
+ _cleanup_strv_free_ char **scope_list = NULL;
+ const char *scope;
bool valid;
- extension_sysext_scope = strv_env_pairs_get(extension_release, "SYSEXT_SCOPE");
- if (extension_sysext_scope) {
- extension_sysext_scope_list = strv_split(extension_sysext_scope, WHITESPACE);
- if (!extension_sysext_scope_list)
+ scope = strv_env_pairs_get(extension_release, extension_scope);
+ if (scope) {
+ scope_list = strv_split(scope, WHITESPACE);
+ if (!scope_list)
return -ENOMEM;
}
- /* by default extension are good for attachment in portable service and on the system */
+ /* By default extension are good for attachment in portable service and on the system */
valid = strv_contains(
- extension_sysext_scope_list ?: STRV_MAKE("system", "portable"),
- host_sysext_scope);
+ scope_list ?: STRV_MAKE("system", "portable"),
+ host_extension_scope);
if (!valid) {
- log_debug("Extension '%s' is not suitable for scope %s, ignoring extension.", name, host_sysext_scope);
+ log_debug("Extension '%s' is not suitable for scope %s, ignoring.", name, host_extension_scope);
return 0;
}
}
* the future we could check if the kernel also supports 32 bit or binfmt has a translator set up for the architecture */
extension_architecture = strv_env_pairs_get(extension_release, "ARCHITECTURE");
if (!isempty(extension_architecture) && !streq(extension_architecture, "_any") &&
- !streq(architecture_to_string(uname_architecture()), extension_architecture)) {
+ !streq(architecture_to_string(uname_architecture()), extension_architecture)) {
log_debug("Extension '%s' is for architecture '%s', but deployed on top of '%s'.",
- name, extension_architecture, architecture_to_string(uname_architecture()));
+ name, extension_architecture, architecture_to_string(uname_architecture()));
return 0;
}
extension_release_id = strv_env_pairs_get(extension_release, "ID");
if (isempty(extension_release_id)) {
- log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s' or be '_any'",
- name, host_os_release_id);
+ log_debug("Extension '%s' does not contain ID in release file but requested to match '%s' or be '_any'",
+ name, host_os_release_id);
return 0;
}
- /* A sysext with no host OS dependency (static binaries or scripts) can match
- * '_any' host OS, and VERSION_ID or SYSEXT_LEVEL are not required anywhere */
+ /* A sysext(or confext) with no host OS dependency (static binaries or scripts) can match
+ * '_any' host OS, and VERSION_ID or SYSEXT_LEVEL(or CONFEXT_LEVEL) are not required anywhere */
if (streq(extension_release_id, "_any")) {
log_debug("Extension '%s' matches '_any' OS.", name);
return 1;
}
/* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
- if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
+ if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) {
log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
return 1;
}
/* If the extension has a sysext API level declared, then it must match the host API
* level. Otherwise, compare OS version as a whole */
- extension_release_sysext_level = strv_env_pairs_get(extension_release, "SYSEXT_LEVEL");
- if (!isempty(host_os_release_sysext_level) && !isempty(extension_release_sysext_level)) {
- if (!streq_ptr(host_os_release_sysext_level, extension_release_sysext_level)) {
- log_debug("Extension '%s' is for sysext API level '%s', but running on sysext API level '%s'",
- name, strna(extension_release_sysext_level), strna(host_os_release_sysext_level));
+ extension_release_level = strv_env_pairs_get(extension_release, extension_level);
+ if (!isempty(host_os_extension_release_level) && !isempty(extension_release_level)) {
+ if (!streq_ptr(host_os_extension_release_level, extension_release_level)) {
+ log_debug("Extension '%s' is for API level '%s', but running on API level '%s'",
+ name, strna(extension_release_level), strna(host_os_extension_release_level));
return 0;
}
} else if (!isempty(host_os_release_version_id)) {
extension_release_version_id = strv_env_pairs_get(extension_release, "VERSION_ID");
if (isempty(extension_release_version_id)) {
- log_debug("Extension '%s' does not contain VERSION_ID in extension-release but requested to match '%s'",
+ log_debug("Extension '%s' does not contain VERSION_ID in release file but requested to match '%s'",
name, strna(host_os_release_version_id));
return 0;
}
name, strna(extension_release_version_id), strna(host_os_release_version_id));
return 0;
}
- } else if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
+ } else if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) {
/* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
return 1;
return 1;
}
-int parse_env_extension_hierarchies(char ***ret_hierarchies) {
+int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarchy_env) {
_cleanup_free_ char **l = NULL;
int r;
- r = getenv_path_list("SYSTEMD_SYSEXT_HIERARCHIES", &l);
+ assert(hierarchy_env);
+ r = getenv_path_list(hierarchy_env, &l);
if (r == -ENXIO) {
- /* Default when unset */
- l = strv_new("/usr", "/opt");
- if (!l)
- return -ENOMEM;
+ if (streq(hierarchy_env, "SYSTEMD_CONFEXT_HIERARCHIES"))
+ /* Default for confext when unset */
+ l = strv_new("/etc");
+ else if (streq(hierarchy_env, "SYSTEMD_SYSEXT_HIERARCHIES"))
+ /* Default for sysext when unset */
+ l = strv_new("/usr", "/opt");
+ else
+ return -ENXIO;
} else if (r < 0)
return r;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include "os-util.h"
+
/* Given an image name (for logging purposes), a set of os-release values from the host and a key-value pair
* vector of extension-release variables, check that the distro and (system extension level or distro
* version) match and return 1, and 0 otherwise. */
const char *name,
const char *host_os_release_id,
const char *host_os_release_version_id,
- const char *host_os_release_sysext_level,
- const char *host_sysext_scope,
- char **extension_release);
+ const char *host_os_extension_release_level,
+ const char *host_extension_scope,
+ char **extension_release,
+ ImageClass image_class);
-/* Parse SYSTEMD_SYSEXT_HIERARCHIES and if not set, return "/usr /opt" */
-int parse_env_extension_hierarchies(char ***ret_hierarchies);
+/* Parse hierarchy variables and if not set, return "/usr /opt" for sysext and "/etc" for confext */
+int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarchy_env);
/* Insist that extension images do not overwrite the underlying OS release file (it's fine if they place one
* in /etc/os-release, i.e. where things don't matter, as they aren't merged.) */
return r;
if (ret_path) {
- char *q = path_join(empty_to_root(root), p);
- if (!q)
- return -ENOMEM;
-
- *ret_path = TAKE_PTR(q);
+ r = path_prefix_root_cwd(p, root, ret_path);
+ if (r < 0)
+ return r;
}
if (ret_part)
*ret_part = part;
return r;
if (ret_path) {
- char *q = path_join(empty_to_root(root), p);
- if (!q)
- return -ENOMEM;
-
- *ret_path = TAKE_PTR(q);
+ r = path_prefix_root_cwd(p, root, ret_path);
+ if (r < 0)
+ return r;
}
if (ret_uuid)
*ret_uuid = uuid;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "fd-util.h"
+#include "fileio.h"
#include "env-file.h"
#include "kernel-image.h"
#include "os-util.h"
}
int inspect_kernel(
+ int dir_fd,
const char *filename,
KernelImageType *ret_type,
char **ret_cmdline,
KernelImageType t;
int r;
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(filename);
- f = fopen(filename, "re");
- if (!f)
- return log_error_errno(errno, "Failed to open kernel image file '%s': %m", filename);
+ r = xfopenat(dir_fd, filename, "re", 0, &f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open kernel image file '%s': %m", filename);
r = pe_sections(f, §ions, &scount);
if (r < 0)
const char* kernel_image_type_to_string(KernelImageType t) _const_;
int inspect_kernel(
+ int dir_fd,
const char *filename,
KernelImageType *ret_type,
char **ret_cmdline,
if (extra_mkfs_args && strv_extend_strv(&argv, extra_mkfs_args, false) < 0)
return log_oom();
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *j = NULL;
+
+ j = strv_join(argv, " ");
+ log_debug("Executing mkfs command: %s", strna(j));
+ }
+
r = safe_fork_full(
"(mkfs)",
stdio_fds,
#include "exec-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "getopt-defs.h"
#include "initrd-util.h"
#include "killall.h"
#include "log.h"
static int parse_argv(int argc, char *argv[]) {
enum {
- ARG_LOG_LEVEL = 0x100,
- ARG_LOG_TARGET,
- ARG_LOG_COLOR,
- ARG_LOG_LOCATION,
- ARG_LOG_TIME,
- ARG_EXIT_CODE,
- ARG_TIMEOUT,
+ COMMON_GETOPT_ARGS,
+ SHUTDOWN_GETOPT_ARGS,
};
static const struct option options[] = {
- { "log-level", required_argument, NULL, ARG_LOG_LEVEL },
- { "log-target", required_argument, NULL, ARG_LOG_TARGET },
- { "log-color", optional_argument, NULL, ARG_LOG_COLOR },
- { "log-location", optional_argument, NULL, ARG_LOG_LOCATION },
- { "log-time", optional_argument, NULL, ARG_LOG_TIME },
- { "exit-code", required_argument, NULL, ARG_EXIT_CODE },
- { "timeout", required_argument, NULL, ARG_TIMEOUT },
+ COMMON_GETOPT_OPTIONS,
+ SHUTDOWN_GETOPT_OPTIONS,
{}
};
assert(argc >= 1);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
+
/* "-" prevents getopt from permuting argv[] and moving the verb away
* from argv[1]. Our interface to initrd promises it'll be there. */
while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0)
# SPDX-License-Identifier: LGPL-2.1-or-later
systemd_sysext_sources = files('sysext.c')
+
+meson.add_install_script(meson_make_symlink,
+ rootbindir / 'systemd-sysext',
+ rootbindir / 'systemd-confext')
#include "user-util.h"
#include "verbs.h"
-static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default */
+static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
static char *arg_root = NULL;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static bool arg_force = false;
static ImagePolicy *arg_image_policy = NULL;
+/* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
+static ImageClass arg_image_class = IMAGE_SYSEXT;
+
STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
+/* Helper struct for naming simplicity and reusability */
+static const struct {
+ const char *dot_directory_name;
+ const char *directory_name;
+ const char *short_identifier;
+ const char *short_identifier_plural;
+ const char *level_env;
+ const char *scope_env;
+ const char *name_env;
+} image_class_info[_IMAGE_CLASS_MAX] = {
+ [IMAGE_SYSEXT] = {
+ .dot_directory_name = ".systemd-sysext",
+ .directory_name = "systemd-sysext",
+ .short_identifier = "sysext",
+ .short_identifier_plural = "extensions",
+ .level_env = "SYSEXT_LEVEL",
+ .scope_env = "SYSEXT_SCOPE",
+ .name_env = "SYSTEMD_SYSEXT_HIERARCHIES",
+ },
+ [IMAGE_CONFEXT] = {
+ .dot_directory_name = ".systemd-confext",
+ .directory_name = "systemd-confext",
+ .short_identifier = "confext",
+ .short_identifier_plural = "confexts",
+ .level_env = "CONFEXT_LEVEL",
+ .scope_env = "CONFEXT_SCOPE",
+ .name_env = "SYSTEMD_CONFEXT_HIERARCHIES",
+ }
+};
+
static int is_our_mount_point(const char *p) {
_cleanup_free_ char *buf = NULL, *f = NULL;
struct stat st;
/* So we know now that it's a mount point. Now let's check if it's one of ours, so that we don't
* accidentally unmount the user's own /usr/ but just the mounts we established ourselves. We do this
* check by looking into the metadata directory we place in merged mounts: if the file
- * .systemd-sysext/dev contains the major/minor device pair of the mount we have a good reason to
+ * ../dev contains the major/minor device pair of the mount we have a good reason to
* believe this is one of our mounts. This thorough check has the benefit that we aren't easily
* confused if people tar up one of our merged trees and untar them elsewhere where we might mistake
* them for a live sysext tree. */
- f = path_join(p, ".systemd-sysext/dev");
+ f = path_join(p, image_class_info[arg_image_class].dot_directory_name, "dev");
if (!f)
return log_oom();
r = read_one_line_file(f, &buf);
if (r == -ENOENT) {
- log_debug("Hierarchy '%s' does not carry a .systemd-sysext/dev file, not a sysext merged tree.", p);
+ log_debug("Hierarchy '%s' does not carry a %s/dev file, not a merged tree.", p, image_class_info[arg_image_class].dot_directory_name);
return false;
}
if (r < 0)
- return log_error_errno(r, "Failed to determine whether hierarchy '%s' contains '.systemd-sysext/dev': %m", p);
+ return log_error_errno(r, "Failed to determine whether hierarchy '%s' contains '%s/dev': %m", p, image_class_info[arg_image_class].dot_directory_name);
r = parse_devnum(buf, &dev);
if (r < 0)
- return log_error_errno(r, "Failed to parse device major/minor stored in '.systemd-sysext/dev' file on '%s': %m", p);
+ return log_error_errno(r, "Failed to parse device major/minor stored in '%s/dev' file on '%s': %m", image_class_info[arg_image_class].dot_directory_name, p);
if (lstat(p, &st) < 0)
return log_error_errno(r, "Failed to stat %s: %m", p);
continue;
}
- f = path_join(*p, ".systemd-sysext/extensions");
+ f = path_join(*p, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural);
if (!f)
return log_oom();
}
/* Now mount the actual overlayfs */
- r = mount_nofollow_verbose(LOG_ERR, "sysext", where, "overlay", MS_RDONLY, options);
+ r = mount_nofollow_verbose(LOG_ERR, image_class_info[arg_image_class].short_identifier, where, "overlay", MS_RDONLY, options);
if (r < 0)
return r;
/* Let's generate a metadata file that lists all extensions we took into account for this
* hierarchy. We include this in the final fs, to make things nicely discoverable and
* recognizable. */
- f = path_join(meta_path, ".systemd-sysext/extensions");
+ f = path_join(meta_path, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural);
if (!f)
return log_oom();
/* Now we have mounted the new file system. Let's now figure out its .st_dev field, and make that
* available in the metadata directory. This is useful to detect whether the metadata dir actually
* belongs to the fs it is found on: if .st_dev of the top-level mount matches it, it's pretty likely
- * we are looking at a live sysext tree, and not an unpacked tar or so of one. */
+ * we are looking at a live tree, and not an unpacked tar or so of one. */
if (stat(overlay_path, &st) < 0)
return log_error_errno(r, "Failed to stat mount '%s': %m", overlay_path);
free(f);
- f = path_join(meta_path, ".systemd-sysext/dev");
+ f = path_join(meta_path, image_class_info[arg_image_class].dot_directory_name, "dev");
if (!f)
return log_oom();
static int merge_subprocess(Hashmap *images, const char *workspace) {
_cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_sysext_level = NULL,
- *buf = NULL;
+ *host_os_release_confext_level = NULL, *buf = NULL;
_cleanup_strv_free_ char **extensions = NULL, **paths = NULL;
size_t n_extensions = 0;
unsigned n_ignored = 0;
return r;
/* Acquire host OS release info, so that we can compare it with the extension's data */
+ char **host_os_release_level = (arg_image_class == IMAGE_CONFEXT) ? &host_os_release_confext_level : &host_os_release_sysext_level;
r = parse_os_release(
arg_root,
"ID", &host_os_release_id,
"VERSION_ID", &host_os_release_version_id,
- "SYSEXT_LEVEL", &host_os_release_sysext_level);
+ image_class_info[arg_image_class].level_env,
+ host_os_release_level);
if (r < 0)
return log_error_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(arg_root));
if (isempty(host_os_release_id))
HASHMAP_FOREACH(img, images) {
_cleanup_free_ char *p = NULL;
- p = path_join(workspace, "extensions", img->name);
+ p = path_join(workspace, image_class_info[arg_image_class].short_identifier_plural, img->name);
if (!p)
return log_oom();
host_os_release_version_id,
host_os_release_sysext_level,
in_initrd() ? "initrd" : "system",
- img->extension_release);
+ img->extension_release,
+ arg_image_class);
if (r < 0)
return r;
if (r == 0) {
assert_se(img = hashmap_get(images, extensions[n_extensions - 1 - k]));
- p = path_join(workspace, "extensions", img->name);
+ p = path_join(workspace, image_class_info[arg_image_class].short_identifier_plural, img->name);
if (!p)
return log_oom();
pid_t pid;
int r;
- r = safe_fork("(sd-sysext)", FORK_DEATHSIG|FORK_LOG|FORK_NEW_MOUNTNS, &pid);
+ r = safe_fork("(sd-merge)", FORK_DEATHSIG|FORK_LOG|FORK_NEW_MOUNTNS, &pid);
if (r < 0)
return log_error_errno(r, "Failed to fork off child: %m");
if (r == 0) {
_exit(r > 0 ? EXIT_SUCCESS : 123); /* 123 means: didn't find any extensions */
}
- r = wait_for_terminate_and_check("(sd-sysext)", pid, WAIT_LOG_ABNORMAL);
+ r = wait_for_terminate_and_check("(sd-merge)", pid, WAIT_LOG_ABNORMAL);
if (r < 0)
return r;
if (!images)
return log_oom();
- r = image_discover(IMAGE_EXTENSION, arg_root, images);
+ r = image_discover(arg_image_class, arg_root, images);
if (r < 0)
- return log_error_errno(r, "Failed to discover extension images: %m");
+ return log_error_errno(r, "Failed to discover images: %m");
HASHMAP_FOREACH(img, images) {
r = image_read_metadata(img, &image_policy_sysext);
if (!images)
return log_oom();
- r = image_discover(IMAGE_EXTENSION, arg_root, images);
+ r = image_discover(arg_image_class, arg_root, images);
if (r < 0)
- return log_error_errno(r, "Failed to discover extension images: %m");
+ return log_error_errno(r, "Failed to discover images: %m");
if ((arg_json_format_flags & JSON_FORMAT_OFF) && hashmap_isempty(images)) {
log_info("No OS extensions found.");
return log_oom();
printf("%1$s [OPTIONS...] COMMAND\n"
- "\n%5$sMerge extension images into /usr/ and /opt/ hierarchies.%6$s\n"
- "\n%3$sCommands:%4$s\n"
+ "\n%5$sMerge extension images into /usr/ and /opt/ hierarchies for\n"
+ " sysext and into the /etc/ hierarchy for confext.%6$s\n"
" status Show current merge status (default)\n"
- " merge Merge extensions into /usr/ and /opt/\n"
- " unmerge Unmerge extensions from /usr/ and /opt/\n"
+ " merge Merge extensions into relevant hierarchies\n"
+ " unmerge Unmerge extensions from relevant hierarchies\n"
" refresh Unmerge/merge extensions again\n"
" list List installed extensions\n"
" -h --help Show this help\n"
static int run(int argc, char *argv[]) {
int r;
+ arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT;
log_setup();
/* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and
* /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline
* switch. */
- r = parse_env_extension_hierarchies(&arg_hierarchies);
+ r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env);
if (r < 0)
- return log_error_errno(r, "Failed to parse $SYSTEMD_SYSEXT_HIERARCHIES environment variable: %m");
+ return log_error_errno(r, "Failed to parse environment variable: %m");
return sysext_main(argc, argv);
}
_cleanup_strv_free_ char **names = NULL;
_cleanup_free_ char *target = NULL;
const char *verb = argv[0];
- InstallChange *changes = NULL;
- size_t n_changes = 0;
UnitDependency dep;
int r;
assert_not_reached();
if (install_client_side()) {
+ InstallChange *changes = NULL;
+ size_t n_changes = 0;
+
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
r = unit_file_add_dependency(arg_runtime_scope, unit_file_flags_from_args(), arg_root, names, target, dep, &changes, &n_changes);
install_changes_dump(r, "add dependency on", changes, n_changes, arg_quiet);
-
- if (r > 0)
- r = 0;
+ if (r < 0)
+ return r;
} else {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
if (r < 0)
return log_error_errno(r, "Failed to add dependency: %s", bus_error_message(&error, r));
- r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+ r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
- if (arg_no_reload) {
- r = 0;
- goto finish;
+ if (!arg_no_reload) {
+ r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
+ if (r < 0)
+ return r;
}
-
- r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
- if (r > 0)
- r = 0;
}
-finish:
- install_changes_free(changes, n_changes);
-
- return r;
+ return 0;
}
#include "systemctl.h"
#include "terminal-util.h"
-#define EDIT_MARKER_START "### Anything between here and the comment below will become the contents of the drop-in file"
-#define EDIT_MARKER_END "### Edits below this comment will be discarded"
-
int verb_cat(int argc, char *argv[], void *userdata) {
_cleanup_(hashmap_freep) Hashmap *cached_name_map = NULL, *cached_id_map = NULL;
_cleanup_(lookup_paths_free) LookupPaths lp = {};
int verb_edit(int argc, char *argv[], void *userdata) {
_cleanup_(edit_file_context_done) EditFileContext context = {
- .marker_start = EDIT_MARKER_START,
- .marker_end = EDIT_MARKER_END,
+ .marker_start = DROPIN_MARKER_START,
+ .marker_end = DROPIN_MARKER_END,
.remove_parent = !arg_full,
+ .overwrite_with_origin = true,
};
_cleanup_(lookup_paths_free) LookupPaths lp = {};
_cleanup_strv_free_ char **names = NULL;
int verb_enable(int argc, char *argv[], void *userdata) {
_cleanup_strv_free_ char **names = NULL;
const char *verb = argv[0];
- InstallChange *changes = NULL;
- size_t n_changes = 0;
int carries_install_info = -1;
bool ignore_carries_install_info = arg_quiet || arg_no_warn;
int r;
if (install_client_side()) {
UnitFileFlags flags;
+ InstallChange *changes = NULL;
+ size_t n_changes = 0;
+
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
flags = unit_file_flags_from_args();
if (streq(verb, "enable")) {
install_changes_dump(r, verb, changes, n_changes, arg_quiet);
if (r < 0)
- goto finish;
- r = 0;
+ return r;
} else {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
return bus_log_parse_error(r);
}
- r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+ r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
/* Try to reload if enabled */
if (!arg_no_reload) {
r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
- if (r > 0)
- r = 0;
- } else
- r = 0;
+ if (r < 0)
+ return r;
+ }
}
if (carries_install_info == 0 && !ignore_carries_install_info)
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
- goto finish;
+ return r;
len = strv_length(names);
{
}
}
-finish:
- install_changes_free(changes, n_changes);
-
- return r;
+ return 0;
}
#include "systemctl.h"
int verb_preset_all(int argc, char *argv[], void *userdata) {
- InstallChange *changes = NULL;
- size_t n_changes = 0;
int r;
if (install_client_side()) {
+ InstallChange *changes = NULL;
+ size_t n_changes = 0;
+
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
r = unit_file_preset_all(arg_runtime_scope, unit_file_flags_from_args(), arg_root, arg_preset_mode, &changes, &n_changes);
install_changes_dump(r, "preset", changes, n_changes, arg_quiet);
if (r < 0)
return log_error_errno(r, "Failed to preset all units: %s", bus_error_message(&error, r));
- r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+ r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
- if (arg_no_reload) {
- r = 0;
- goto finish;
+ if (!arg_no_reload) {
+ r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
+ if (r < 0)
+ return r;
}
-
- r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
- if (r > 0)
- r = 0;
}
-finish:
- install_changes_free(changes, n_changes);
-
- return r;
+ return 0;
}
int verb_set_default(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *unit = NULL;
- InstallChange *changes = NULL;
- size_t n_changes = 0;
int r;
assert(argc >= 2);
return log_error_errno(r, "Failed to mangle unit name: %m");
if (install_client_side()) {
+ InstallChange *changes = NULL;
+ size_t n_changes = 0;
+
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
r = unit_file_set_default(arg_runtime_scope, UNIT_FILE_FORCE, arg_root, unit, &changes, &n_changes);
install_changes_dump(r, "set default", changes, n_changes, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
} else {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
if (r < 0)
return log_error_errno(r, "Failed to set default target: %s", bus_error_message(&error, r));
- r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+ r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
/* Try to reload if enabled */
if (!arg_no_reload) {
r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
if (r < 0)
- goto finish;
+ return r;
}
}
r = determine_default(&final);
if (r < 0)
- goto finish;
+ return r;
if (!streq(final, unit))
log_notice("Note: \"%s\" is the default unit (possibly a runtime override).", final);
}
-finish:
- install_changes_free(changes, n_changes);
-
- return r < 0 ? r : 0;
+ return 0;
}
if (!i->condition_result && i->condition_timestamp > 0) {
int n = 0;
- printf(" Condition: start %scondition failed%s at %s; %s\n",
+ printf(" Condition: start %scondition unmet%s at %s; %s\n",
ansi_highlight_yellow(), ansi_normal(),
FORMAT_TIMESTAMP_STYLE(i->condition_timestamp, arg_timestamp_style),
FORMAT_TIMESTAMP_RELATIVE(i->condition_timestamp));
if (arg_action != ACTION_SYSTEMCTL)
return r;
- log_error_errno(r, "Failed to %s %s: %s", job_type, name, bus_error_message(error, r));
+ if (sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED) &&
+ STR_IN_SET(method, "TryRestartUnit", "ReloadOrTryRestartUnit")) {
+ /* Ignore masked unit if try-* is requested */
+
+ log_debug_errno(r, "Failed to %s %s, ignoring: %s", job_type, name, bus_error_message(error, r));
+ return 0;
+ } else
+ log_error_errno(r, "Failed to %s %s: %s", job_type, name, bus_error_message(error, r));
if (!sd_bus_error_has_names(error, BUS_ERROR_NO_SUCH_UNIT,
BUS_ERROR_UNIT_MASKED,
if (arg_marked)
ret = enqueue_marked_jobs(bus, w);
-
else
STRV_FOREACH(name, names) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = compress(text, size, buf, size, &j);
/* assume compression must be successful except for small or random inputs */
- assert_se(r > 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random"));
+ assert_se(r >= 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random"));
/* check for overwrites */
assert_se(buf[size] == 0);
#if HAVE_COMPRESSION
_unused_ static void test_compress_decompress(
- int flag,
const char *compression,
compress_blob_t compress,
decompress_blob_t decompress,
log_info_errno(r, "compression failed: %m");
assert_se(may_fail);
} else {
- assert_se(r == flag);
+ assert_se(r >= 0);
r = decompress(compressed, csize,
(void **) &decompressed, &csize, 0);
assert_se(r == 0);
assert_se(compressed2);
r = compress(data, data_len, compressed, BUFSIZE_2, &csize);
}
- assert_se(r > 0);
+ assert_se(r >= 0);
len = strlen(data);
log_info("/* %s with %s */", __func__, compression);
r = compress(TEXT, sizeof TEXT, buf, sizeof buf, &csize);
- assert_se(r > 0);
+ assert_se(r >= 0);
for (size_t i = 1; i < strlen(TEXT); i++) {
_cleanup_free_ void *buf2 = NULL;
}
}
-_unused_ static void test_compress_stream(int flag,
- const char *compression,
+_unused_ static void test_compress_stream(const char *compression,
const char *cat,
compress_stream_t compress,
decompress_stream_t decompress,
assert_se((dst = mkostemp_safe(pattern)) >= 0);
- assert_se(compress(src, dst, -1, &uncompressed_size) == flag);
+ assert_se(compress(src, dst, -1, &uncompressed_size) >= 0);
if (cat) {
assert_se(asprintf(&cmd, "%s %s | diff %s -", cat, pattern, srcfile) > 0);
random_bytes(data + 7, sizeof(data) - 7);
#if HAVE_XZ
- test_compress_decompress(COMPRESSION_XZ, "XZ",
- compress_blob_xz, decompress_blob_xz,
+ test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz,
text, sizeof(text), false);
- test_compress_decompress(COMPRESSION_XZ, "XZ",
- compress_blob_xz, decompress_blob_xz,
+ test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz,
data, sizeof(data), true);
test_decompress_startswith("XZ",
compress_blob_xz, decompress_startswith_xz,
huge, HUGE_SIZE, true);
- test_compress_stream(COMPRESSION_XZ, "XZ", "xzcat",
+ test_compress_stream("XZ", "xzcat",
compress_stream_xz, decompress_stream_xz, srcfile);
test_decompress_startswith_short("XZ", compress_blob_xz, decompress_startswith_xz);
#endif
#if HAVE_LZ4
- test_compress_decompress(COMPRESSION_LZ4, "LZ4",
- compress_blob_lz4, decompress_blob_lz4,
+ test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4,
text, sizeof(text), false);
- test_compress_decompress(COMPRESSION_LZ4, "LZ4",
- compress_blob_lz4, decompress_blob_lz4,
+ test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4,
data, sizeof(data), true);
test_decompress_startswith("LZ4",
compress_blob_lz4, decompress_startswith_lz4,
huge, HUGE_SIZE, true);
- test_compress_stream(COMPRESSION_LZ4, "LZ4", "lz4cat",
+ test_compress_stream("LZ4", "lz4cat",
compress_stream_lz4, decompress_stream_lz4, srcfile);
test_lz4_decompress_partial();
#endif
#if HAVE_ZSTD
- test_compress_decompress(COMPRESSION_ZSTD, "ZSTD",
- compress_blob_zstd, decompress_blob_zstd,
+ test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd,
text, sizeof(text), false);
- test_compress_decompress(COMPRESSION_ZSTD, "ZSTD",
- compress_blob_zstd, decompress_blob_zstd,
+ test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd,
data, sizeof(data), true);
test_decompress_startswith("ZSTD",
compress_blob_zstd, decompress_startswith_zstd,
huge, HUGE_SIZE, true);
- test_compress_stream(COMPRESSION_ZSTD, "ZSTD", "zstdcat",
+ test_compress_stream("ZSTD", "zstdcat",
compress_stream_zstd, decompress_stream_zstd, srcfile);
test_decompress_startswith_short("ZSTD", compress_blob_zstd, decompress_startswith_zstd);
#include "alloc-util.h"
#include "conf-files.h"
+#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "macro.h"
#include "mkdir.h"
-#include "parse-util.h"
#include "path-util.h"
#include "rm-rf.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
-#include "user-util.h"
+#include "tmpfile-util.h"
-static void setup_test_dir(char *tmp_dir, const char *files, ...) {
- va_list ap;
+TEST(conf_files_list) {
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_close_ int tfd = -EBADF;
+ _cleanup_strv_free_ char **result = NULL;
+ const char *search1, *search2, *search1_a, *search1_b, *search1_c, *search2_aa;
+
+ tfd = mkdtemp_open("/tmp/test-conf-files-XXXXXX", O_PATH, &t);
+ assert(tfd >= 0);
- assert_se(mkdtemp(tmp_dir));
+ assert_se(mkdirat(tfd, "dir1", 0755) >= 0);
+ assert_se(mkdirat(tfd, "dir2", 0755) >= 0);
- va_start(ap, files);
- while (files) {
- _cleanup_free_ char *path;
+ search1 = strjoina(t, "/dir1/");
+ search2 = strjoina(t, "/dir2/");
- assert_se(path = path_join(tmp_dir, files));
- assert_se(write_string_file(path, "foobar", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0);
+ FOREACH_STRING(p, "a.conf", "b.conf", "c.foo") {
+ _cleanup_free_ char *path = NULL;
- files = va_arg(ap, const char *);
+ assert_se(path = path_join(search1, p));
+ assert_se(write_string_file(path, "foobar", WRITE_STRING_FILE_CREATE) >= 0);
}
- va_end(ap);
-}
-static void test_conf_files_list_one(bool use_root) {
- char tmp_dir[] = "/tmp/test-conf-files-XXXXXX";
- _cleanup_strv_free_ char **found_files = NULL, **found_files2 = NULL;
- const char *root_dir, *search, *expect_a, *expect_b, *expect_c, *mask;
+ assert_se(symlinkat("/dev/null", tfd, "dir1/m.conf") >= 0);
+
+ FOREACH_STRING(p, "a.conf", "aa.conf", "m.conf") {
+ _cleanup_free_ char *path = NULL;
- log_info("/* %s(%s) */", __func__, yes_no(use_root));
+ assert_se(path = path_join(search2, p));
+ assert_se(write_string_file(path, "hogehoge", WRITE_STRING_FILE_CREATE) >= 0);
+ }
- setup_test_dir(tmp_dir,
- "/dir/a.conf",
- "/dir/b.conf",
- "/dir/c.foo",
- NULL);
+ search1_a = strjoina(search1, "a.conf");
+ search1_b = strjoina(search1, "b.conf");
+ search1_c = strjoina(search1, "c.foo");
+ search2_aa = strjoina(search2, "aa.conf");
- mask = strjoina(tmp_dir, "/dir/d.conf");
- assert_se(symlink("/dev/null", mask) >= 0);
+ /* search dir1 without suffix */
+ assert_se(conf_files_list(&result, NULL, NULL, CONF_FILES_FILTER_MASKED, search1) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c)));
- if (use_root) {
- root_dir = tmp_dir;
- search = "/dir";
- } else {
- root_dir = NULL;
- search = strjoina(tmp_dir, "/dir");
- }
+ result = strv_free(result);
- expect_a = strjoina(tmp_dir, "/dir/a.conf");
- expect_b = strjoina(tmp_dir, "/dir/b.conf");
- expect_c = strjoina(tmp_dir, "/dir/c.foo");
+ assert_se(conf_files_list(&result, NULL, t, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c)));
- log_debug("/* Check when filtered by suffix */");
+ result = strv_free(result);
- assert_se(conf_files_list(&found_files, ".conf", root_dir, CONF_FILES_FILTER_MASKED, search) == 0);
- strv_print(found_files);
+ assert_se(conf_files_list_at(&result, NULL, AT_FDCWD, CONF_FILES_FILTER_MASKED, search1) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c)));
- assert_se(found_files);
- assert_se(streq_ptr(found_files[0], expect_a));
- assert_se(streq_ptr(found_files[1], expect_b));
- assert_se(!found_files[2]);
+ result = strv_free(result);
- log_debug("/* Check when unfiltered */");
- assert_se(conf_files_list(&found_files2, NULL, root_dir, CONF_FILES_FILTER_MASKED, search) == 0);
- strv_print(found_files2);
+ assert_se(conf_files_list_at(&result, NULL, tfd, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir1/b.conf", "dir1/c.foo")));
- assert_se(found_files2);
- assert_se(streq_ptr(found_files2[0], expect_a));
- assert_se(streq_ptr(found_files2[1], expect_b));
- assert_se(streq_ptr(found_files2[2], expect_c));
- assert_se(!found_files2[3]);
+ result = strv_free(result);
- assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
-}
+ /* search dir1 with suffix */
+ assert_se(conf_files_list(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED, search1) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b)));
-TEST(conf_files_list) {
- test_conf_files_list_one(false);
- test_conf_files_list_one(true);
+ result = strv_free(result);
+
+ assert_se(conf_files_list(&result, ".conf", t, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b)));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED, search1) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b)));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir1/b.conf")));
+
+ result = strv_free(result);
+
+ /* search two dirs */
+ assert_se(conf_files_list_strv(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST(search1, search2)) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b)));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv(&result, ".conf", t, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b)));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST(search1, search2)) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b)));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir2/aa.conf", "dir1/b.conf")));
+
+ result = strv_free(result);
+
+ /* filename only */
+ assert_se(conf_files_list_strv(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST(search1, search2)) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv(&result, ".conf", t, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST(search1, search2)) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
}
static void test_conf_files_insert_one(const char *root) {
log_info("%s try_reflink=%s max_bytes=%" PRIu64, __func__, yes_no(try_reflink), max_bytes);
- fd = open(src, O_RDONLY | O_CLOEXEC | O_NOCTTY);
+ fd = open(src, O_CLOEXEC | O_PATH);
assert_se(fd >= 0);
fd2 = mkostemp_safe(fn2);
}
TEST(strv_env_name_is_valid) {
- const char *valid_env_names[] = {"HOME", "USER", "SHELL", "PATH", NULL};
- const char *invalid_env_names[] = {"", "PATH", "home", "user", "SHELL", NULL};
- const char *repeated_env_names[] = {"HOME", "USER", "SHELL", "USER", NULL};
- assert_se(strv_env_name_is_valid((char **) valid_env_names));
- assert_se(!strv_env_name_is_valid((char **) invalid_env_names));
- assert_se(!strv_env_name_is_valid((char **) repeated_env_names));
+ assert_se(strv_env_name_is_valid(STRV_MAKE("HOME", "USER", "SHELL", "PATH")));
+ assert_se(!strv_env_name_is_valid(STRV_MAKE("", "PATH", "home", "user", "SHELL")));
+ assert_se(!strv_env_name_is_valid(STRV_MAKE("HOME", "USER", "SHELL", "USER")));
}
DEFINE_TEST_MAIN(LOG_DEBUG);
IN_SET(r,
-ENOENT, /* Some of the files might be absent */
-EINVAL, /* too small reads from /proc/self/pagemap trigger EINVAL */
- -EFBIG)); /* /proc/kcore and /proc/self/pagemap should be too large */
+ -EFBIG, /* /proc/kcore and /proc/self/pagemap should be too large */
+ -EBADF)); /* /proc/kcore is masked when we are running in docker. */
} else
log_info("read_virtual_file(\"%s\", %zu): %s (%zu bytes)", filename, max_size, r ? "non-truncated" : "truncated", size);
}
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
-
+#include "fileio.h"
#include "fs-util.h"
#include "log.h"
+#include "mkdir.h"
#include "os-util.h"
+#include "path-util.h"
+#include "rm-rf.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
+#include "tmpfile-util.h"
TEST(path_is_os_tree) {
assert_se(path_is_os_tree("/") > 0);
assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0);
}
+TEST(parse_extension_release) {
+ /* Let's assume that we have a valid extension image */
+ _cleanup_free_ char *id = NULL, *version_id = NULL, *foobar = NULL, *a = NULL, *b = NULL;
+ _cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL;
+
+ int r = mkdtemp_malloc("/tmp/test-os-util.XXXXXX", &tempdir);
+ if (r < 0)
+ log_error_errno(r, "Failed to setup working directory: %m");
+
+ assert_se(a = path_join(tempdir, "/usr/lib/extension-release.d/extension-release.test"));
+ assert_se(mkdir_parents(a, 0777) >= 0);
+
+ r = write_string_file(a, "ID=the-id \n VERSION_ID=the-version-id", WRITE_STRING_FILE_CREATE);
+ if (r < 0)
+ log_error_errno(r, "Failed to write file: %m");
+
+ assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, "test", false, "ID", &id, "VERSION_ID", &version_id) == 0);
+ log_info("ID: %s VERSION_ID: %s", id, version_id);
+ assert_se(streq(id, "the-id"));
+ assert_se(streq(version_id, "the-version-id"));
+
+ assert_se(b = path_join(tempdir, "/etc/extension-release.d/extension-release.tester"));
+ assert_se(mkdir_parents(b, 0777) >= 0);
+
+ r = write_string_file(b, "ID=\"ignored\" \n ID=\"the-id\" \n VERSION_ID='the-version-id'", WRITE_STRING_FILE_CREATE);
+ if (r < 0)
+ log_error_errno(r, "Failed to write file: %m");
+
+ assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, "tester", false, "ID", &id, "VERSION_ID", &version_id) == 0);
+ log_info("ID: %s VERSION_ID: %s", id, version_id);
+ assert_se(streq(id, "the-id"));
+ assert_se(streq(version_id, "the-version-id"));
+
+ assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, "tester", false, "FOOBAR", &foobar) == 0);
+ log_info("FOOBAR: %s", strnull(foobar));
+ assert_se(foobar == NULL);
+
+ assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, "test", false, "FOOBAR", &foobar) == 0);
+ log_info("FOOBAR: %s", strnull(foobar));
+ assert_se(foobar == NULL);
+}
+
TEST(load_os_release_pairs) {
_cleanup_(unlink_tempfilep) char tmpfile[] = "/tmp/test-os-util.XXXXXX";
assert_se(write_tmpfile(tmpfile,
assert_se(!path_equal_ptr("/a", "/b"));
assert_se(!path_equal_ptr("/a", NULL));
assert_se(!path_equal_ptr(NULL, "/a"));
-
- assert_se(path_equal_filename("/a/c", "/b/c"));
- assert_se(path_equal_filename("/a", "/a"));
- assert_se(!path_equal_filename("/a/b", "/a/c"));
- assert_se(!path_equal_filename("/b", "/c"));
}
static void test_path_simplify_one(const char *in, const char *out) {
test_path_compare_one("/foo/a/b", "/foo/aaa", -1);
}
+static void test_path_compare_filename_one(const char *a, const char *b, int expected) {
+ int r;
+
+ assert_se(path_compare_filename(a, a) == 0);
+ assert_se(path_compare_filename(b, b) == 0);
+
+ r = path_compare_filename(a, b);
+ assert_se((r > 0) == (expected > 0) && (r < 0) == (expected < 0));
+ r = path_compare_filename(b, a);
+ assert_se((r < 0) == (expected > 0) && (r > 0) == (expected < 0));
+
+ assert_se(path_equal_filename(a, a) == 1);
+ assert_se(path_equal_filename(b, b) == 1);
+ assert_se(path_equal_filename(a, b) == (expected == 0));
+ assert_se(path_equal_filename(b, a) == (expected == 0));
+}
+
+TEST(path_compare_filename) {
+ test_path_compare_filename_one("/goo", "/goo", 0);
+ test_path_compare_filename_one("/goo", "/goo", 0);
+ test_path_compare_filename_one("//goo", "/goo", 0);
+ test_path_compare_filename_one("//goo/////", "/goo", 0);
+ test_path_compare_filename_one("goo/////", "goo", 0);
+ test_path_compare_filename_one("/goo/boo", "/goo//boo", 0);
+ test_path_compare_filename_one("//goo/boo", "/goo/boo//", 0);
+ test_path_compare_filename_one("//goo/././//./boo//././//", "/goo/boo//.", 0);
+ test_path_compare_filename_one("/.", "//.///", -1);
+ test_path_compare_filename_one("/x", "x/", 0);
+ test_path_compare_filename_one("x/", "/", 1);
+ test_path_compare_filename_one("/x/./y", "x/y", 0);
+ test_path_compare_filename_one("/x/./y", "/x/y", 0);
+ test_path_compare_filename_one("/x/./././y", "/x/y/././.", 0);
+ test_path_compare_filename_one("./x/./././y", "./x/y/././.", 0);
+ test_path_compare_filename_one(".", "./.", -1);
+ test_path_compare_filename_one(".", "././.", -1);
+ test_path_compare_filename_one("./..", ".", 1);
+ test_path_compare_filename_one("x/.y", "x/y", -1);
+ test_path_compare_filename_one("foo", "/foo", 0);
+ test_path_compare_filename_one("/foo", "/foo/bar", 1);
+ test_path_compare_filename_one("/foo/aaa", "/foo/b", -1);
+ test_path_compare_filename_one("/foo/aaa", "/foo/b/a", 1);
+ test_path_compare_filename_one("/foo/a", "/foo/aaa", -1);
+ test_path_compare_filename_one("/foo/a/b", "/foo/aaa", 1);
+ test_path_compare_filename_one("/a/c", "/b/c", 0);
+ test_path_compare_filename_one("/a", "/a", 0);
+ test_path_compare_filename_one("/a/b", "/a/c", -1);
+ test_path_compare_filename_one("/b", "/c", -1);
+}
+
TEST(path_equal_root) {
/* Nail down the details of how path_equal("/", ...) works. */
assert_se(fsck_exists_for_fstype("/../bin/") == 0);
}
+TEST(path_prefix_root_cwd) {
+ _cleanup_free_ char *cwd = NULL, *ret = NULL, *expected = NULL;
+
+ assert_se(safe_getcwd(&cwd) >= 0);
+
+ assert_se(path_prefix_root_cwd("hoge", NULL, &ret) >= 0);
+ assert_se(expected = path_join(cwd, "hoge"));
+ assert_se(streq(ret, expected));
+
+ ret = mfree(ret);
+ expected = mfree(expected);
+
+ assert_se(path_prefix_root_cwd("/hoge", NULL, &ret) >= 0);
+ assert_se(streq(ret, "/hoge"));
+
+ ret = mfree(ret);
+
+ assert_se(path_prefix_root_cwd("hoge", "/a/b//./c///", &ret) >= 0);
+ assert_se(streq(ret, "/a/b/c/hoge"));
+
+ ret = mfree(ret);
+
+ assert_se(path_prefix_root_cwd("hoge", "a/b//./c///", &ret) >= 0);
+ assert_se(expected = path_join(cwd, "a/b/c/hoge"));
+ assert_se(streq(ret, expected));
+
+ ret = mfree(ret);
+ expected = mfree(expected);
+
+ assert_se(path_prefix_root_cwd("/../hoge/aaa/../././b", "/a/b//./c///", &ret) >= 0);
+ assert_se(streq(ret, "/a/b/c/../hoge/aaa/../././b"));
+
+ ret = mfree(ret);
+
+ assert_se(path_prefix_root_cwd("/../hoge/aaa/../././b", "a/b//./c///", &ret) >= 0);
+ assert_se(expected = path_join(cwd, "a/b/c/../hoge/aaa/../././b"));
+ assert_se(streq(ret, expected));
+}
+
static void test_path_make_relative_one(const char *from, const char *to, const char *expected) {
_cleanup_free_ char *z = NULL;
int r;
r = path_find_first_component(&p, accept_dot_dot, &e);
if (r <= 0) {
if (r == 0) {
- if (path)
+ if (path) {
assert_se(p == path + strlen_ptr(path));
- else
+ assert_se(isempty(p));
+ } else
assert_se(!p);
assert_se(!e);
}
assert_se(strcspn(e, "/") == (size_t) r);
assert_se(strlen_ptr(*expected) == (size_t) r);
assert_se(strneq(e, *expected++, r));
+
+ assert_se(p);
+ log_debug("p=%s", p);
+ if (!isempty(*expected))
+ assert_se(startswith(p, *expected));
+ else if (ret >= 0) {
+ assert_se(p == path + strlen_ptr(path));
+ assert_se(isempty(p));
+ }
}
}
test_path_find_first_component_one("././//.///aa/bbb//./ccc", false, STRV_MAKE("aa", "bbb", "ccc"), 0);
test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", false, STRV_MAKE("aa", "..."), -EINVAL);
test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", false, STRV_MAKE("aaa", ".bbb"), -EINVAL);
- test_path_find_first_component_one("a/foo./b", false, STRV_MAKE("a", "foo.", "b"), 0);
+ test_path_find_first_component_one("a/foo./b//././/", false, STRV_MAKE("a", "foo.", "b"), 0);
test_path_find_first_component_one(NULL, true, NULL, 0);
test_path_find_first_component_one("", true, NULL, 0);
test_path_find_first_component_one("././//.///aa/bbb//./ccc", true, STRV_MAKE("aa", "bbb", "ccc"), 0);
test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", true, STRV_MAKE("aa", "...", "..", "bbb", "ccc"), 0);
test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", true, STRV_MAKE("aaa", ".bbb", "..", "c.", "d.dd", "..eeee"), 0);
- test_path_find_first_component_one("a/foo./b", true, STRV_MAKE("a", "foo.", "b"), 0);
+ test_path_find_first_component_one("a/foo./b//././/", true, STRV_MAKE("a", "foo.", "b"), 0);
memset(foo, 'a', sizeof(foo) -1);
char_array_0(foo);
assert_se(strcspn(e, "/") == (size_t) r);
assert_se(strlen_ptr(*expected) == (size_t) r);
assert_se(strneq(e, *expected++, r));
+
+ assert_se(next);
+ log_debug("path=%s\nnext=%s", path, next);
+ if (!isempty(*expected)) {
+ assert_se(next < path + strlen(path));
+ assert_se(next >= path + strlen(*expected));
+ assert_se(startswith(next - strlen(*expected), *expected));
+ } else if (ret >= 0)
+ assert_se(next == path);
}
}
#include "initrd-util.h"
#include "log.h"
#include "macro.h"
+#include "nulstr-util.h"
#include "proc-cmdline.h"
+#include "process-util.h"
#include "special.h"
#include "string-util.h"
+#include "strv.h"
#include "tests.h"
static int obj;
TEST(proc_cmdline_override) {
_cleanup_free_ char *line = NULL, *value = NULL;
+ _cleanup_strv_free_ char **args = NULL;
assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"") == 0);
assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=different") == 0);
assert_se(proc_cmdline(&line) >= 0);
assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\""));
line = mfree(line);
+ assert_se(proc_cmdline_strv(&args) >= 0);
+ assert_se(strv_equal(args, STRV_MAKE("foo_bar=quux", "wuff-piep=tuet", "zumm", "some_arg_with_space=foo bar", "and_one_more=zzz aaa")));
+ args = strv_free(args);
/* Test if parsing makes uses of the override */
assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux"));
assert_se(proc_cmdline(&line) >= 0);
assert_se(streq(line, "hoge"));
line = mfree(line);
+ assert_se(proc_cmdline_strv(&args) >= 0);
+ assert_se(strv_equal(args, STRV_MAKE("hoge")));
+ args = strv_free(args);
assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux"));
value = mfree(value);
assert_se(!proc_cmdline_key_startswith("foo-bar", "foo_xx"));
}
+#define test_proc_cmdline_filter_pid1_args_one(nulstr, expected) \
+ ({ \
+ _cleanup_strv_free_ char **a = NULL, **b = NULL; \
+ const char s[] = (nulstr); \
+ \
+ /* This emulates get_process_cmdline_strv(). */ \
+ assert_se(a = strv_parse_nulstr_full(s, ELEMENTSOF(s), \
+ /* drop_trailing_nuls = */ true)); \
+ assert_se(proc_cmdline_filter_pid1_args(a, &b) >= 0); \
+ assert_se(strv_equal(b, expected)); \
+ })
+
+TEST(proc_cmdline_filter_pid1_args) {
+ test_proc_cmdline_filter_pid1_args_one("systemd\0",
+ STRV_MAKE_EMPTY);
+
+ test_proc_cmdline_filter_pid1_args_one("systemd\0"
+ "hoge\0"
+ "-x\0"
+ "foo\0"
+ "--aaa\0"
+ "var\0",
+ STRV_MAKE("hoge", "foo", "var"));
+
+ test_proc_cmdline_filter_pid1_args_one("/usr/lib/systemd/systemd\0"
+ "--switched-root\0"
+ "--system\0"
+ "--deserialize\030\0" /* followed with space */
+ "--deserialize=31\0" /* followed with '=' */
+ "--exit-code=42\0"
+ "\0\0\0"
+ "systemd.log_level=debug\0"
+ "--unit\0foo.target\0"
+ " ' quoted '\0"
+ "systemd.log_target=console\0"
+ "\t\0"
+ " arg with space \0"
+ "3\0"
+ "\0\0\0",
+ STRV_MAKE("", "", "", "systemd.log_level=debug", " ' quoted '", "systemd.log_target=console", "\t", " arg with space ", "3"));
+}
+
static int intro(void) {
if (access("/proc/cmdline", R_OK) < 0 && ERRNO_IS_PRIVILEGE(errno))
return log_tests_skipped("can't read /proc/cmdline");
{ "device", required_argument, NULL, 'd' },
{ "config", required_argument, NULL, 'f' },
{ "page", required_argument, NULL, 'p' },
- { "blacklisted", no_argument, NULL, 'b' },
- { "whitelisted", no_argument, NULL, 'g' },
+ { "denylisted", no_argument, NULL, 'b' },
+ { "allowlisted", no_argument, NULL, 'g' },
+ { "blacklisted", no_argument, NULL, 'b' }, /* backward compat */
+ { "whitelisted", no_argument, NULL, 'g' }, /* backward compat */
{ "replace-whitespace", no_argument, NULL, 'u' },
{ "sg-version", required_argument, NULL, 's' },
{ "verbose", no_argument, NULL, 'v' },
" -f --config= Location of config file\n"
" -p --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n"
" -s --sg-version=3|4 Use SGv3 or SGv4\n"
- " -b --blacklisted Treat device as blacklisted\n"
- " -g --whitelisted Treat device as whitelisted\n"
+ " -b --denylisted Treat device as denylisted\n"
+ " -g --allowlisted Treat device as allowlisted\n"
" -u --replace-whitespace Replace all whitespace by underscores\n"
" -v --verbose Verbose logging\n"
" -x --export Print values as environment keys\n",
* Figure out and print the sense key, asc and ascq.
*
* If you want to suppress these for a particular drive model, add
- * a black list entry in the scsi_id config file.
+ * a deny list entry in the scsi_id config file.
*
* XXX We probably need to: lookup the sense/asc/ascq in a retry
* table, and if found return 1 (after dumping the sense, asc, and
#include "user-util.h"
#include "virt.h"
-#define RULES_DIRS (const char* const*) CONF_PATHS_STRV("udev/rules.d")
+#define RULES_DIRS ((const char* const*) CONF_PATHS_STRV("udev/rules.d"))
typedef enum {
OP_MATCH, /* == */
if (token->op == OP_ASSIGN)
device_cleanup_tags(dev);
- if (buf[strspn(buf, ALPHANUMERICAL "-_")] != '\0') {
- log_event_error(dev, token, "Invalid tag name '%s', ignoring", buf);
- break;
- }
if (token->op == OP_REMOVE)
device_remove_tag(dev, buf);
else {
r = device_add_tag(dev, buf, true);
+ if (r == -ENOMEM)
+ return log_oom();
if (r < 0)
- return log_event_error_errno(dev, token, r, "Failed to add tag '%s': %m", buf);
+ log_event_warning_errno(dev, token, r, "Failed to add tag '%s', ignoring: %m", buf);
}
break;
}
break;
}
case TK_A_DEVLINK: {
- char buf[UDEV_PATH_SIZE], *p;
+ char buf[UDEV_PATH_SIZE];
bool truncated;
size_t count;
if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL))
device_cleanup_devlinks(dev);
- /* allow multiple symlinks separated by spaces */
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), event->esc != ESCAPE_NONE, &truncated);
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf),
+ /* replace_whitespace = */ event->esc != ESCAPE_NONE, &truncated);
if (truncated) {
log_event_truncated(dev, token, "symbolic link path", token->value, "SYMLINK", /* is_match = */ false);
break;
}
+ /* By default or string_escape=none, allow multiple symlinks separated by spaces. */
if (event->esc == ESCAPE_UNSET)
- count = udev_replace_chars(buf, "/ ");
+ count = udev_replace_chars(buf, /* allow = */ "/ ");
else if (event->esc == ESCAPE_REPLACE)
- count = udev_replace_chars(buf, "/");
+ count = udev_replace_chars(buf, /* allow = */ "/");
else
count = 0;
if (count > 0)
"Replaced %zu character(s) from result of SYMLINK=\"%s\"",
count, token->value);
- p = skip_leading_chars(buf, NULL);
- while (!isempty(p)) {
- char filename[UDEV_PATH_SIZE], *next;
+ for (const char *p = buf;;) {
+ _cleanup_free_ char *path = NULL;
- next = strchr(p, ' ');
- if (next) {
- *next++ = '\0';
- next = skip_leading_chars(next, NULL);
+ r = extract_first_word(&p, &path, NULL, EXTRACT_RETAIN_ESCAPE);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_warning_errno(r, "Failed to extract first path in SYMLINK=, ignoring: %m");
+ break;
}
-
- strscpyl_full(filename, sizeof(filename), &truncated, "/dev/", p, NULL);
- if (truncated)
- continue;
+ if (r == 0)
+ break;
if (token->op == OP_REMOVE) {
- device_remove_devlink(dev, filename);
- log_event_debug(dev, token, "Dropped SYMLINK '%s'", p);
+ r = device_remove_devlink(dev, path);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ log_event_warning_errno(dev, token, r, "Failed to remove devlink '%s', ignoring: %m", path);
+ else if (r > 0)
+ log_event_debug(dev, token, "Dropped SYMLINK '%s'", path);
} else {
- r = device_add_devlink(dev, filename);
+ r = device_add_devlink(dev, path);
+ if (r == -ENOMEM)
+ return log_oom();
if (r < 0)
- return log_event_error_errno(dev, token, r, "Failed to add devlink '%s': %m", filename);
-
- log_event_debug(dev, token, "Added SYMLINK '%s'", p);
+ log_event_warning_errno(dev, token, r, "Failed to add devlink '%s', ignoring: %m", path);
+ else if (r > 0)
+ log_event_debug(dev, token, "Added SYMLINK '%s'", path);
}
-
- p = next;
}
break;
}
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, arg_print ? "hVd:b:t:p" : "+hVd:b:t:p", options, NULL)) >= 0)
switch (c) {
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+dhV", options, NULL)) >= 0)
switch (c) {
arg_services = l;
}
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
+
for (;;) {
int c;
# Clean up certain "problematic" files which may be left over by failing tests
: >"${initdir:?}/etc/fstab"
: >"${initdir:?}/etc/crypttab"
+ # Clear previous assignment
+ QEMU_OPTIONS_ARRAY=()
}
test_run_one() {
local i
local qemu_opts=()
- for i in {0..27}; do
+ for (( i = 0; i < 5; i++ )); do
qemu_opts+=(
- "-device nvme,drive=nvme$i,serial=deadbeef$i,num_queues=8"
- "-drive format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+ "-device" "nvme,drive=nvme$i,serial=deadbeef$i,num_queues=8"
+ "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+ )
+ done
+ for (( i = 5; i < 10; i++ )); do
+ qemu_opts+=(
+ "-device" "nvme,drive=nvme$i,serial= deadbeef $i ,num_queues=8"
+ "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+ )
+ done
+ for (( i = 10; i < 15; i++ )); do
+ qemu_opts+=(
+ "-device" "nvme,drive=nvme$i,serial= dead/beef/$i ,num_queues=8"
+ "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+ )
+ done
+ for (( i = 15; i < 20; i++ )); do
+ qemu_opts+=(
+ "-device" "nvme,drive=nvme$i,serial=dead/../../beef/$i,num_queues=8"
+ "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
)
done
KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
- QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
+ QEMU_OPTIONS="${USER_QEMU_OPTIONS}"
+ QEMU_OPTIONS_ARRAY=("${qemu_opts[@]}")
test_run_one "${1:?}"
}
--- /dev/null
+../TEST-01-BASIC/Makefile
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -e
+
+TEST_DESCRIPTION="Test systemd generators"
+
+# shellcheck source=test/test-functions
+. "${TEST_BASE_DIR:?}/test-functions"
+
+do_test "$@"
ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_tape_end"
# type 8 devices are "Medium Changers"
-SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --allowlisted -d $devnode", \
SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL}"
# iSCSI devices from the same host have all the same ID_SERIAL,
KERNEL=="st*[0-9]|nst*[0-9]", ATTRS{ieee1394_id}=="?*", ENV{ID_SERIAL}="$attr{ieee1394_id}", ENV{ID_BUS}="ieee1394"
KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", KERNELS=="[0-9]*:*[0-9]", ENV{.BSG_DEV}="$root/bsg/$id"
-KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --whitelisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
+KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --allowlisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
KERNEL=="st*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
KERNEL=="st*[0-9]", ENV{ID_SCSI_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SCSI_SERIAL}"
KERNEL=="nst*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}-nst"
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
# SCSI devices
-KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="scsi"
-KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="cciss"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="scsi"
+KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="cciss"
KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
KERNEL=="sd*|cciss*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n"
# To force creating a new image from scratch (eg: to encrypt it), also define
# TEST_FORCE_NEWIMAGE=1 in the test setup script.
IMAGE_NAME=${IMAGE_NAME:-default}
-STRIP_BINARIES="${STRIP_BINARIES:-yes}"
TEST_REQUIRE_INSTALL_TESTS="${TEST_REQUIRE_INSTALL_TESTS:-1}"
TEST_PARALLELIZE="${TEST_PARALLELIZE:-0}"
TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED="${TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED:-1}"
sh
sleep
stat
+ stty
su
sulogin
sysctl
route
sort
strace
- stty
tty
vi
/usr/libexec/vi
IS_BUILT_WITH_COVERAGE=$(is_built_with_coverage && echo yes || echo no)
if get_bool "$IS_BUILT_WITH_ASAN"; then
- STRIP_BINARIES=no
SKIP_INITRD="${SKIP_INITRD:-yes}"
PATH_TO_INIT=$ROOTLIBDIR/systemd-under-asan
QEMU_MEM="${QEMU_MEM:-2G}"
read -ra user_qemu_options <<< "$QEMU_OPTIONS"
qemu_options+=("${user_qemu_options[@]}")
fi
+ qemu_options+=(${QEMU_OPTIONS_ARRAY:+"${QEMU_OPTIONS_ARRAY[@]}"})
if [[ -n "${KERNEL_APPEND:=}" ]]; then
local user_kernel_append
install_testuser
has_user_dbus_socket && install_user_dbus
setup_selinux
- strip_binaries
instmods veth
install_depmod_files
generate_module_dependencies
fi
# Partition sizes are in MiBs
- local root_size=1000
- local data_size=50
+ local root_size=768
+ local data_size=100
if ! get_bool "$NO_BUILD"; then
if meson configure "${BUILD_DIR:?}" | grep 'static-lib\|standalone-binaries' | awk '{ print $2 }' | grep -q 'true'; then
root_size=$((root_size+=200))
if get_bool "$IS_BUILT_WITH_COVERAGE"; then
root_size=$((root_size+=250))
fi
+ if get_bool "$IS_BUILT_WITH_ASAN"; then
+ root_size=$((root_size * 2))
+ fi
fi
- if ! get_bool "$STRIP_BINARIES"; then
- root_size=$((4 * root_size))
- data_size=$((2 * data_size))
- fi
+
if [ "$IMAGE_NAME" = "repart" ]; then
root_size=$((root_size+=1000))
fi
return $ret
}
-strip_binaries() {
- dinfo "Strip binaries"
- if ! get_bool "$STRIP_BINARIES"; then
- dinfo "STRIP_BINARIES == no, keeping binaries unstripped"
- return 0
- fi
- while read -r bin; do
- strip --strip-unneeded "$bin" |& grep -vi 'file format not recognized' | ddebug || :
- done < <(find "${initdir:?}" -executable -not -path '*/lib/modules/*.ko' -type f)
-}
-
create_rc_local() {
dinfo "Create rc.local"
mkdir -p "${initdir:?}/etc/rc.d"
ID='the-id2'
EOF
-SYSTEMD_OS_RELEASE="$root/etc/os-release2" check_alias o 'the-id2'
+SYSTEMD_OS_RELEASE="/etc/os-release2" check_alias o 'the-id2'
{
devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
exp_links => ["boot_disk1", "boot_diskXY1"],
- not_exp_links => ["boot_diskXX1", "hoge"],
+ not_exp_links => ["boot_diskXX1"],
}],
rules => <<EOF
SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n"
SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="1", SYMLINK+="boot_diskXY%n"
-SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n", SYMLINK+="hoge"
-SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK-="hoge"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n"
+EOF
+ },
+ {
+ desc => "SYMLINK tests",
+ devices => [
+ {
+ devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+ exp_links => ["link1", "link2/foo", "link3/aaa/bbb",
+ "abs1", "abs2/foo", "abs3/aaa/bbb",
+ "default___replace_test/foo_aaa",
+ "string_escape___replace/foo_bbb",
+ "env_with_space",
+ "default/replace/mode_foo__hoge",
+ "replace_env_harder_foo__hoge"],
+ not_exp_links => ["removed1", "removed2", "removed3", "unsafe/../../path", "/nondev/path/will/be/refused"],
+ }],
+ rules => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="removed1"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed1"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/./dev///removed2"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed2"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="././removed3"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="/dev//./removed3/./"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="unsafe/../../path"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/nondev/path/will/be/refused"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="link1 .///link2/././/foo//./ .///link3/aaa/bbb"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/dev/abs1 /dev//./abs2///foo/./ ////dev/abs3/aaa/bbb"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="default?;;replace%%test/foo'aaa"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", SYMLINK+="string_escape replace/foo%%bbb"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ENV{.HOGE}="env with space", SYMLINK+="%E{.HOGE}"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ENV{.HOGE}="default/replace/mode?foo;;hoge", SYMLINK+="%E{.HOGE}"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", ENV{.HOGE}="replace/env/harder?foo;;hoge", SYMLINK+="%E{.HOGE}"
EOF
},
{
not_exp_name => "bad",
}],
rules => <<EOF
-KERNEL=="sda", TAG=""
-TAGS=="|foo", SYMLINK+="found"
-TAGS=="aaa|bbb", SYMLINK+="bad"
+KERNEL=="sda", ENV{HOGE}=""
+ENV{HOGE}=="|foo", SYMLINK+="found"
+ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
EOF
},
{
not_exp_name => "bad",
}],
rules => <<EOF
-KERNEL=="sda", TAG=""
-TAGS=="foo||bar", SYMLINK+="found"
-TAGS=="aaa|bbb", SYMLINK+="bad"
+KERNEL=="sda", ENV{HOGE}=""
+ENV{HOGE}=="foo||bar", SYMLINK+="found"
+ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
EOF
},
{
not_exp_name => "bad",
}],
rules => <<EOF
-KERNEL=="sda", TAG=""
-TAGS=="foo|", SYMLINK+="found"
-TAGS=="aaa|bbb", SYMLINK+="bad"
+KERNEL=="sda", ENV{HOGE}=""
+ENV{HOGE}=="foo|", SYMLINK+="found"
+ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
EOF
},
{
KERNEL=="sda", TAG="c"
TAGS=="foo||bar||c", SYMLINK+="found"
TAGS=="aaa||bbb||ccc", SYMLINK+="bad"
+EOF
+ },
+ {
+ desc => "TAG refuses invalid string",
+ devices => [
+ {
+ devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+ exp_links => ["valid", "found"],
+ not_exp_links => ["empty", "invalid_char", "path", "bad", "bad2"],
+ }],
+ rules => <<EOF
+KERNEL=="sda", TAG+="", TAG+="invalid.char", TAG+="path/is/also/invalid", TAG+="valid"
+TAGS=="", SYMLINK+="empty"
+TAGS=="invalid.char", SYMLINK+="invalid_char"
+TAGS=="path/is/also/invalid", SYMLINK+="path"
+TAGS=="valid", SYMLINK+="valid"
+TAGS=="valid|", SYMLINK+="found"
+TAGS=="aaa|", SYMLINK+="bad"
+TAGS=="aaa|bbb", SYMLINK+="bad2"
EOF
},
{
EOF
},
{
- desc => "continuations with white only line",
+ desc => "continuations with space only line",
devices => [
{
devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+link_endswith() {
+ [[ -h "${1:?}" && "$(readlink "${1:?}")" =~ ${2:?}$ ]]
+}
+
+link_eq() {
+ [[ -h "${1:?}" && "$(readlink "${1:?}")" == "${2:?}" ]]
+}
+
+# Get the value from a 'key=value' assignment
+opt_get_arg() {
+ local arg
+
+ IFS="=" read -r _ arg <<< "${1:?}"
+ test -n "$arg"
+ echo "$arg"
+}
+
+in_initrd() {
+ [[ "${SYSTEMD_IN_INITRD:-0}" -ne 0 ]]
+}
+
+# Check if we're parsing host's fstab in initrd
+in_initrd_host() {
+ in_initrd && [[ "${SYSTEMD_SYSROOT_FSTAB:-/dev/null}" != /dev/null ]]
+}
+
+in_container() {
+ systemd-detect-virt -qc
+}
+
+# Filter out "unwanted" options, i.e. options that the fstab-generator doesn't
+# propagate to the final mount unit
+opt_filter_consumed() {(
+ set +x
+ local opt split_options filtered_options
+
+ IFS="," read -ra split_options <<< "${1:?}"
+ for opt in "${split_options[@]}"; do
+ if [[ "$opt" =~ ^x-systemd.device-timeout= ]]; then
+ continue
+ fi
+
+ filtered_options+=("$opt")
+ done
+
+ IFS=","; printf "%s" "${filtered_options[*]}"
+)}
+
+# Run the given generator $1 with target directory $2 - clean the target
+# directory beforehand
+run_and_list() {
+ local generator="${1:?}"
+ local out_dir="${2:?}"
+
+ rm -fr "${out_dir:?}"/*
+ "$generator" "$out_dir"
+ ls -lR "$out_dir"
+}
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=TEST-01-BASIC
-After=multi-user.target
+# Order the test unit after systemd-update-utmp-runlevel.service, since
+# the service doesn't play well with daemon-reexec
+# See: https://github.com/systemd/systemd/issues/27167
+After=multi-user.target systemd-update-utmp-runlevel.service
Wants=systemd-resolved.service systemd-networkd.service
[Service]
ExecStartPre=rm -f /failed /testok
-ExecStart=sh -e -x -c 'systemctl --state=failed --no-legend --no-pager >/failed ; systemctl daemon-reload ; echo OK >/testok'
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
Type=oneshot
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Check if we properly differentiate between a full systemd setup and a "light"
+# version of it that's done during daemon-reexec
+#
+# See: https://github.com/systemd/systemd/issues/27106
+if systemd-detect-virt -q --container; then
+ # We initialize /run/systemd/container only during a full setup
+ test -e /run/systemd/container
+ cp -afv /run/systemd/container /tmp/container
+ rm -fv /run/systemd/container
+ systemctl daemon-reexec
+ test ! -e /run/systemd/container
+ cp -afv /tmp/container /run/systemd/container
+else
+ # We bring the loopback netdev up only during a full setup, so it should
+ # not get brought back up during reexec if we disable it beforehand
+ [[ "$(ip -o link show lo)" =~ LOOPBACK,UP ]]
+ ip link set lo down
+ [[ "$(ip -o link show lo)" =~ state\ DOWN ]]
+ systemctl daemon-reexec
+ [[ "$(ip -o link show lo)" =~ state\ DOWN ]]
+ ip link set lo up
+
+ # We also disable coredumps only during a full setup
+ sysctl -w kernel.core_pattern=dont-overwrite-me
+ systemctl daemon-reexec
+ diff <(echo dont-overwrite-me) <(sysctl --values kernel.core_pattern)
+fi
+
+# Collect failed units & do one daemon-reload to a basic sanity check
+systemctl --state=failed --no-legend --no-pager | tee /failed
+systemctl daemon-reload
+
+# Check that the early setup is actually skipped on reexec.
+# If the early setup is done more than once, then several timestamps,
+# e.g. SecurityStartTimestamp, are re-initialized, and causes an ABRT
+# of systemd-analyze blame. See issue #27187.
+systemd-analyze blame
+
+echo OK >/testok
rm /tmp/lb1
+# https://bugzilla.redhat.com/show_bug.cgi?id=2183546
+mkdir /run/systemd/system/systemd-journald.service.d
+MID=$(cat /etc/machine-id)
+for c in "NONE" "XZ" "LZ4" "ZSTD"; do
+ cat >/run/systemd/system/systemd-journald.service.d/compress.conf <<EOF
+[Service]
+Environment=SYSTEMD_JOURNAL_COMPRESS=${c}
+EOF
+ systemctl daemon-reload
+ systemctl restart systemd-journald.service
+ journalctl --rotate
+
+ ID=$(systemd-id128 new)
+ systemd-cat -t "$ID" /bin/bash -c "for ((i=0;i<100;i++)); do echo -n hoge with ${c}; done; echo"
+ journalctl --sync
+ timeout 10 bash -c "while ! SYSTEMD_LOG_LEVEL=debug journalctl --verify --quiet --file /var/log/journal/$MID/system.journal 2>&1 | grep -q -F 'compress=${c}'; do sleep .5; done"
+
+ # $SYSTEMD_JOURNAL_COMPRESS= also works for journal-remote
+ if [[ -x /usr/lib/systemd/systemd-journal-remote ]]; then
+ for cc in "NONE" "XZ" "LZ4" "ZSTD"; do
+ rm -f /tmp/foo.journal
+ SYSTEMD_JOURNAL_COMPRESS="${cc}" /usr/lib/systemd/systemd-journal-remote --split-mode=none -o /tmp/foo.journal --getter="journalctl -b -o export -t $ID"
+ SYSTEMD_LOG_LEVEL=debug journalctl --verify --quiet --file /tmp/foo.journal 2>&1 | grep -q -F "compress=${cc}"
+ journalctl -t "$ID" -o cat --file /tmp/foo.journal | grep -q -F "hoge with ${c}"
+ done
+ fi
+done
+rm /run/systemd/system/systemd-journald.service.d/compress.conf
+systemctl daemon-reload
+systemctl restart systemd-journald.service
+journalctl --rotate
+
touch /testok
STATE_DIRECTORY=/var/lib/
fi
# Bump the timeout if we're running with plain QEMU
-[[ "$(systemd-detect-virt -v)" == "qemu" ]] && TIMEOUT=60 || TIMEOUT=30
+[[ "$(systemd-detect-virt -v)" == "qemu" ]] && TIMEOUT=90 || TIMEOUT=30
systemd-dissect --no-pager /usr/share/minimal_0.raw | grep -q '✓ portable service'
systemd-dissect --no-pager /usr/share/minimal_1.raw | grep -q '✓ portable service'
systemd-dissect --detach "${image}.raw"
(! systemd-dissect --detach "${image}.raw")
+# check for confext functionality
+mkdir -p /run/confexts/test/etc/extension-release.d
+echo "ID=_any" >/run/confexts/test/etc/extension-release.d/extension-release.test
+echo "ARCHITECTURE=_any" >>/run/confexts/test/etc/extension-release.d/extension-release.test
+echo "MARKER_CONFEXT_123" >/run/confexts/test/etc/testfile
+systemd-confext merge
+grep -q -F "MARKER_CONFEXT_123" /etc/testfile
+systemd-confext status
+systemd-confext unmerge
+rm -rf /run/confexts/
+
echo OK >/testok
exit 0
}
testcase_nvme_basic() {
+ local expected_symlinks=()
+ local i
+
+ for (( i = 0; i < 5; i++ )); do
+ expected_symlinks+=(
+ # both replace mode provides the same devlink
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i"
+ # with nsid
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i"_1
+ )
+ done
+ for (( i = 5; i < 10; i++ )); do
+ expected_symlinks+=(
+ # old replace mode
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl__deadbeef_"$i"
+ # newer replace mode
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i"
+ # with nsid
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i"_1
+ )
+ done
+ for (( i = 10; i < 15; i++ )); do
+ expected_symlinks+=(
+ # old replace mode does not provide devlink, as serial contains "/"
+ # newer replace mode
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i"
+ # with nsid
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i"_1
+ )
+ done
+ for (( i = 15; i < 20; i++ )); do
+ expected_symlinks+=(
+ # old replace mode does not provide devlink, as serial contains "/"
+ # newer replace mode
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_dead_.._.._beef_"$i"
+ # with nsid
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_dead_.._.._beef_"$i"_1
+ )
+ done
+
+ udevadm settle
+ ls /dev/disk/by-id
+ for i in "${expected_symlinks[@]}"; do
+ udevadm wait --settle --timeout=30 "$i"
+ done
+
lsblk --noheadings | grep "^nvme"
- [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 28 ]]
+ [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 20 ]]
}
testcase_nvme_subsystem() {
# Sanity checks
#
-# We can't really test time, blame, critical-chain and plot verbs here, as
+# We can't really test time, critical-chain and plot verbs here, as
# the testsuite service is a part of the boot transaction, so let's assume
# they fail
systemd-analyze || :
systemd-analyze time || :
-systemd-analyze blame || :
systemd-analyze critical-chain || :
+# blame
+systemd-analyze blame
+systemd-run --wait --user --pipe -M testuser@.host systemd-analyze blame
# plot
systemd-analyze plot >/dev/null || :
systemd-analyze plot --json=pretty >/dev/null || :
: >/failed
+# Make sure the content of kbd-model-map is the one that the tests expect
+# regardless of the version intalled on the distro where the testsuite is
+# running on.
+export SYSTEMD_KBD_MODEL_MAP=/usr/lib/systemd/tests/testdata/test-keymap-util/kbd-model-map
+
enable_debug
test_locale
test_vc_keymap
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+MODULES_LOAD_BIN="/usr/lib/systemd/systemd-modules-load"
+CONFIG_FILE="/run/modules-load.d/99-test.conf"
+
+at_exit() {
+ rm -rfv "${CONFIG_FILE:?}"
+}
+
+trap at_exit EXIT
+
+if systemd-detect-virt -cq; then
+ echo "Running in a container, skipping the systemd-modules-load test..."
+ exit 0
+fi
+
+# Check if we have required kernel modules
+modprobe --all --resolve-alias loop dummy
+
+mkdir -p /run/modules-load.d/
+
+"$MODULES_LOAD_BIN"
+"$MODULES_LOAD_BIN" --help
+"$MODULES_LOAD_BIN" --version
+
+# Explicit config file
+modprobe -v --all --remove loop dummy
+printf "loop\ndummy" >"$CONFIG_FILE"
+"$MODULES_LOAD_BIN" "$CONFIG_FILE" |& tee /tmp/out.log
+grep -E "Inserted module .*loop" /tmp/out.log
+grep -E "Inserted module .*dummy" /tmp/out.log
+
+# Implicit config file
+modprobe -v --all --remove loop dummy
+printf "loop\ndummy" >"$CONFIG_FILE"
+"$MODULES_LOAD_BIN" |& tee /tmp/out.log
+grep -E "Inserted module .*loop" /tmp/out.log
+grep -E "Inserted module .*dummy" /tmp/out.log
+
+# Valid & invalid data mixed together
+modprobe -v --all --remove loop dummy
+cat >"$CONFIG_FILE" <<EOF
+
+loop
+loop
+loop
+ loop
+dummy
+ \\n\n\n\\\\\\
+
+loo!@@123##2455
+# This is a comment
+$(printf "%.0sx" {0..4096})
+dummy
+loop
+foo-bar-baz
+1
+"
+'
+EOF
+"$MODULES_LOAD_BIN" |& tee /tmp/out.log
+grep -E "^Inserted module .*loop" /tmp/out.log
+grep -E "^Inserted module .*dummy" /tmp/out.log
+grep -E "^Failed to find module .*foo-bar-baz" /tmp/out.log
+(! grep -E "This is a comment" /tmp/out.log)
+# Each module should be loaded only once, even if specified multiple times
+[[ "$(grep -Ec "^Inserted module" /tmp/out.log)" -eq 2 ]]
+[[ "$(grep -Ec "^Failed to find module" /tmp/out.log)" -eq 7 ]]
+
+# Command line arguments
+modprobe -v --all --remove loop dummy
+# Make sure we have no config files left over that might interfere with
+# following tests
+rm -fv "$CONFIG_FILE"
+[[ -z "$(systemd-analyze cat-config modules-load.d)" ]]
+CMDLINE="ro root= modules_load= modules_load=, / = modules_load=foo-bar-baz,dummy modules_load=loop,loop,loop"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" "$MODULES_LOAD_BIN" |& tee /tmp/out.log
+grep -E "^Inserted module .*loop" /tmp/out.log
+grep -E "^Inserted module .*dummy" /tmp/out.log
+grep -E "^Failed to find module .*foo-bar-baz" /tmp/out.log
+# Each module should be loaded only once, even if specified multiple times
+[[ "$(grep -Ec "^Inserted module" /tmp/out.log)" -eq 2 ]]
+
+(! "$MODULES_LOAD_BIN" --nope)
+(! "$MODULES_LOAD_BIN" /foo/bar/baz)
# Test for resolvectl, resolvconf
systemctl unmask systemd-resolved.service
-systemctl start systemd-resolved.service
+systemctl enable --now systemd-resolved.service
systemctl service-log-level systemd-resolved.service debug
ip link add hoge type dummy
ip link add hoge.foo type dummy
#!/usr/bin/env bash
set -eux
set -o pipefail
+test "$FDSTORE" -eq 7
N="/tmp/$RANDOM"
echo $RANDOM > "$N"
systemd-notify --fd=4 --fdname=quux --pid=parent 4< "$N"
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-debug-generator"
+OUT_DIR="$(mktemp -d /tmp/debug-generator.XXX)"
+
+at_exit() {
+ rm -frv "${OUT_DIR:?}"
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+# Potential FIXME:
+# - debug-generator should gracefully handle duplicated mask/wants
+# - also, handle gracefully empty mask/wants
+ARGS=(
+ "systemd.mask=masked-no-suffix"
+ "systemd.mask=masked.service"
+ "systemd.mask=masked.socket"
+ "systemd.wants=wanted-no-suffix"
+ "systemd.wants=wanted.service"
+ "systemd.wants=wanted.mount"
+ "rd.systemd.mask=masked-initrd.service"
+ "rd.systemd.wants=wanted-initrd.service"
+)
+
+# Regular (non-initrd) scenario
+#
+: "debug-shell: regular"
+CMDLINE="ro root=/ ${ARGS[*]} rd.systemd.debug_shell"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-no-suffix.service" /dev/null
+link_eq "$OUT_DIR/masked.service" /dev/null
+link_eq "$OUT_DIR/masked.socket" /dev/null
+link_endswith "$OUT_DIR/default.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service
+link_endswith "$OUT_DIR/default.target.wants/wanted.service" /lib/systemd/system/wanted.service
+link_endswith "$OUT_DIR/default.target.wants/wanted.mount" /lib/systemd/system/wanted.mount
+# Following stuff should be ignored, as it's prefixed with rd.
+test ! -h "$OUT_DIR/masked-initrd.service"
+test ! -h "$OUT_DIR/default.target.wants/wants-initrd.service"
+test ! -h "$OUT_DIR/default.target.wants/debug-shell.service"
+test ! -d "$OUT_DIR/initrd.target.wants"
+
+# Let's re-run the generator with systemd.debug_shell that should be honored
+: "debug-shell: regular + systemd.debug_shell"
+CMDLINE="$CMDLINE systemd.debug_shell"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+
+# Same thing, but with custom tty
+: "debug-shell: regular + systemd.debug_shell=/dev/tty666"
+CMDLINE="$CMDLINE systemd.debug_shell=/dev/tty666"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+grep -F "/dev/tty666" "$OUT_DIR/debug-shell.service.d/50-tty.conf"
+
+# Now override the default target via systemd.unit=
+: "debug-shell: regular + systemd.unit="
+CMDLINE="$CMDLINE systemd.unit=my-fancy.target"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-no-suffix.service" /dev/null
+link_eq "$OUT_DIR/masked.service" /dev/null
+link_eq "$OUT_DIR/masked.socket" /dev/null
+link_endswith "$OUT_DIR/my-fancy.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service
+link_endswith "$OUT_DIR/my-fancy.target.wants/wanted.service" /lib/systemd/system/wanted.service
+link_endswith "$OUT_DIR/my-fancy.target.wants/wanted.mount" /lib/systemd/system/wanted.mount
+link_endswith "$OUT_DIR/my-fancy.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+test ! -d "$OUT_DIR/default.target.wants"
+
+
+# Initrd scenario
+: "debug-shell: initrd"
+CMDLINE="ro root=/ ${ARGS[*]} systemd.debug_shell"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-initrd.service" /dev/null
+link_endswith "$OUT_DIR/initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service
+# The non-initrd stuff (i.e. without the rd. suffix) should be ignored in
+# this case
+test ! -h "$OUT_DIR/masked-no-suffix.service"
+test ! -h "$OUT_DIR/masked.service"
+test ! -h "$OUT_DIR/masked.socket"
+test ! -h "$OUT_DIR/initrd.target.wants/debug-shell.service"
+test ! -d "$OUT_DIR/default.target.wants"
+
+# Again, but with rd.systemd.debug_shell
+: "debug-shell: initrd + rd.systemd.debug_shell"
+CMDLINE="$CMDLINE rd.systemd.debug_shell"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/initrd.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+
+# Override the default target
+: "debug-shell: initrd + rd.systemd.unit"
+CMDLINE="$CMDLINE rd.systemd.unit=my-fancy-initrd.target"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-initrd.service" /dev/null
+link_endswith "$OUT_DIR/my-fancy-initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service
+test ! -d "$OUT_DIR/initrd.target.wants"
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+GENERATOR_BIN="/usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator"
+CONFIG_FILE="/run/environment.d/99-test.conf"
+OUT_FILE="$(mktemp)"
+
+at_exit() {
+ set +e
+ rm -frv "${CONFIG_FILE:?}" "${OUT_FILE:?}"
+ systemctl -M testuser@.host --user daemon-reload
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+mkdir -p /run/environment.d/
+
+cat >"$CONFIG_FILE" <<EOF
+
+\t\n\t
+3
+=
+ =
+INVALID
+ALSO_INVALID=
+EMPTY_INVALID=""
+3_INVALID=foo
+xxxx xx xxxxxx
+# This is a comment
+$(printf "%.0sx" {0..4096})=
+SIMPLE=foo
+REF=\$SIMPLE
+ALSO_REF=\${SIMPLE}
+DEFAULT="\${NONEXISTENT:-default value}"
+ALTERNATE="\${SIMPLE:+alternate value}"
+LIST=foo,bar,baz
+SIMPLE=redefined
+UNASSIGNED=\$FOO_BAR_BAZ
+VERY_LONG="very $(printf "%.0sx" {0..4096})= long string"
+EOF
+
+# Source env assignments from a file and check them - do this in a subshell
+# to not pollute the test environment
+check_environment() {(
+ # shellcheck source=/dev/null
+ source "${1:?}"
+
+ [[ "$SIMPLE" == "redefined" ]]
+ [[ "$REF" == "foo" ]]
+ [[ "$ALSO_REF" == "foo" ]]
+ [[ "$DEFAULT" == "default value" ]]
+ [[ "$ALTERNATE" == "alternate value" ]]
+ [[ "$LIST" == "foo,bar,baz" ]]
+ [[ "$VERY_LONG" =~ ^very\ ]]
+ [[ "$VERY_LONG" =~ \ long\ string$ ]]
+ [[ -z "$UNASSIGNED" ]]
+ [[ ! -v INVALID ]]
+ [[ ! -v ALSO_INVALID ]]
+ [[ ! -v EMPTY_INVALID ]]
+ [[ ! -v 3_INVALID ]]
+)}
+
+# Check the output by directly calling the generator
+"$GENERATOR_BIN" | tee "$OUT_FILE"
+check_environment "$OUT_FILE"
+: >"$OUT_FILE"
+
+# Check if the generator is correctly called in a user session
+systemctl -M testuser@.host --user daemon-reload
+systemctl -M testuser@.host --user show-environment | tee "$OUT_FILE"
+check_environment "$OUT_FILE"
+
+(! "$GENERATOR_BIN" foo)
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235,SC2233
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+export SYSTEMD_LOG_LEVEL=debug
+
+GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-fstab-generator"
+NETWORK_FS_RX="^(afs|ceph|cifs|gfs|gfs2|ncp|ncpfs|nfs|nfs4|ocfs2|orangefs|pvfs2|smb3|smbfs|davfs|glusterfs|lustre|sshfs)$"
+OUT_DIR="$(mktemp -d /tmp/fstab-generator.XXX)"
+FSTAB="$(mktemp)"
+
+at_exit() {
+ rm -fr "${OUT_DIR:?}" "${FSTAB:?}"
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+FSTAB_GENERAL=(
+ # Valid entries
+ "/dev/test2 /nofail ext4 nofail 0 0"
+ "/dev/test3 /regular btrfs defaults 0 0"
+ "/dev/test4 /x-systemd.requires xfs x-systemd.requires=foo.service 0 0"
+ "/dev/test5 /x-systemd.before-after xfs x-systemd.before=foo.service,x-systemd.after=bar.mount 0 0"
+ "/dev/test6 /x-systemd.wanted-required-by xfs x-systemd.wanted-by=foo.service,x-systemd.required-by=bar.device 0 0"
+ "/dev/test7 /x-systemd.requires-mounts-for xfs x-systemd.requires-mounts-for=/foo/bar/baz 0 0"
+ "/dev/test8 /x-systemd.automount-idle-timeout vfat x-systemd.automount,x-systemd.idle-timeout=50s 0 0"
+ "/dev/test9 /x-systemd.makefs xfs x-systemd.makefs 0 0"
+ "/dev/test10 /x-systemd.growfs xfs x-systemd.growfs 0 0"
+ "/dev/test11 /_netdev ext4 defaults,_netdev 0 0"
+ "/dev/test12 /_rwonly ext4 x-systemd.rw-only 0 0"
+ "/dev/test13 /chaos1 zfs x-systemd.rw-only,x-systemd.requires=hello.service,x-systemd.after=my.device 0 0"
+ "/dev/test14 /chaos2 zfs x.systemd.wanted-by=foo.service,x-systemd.growfs,x-systemd.makefs 0 0"
+ "/dev/test15 /fstype/auto auto defaults 0 0"
+ "/dev/test16 /fsck/me ext4 defaults 0 1"
+ "/dev/test17 /also/fsck/me ext4 defaults,x-systemd.requires-mounts-for=/var/lib/foo 0 99"
+ "/dev/test18 /swap swap defaults 0 0"
+ "/dev/test19 /swap/makefs swap defaults,x-systemd.makefs 0 0"
+ "/dev/test20 /var xfs defaults,x-systemd.device-timeout=1h 0 0"
+ "/dev/test21 /usr ext4 defaults 0 1"
+ "/dev/test22 /initrd/mount ext2 defaults,x-systemd.rw-only,x-initrd.mount 0 1"
+ "/dev/test23 /initrd/mount/nofail ext3 defaults,nofail,x-initrd.mount 0 1"
+ "/dev/test24 /initrd/mount/deps ext4 x-initrd.mount,x-systemd.before=early.service,x-systemd.after=late.service 0 1"
+
+ # Incomplete, but valid entries
+ "/dev/incomplete1 /incomplete1"
+ "/dev/incomplete2 /incomplete2 ext4"
+ "/dev/incomplete3 /incomplete3 ext4 defaults"
+ "/dev/incomplete4 /incomplete4 ext4 defaults 0"
+
+ # Remote filesystems
+ "/dev/remote1 /nfs nfs bg 0 0"
+ "/dev/remote2 /nfs4 nfs4 bg 0 0"
+ "bar.tld:/store /remote/storage nfs ro,x-systemd.wanted-by=store.service 0 0"
+ "user@host.tld:/remote/dir /remote/top-secret sshfs rw,x-systemd.before=naughty.service 0 0"
+ "foo.tld:/hello /hello/world ceph defaults 0 0"
+ "//192.168.0.1/storage /cifs-storage cifs automount,nofail 0 0"
+)
+
+FSTAB_GENERAL_ROOT=(
+ # rootfs with bunch of options we should ignore and fsck enabled
+ "/dev/test1 / ext4 noauto,nofail,automount,x-systemd.wanted-by=foo,x-systemd.required-by=bar 0 1"
+ "${FSTAB_GENERAL[@]}"
+)
+
+FSTAB_MINIMAL=(
+ "/dev/loop1 /foo/bar ext3 defaults 0 0"
+)
+
+FSTAB_DUPLICATE=(
+ "/dev/dup1 / ext4 defaults 0 1"
+ "/dev/dup2 / ext4 defaults,x-systemd.requires=foo.mount 0 2"
+)
+
+FSTAB_INVALID=(
+ # Ignored entries
+ "/dev/ignored1 /sys/fs/cgroup/foo ext4 defaults 0 0"
+ "/dev/ignored2 /sys/fs/selinux ext4 defaults 0 0"
+ "/dev/ignored3 /dev/console ext4 defaults 0 0"
+ "/dev/ignored4 /proc/kmsg ext4 defaults 0 0"
+ "/dev/ignored5 /proc/sys ext4 defaults 0 0"
+ "/dev/ignored6 /proc/sys/kernel/random/boot_id ext4 defaults 0 0"
+ "/dev/ignored7 /run/host ext4 defaults 0 0"
+ "/dev/ignored8 /run/host/foo ext4 defaults 0 0"
+ "/dev/ignored9 /autofs autofs defaults 0 0"
+ "/dev/invalid1 not-a-path ext4 defaults 0 0"
+ ""
+ "/dev/invalid1"
+ " "
+ "\\"
+ "$"
+)
+
+check_fstab_mount_units() {
+ local what where fstype opts passno unit
+ local item opt split_options filtered_options supp service device arg
+ local array_name="${1:?}"
+ local out_dir="${2:?}"
+ # Get a reference to the array from its name
+ local -n fstab_entries="$array_name"
+
+ # Running the checks in a container is pretty much useless, since we don't
+ # generate any mounts, but don't skip the whole test to test the "skip"
+ # paths as well
+ in_container && return 0
+
+ for item in "${fstab_entries[@]}"; do
+ # Don't use a pipe here, as it would make the variables out of scope
+ read -r what where fstype opts _ passno <<< "$item"
+
+ # Skip non-initrd mounts in initrd
+ if in_initrd_host && ! [[ "$opts" =~ x-initrd.mount ]]; then
+ continue
+ fi
+
+ if [[ "$fstype" == swap ]]; then
+ unit="$(systemd-escape --suffix=swap --path "${what:?}")"
+ cat "$out_dir/$unit"
+
+ grep -qE "^What=$what$" "$out_dir/$unit"
+ if [[ "$opts" != defaults ]]; then
+ grep -qE "^Options=$opts$" "$out_dir/$unit"
+ fi
+
+ if [[ "$opts" =~ x-systemd.makefs ]]; then
+ service="$(systemd-escape --template=systemd-mkswap@.service --path "$what")"
+ test -e "$out_dir/$service"
+ fi
+
+ continue
+ fi
+
+ # If we're parsing host's fstab in initrd, prefix all mount targets
+ # with /sysroot
+ in_initrd_host && where="/sysroot${where:?}"
+ unit="$(systemd-escape --suffix=mount --path "${where:?}")"
+ cat "$out_dir/$unit"
+
+ # Check the general stuff
+ grep -qE "^What=$what$" "$out_dir/$unit"
+ grep -qE "^Where=$where$" "$out_dir/$unit"
+ if [[ -n "$fstype" ]] && [[ "$fstype" != auto ]]; then
+ grep -qE "^Type=$fstype$" "$out_dir/$unit"
+ fi
+ if [[ -n "$opts" ]] && [[ "$opts" != defaults ]]; then
+ # Some options are not propagated to the generated unit
+ filtered_options="$(opt_filter_consumed "$opts")"
+ if [[ "${filtered_options[*]}" != defaults ]]; then
+ grep -qE "^Options=.*$filtered_options.*$" "$out_dir/$unit"
+ fi
+ fi
+
+ if ! [[ "$opts" =~ (noauto|x-systemd.(wanted-by=|required-by=|automount)) ]]; then
+ # We don't create the Requires=/Wants= symlinks for noauto/automount mounts
+ # and for mounts that use x-systemd.wanted-by=/required-by=
+ if in_initrd_host; then
+ if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then
+ link_eq "$out_dir/initrd-fs.target.requires/$unit" "../$unit"
+ else
+ link_eq "$out_dir/initrd-fs.target.wants/$unit" "../$unit"
+ fi
+ elif [[ "$fstype" =~ $NETWORK_FS_RX || "$opts" =~ _netdev ]]; then
+ # Units with network filesystems should have a Requires= dependency
+ # on the remote-fs.target, unless they use nofail or are an nfs "bg"
+ # mounts, in which case the dependency is downgraded to Wants=
+ if [[ "$opts" =~ nofail ]] || [[ "$fstype" =~ ^(nfs|nfs4) && "$opts" =~ bg ]]; then
+ link_eq "$out_dir/remote-fs.target.wants/$unit" "../$unit"
+ else
+ link_eq "$out_dir/remote-fs.target.requires/$unit" "../$unit"
+ fi
+ else
+ # Similarly, local filesystems should have a Requires= dependency on
+ # the local-fs.target, unless they use nofail, in which case the
+ # dependency is downgraded to Wants=. Rootfs is a special case,
+ # since we always ignore nofail there
+ if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then
+ link_eq "$out_dir/local-fs.target.requires/$unit" "../$unit"
+ else
+ link_eq "$out_dir/local-fs.target.wants/$unit" "../$unit"
+ fi
+ fi
+ fi
+
+ if [[ "${passno:=0}" -ne 0 ]]; then
+ # Generate systemd-fsck@.service dependencies, if applicable
+ if in_initrd && [[ "$where" == / || "$where" == /usr ]]; then
+ continue
+ fi
+
+ if [[ "$where" == / ]]; then
+ link_endswith "$out_dir/local-fs.target.wants/systemd-fsck-root.service" "/lib/systemd/system/systemd-fsck-root.service"
+ else
+ service="$(systemd-escape --template=systemd-fsck@.service --path "$what")"
+ grep -qE "^After=$service$" "$out_dir/$unit"
+ if [[ "$where" == /usr ]]; then
+ grep -qE "^Wants=$service$" "$out_dir/$unit"
+ else
+ grep -qE "^Requires=$service$" "$out_dir/$unit"
+ fi
+ fi
+ fi
+
+ # Check various x-systemd options
+ #
+ # First, split them into an array to make splitting them even further
+ # easier
+ IFS="," read -ra split_options <<< "$opts"
+ # and process them one by one.
+ #
+ # Note: the "machinery" below might (and probably does) miss some
+ # combinations of supported options, so tread carefully
+ for opt in "${split_options[@]}"; do
+ if [[ "$opt" =~ ^x-systemd.requires= ]]; then
+ service="$(opt_get_arg "$opt")"
+ grep -qE "^Requires=$service$" "$out_dir/$unit"
+ grep -qE "^After=$service$" "$out_dir/$unit"
+ elif [[ "$opt" =~ ^x-systemd.before= ]]; then
+ service="$(opt_get_arg "$opt")"
+ grep -qE "^Before=$service$" "$out_dir/$unit"
+ elif [[ "$opt" =~ ^x-systemd.after= ]]; then
+ service="$(opt_get_arg "$opt")"
+ grep -qE "^After=$service$" "$out_dir/$unit"
+ elif [[ "$opt" =~ ^x-systemd.wanted-by= ]]; then
+ service="$(opt_get_arg "$opt")"
+ if [[ "$where" == / ]]; then
+ # This option is ignored for rootfs mounts
+ (! link_eq "$out_dir/$service.wants/$unit" "../$unit")
+ else
+ link_eq "$out_dir/$service.wants/$unit" "../$unit"
+ fi
+ elif [[ "$opt" =~ ^x-systemd.required-by= ]]; then
+ service="$(opt_get_arg "$opt")"
+ if [[ "$where" == / ]]; then
+ # This option is ignored for rootfs mounts
+ (! link_eq "$out_dir/$service.requires/$unit" "../$unit")
+ else
+ link_eq "$out_dir/$service.requires/$unit" "../$unit"
+ fi
+ elif [[ "$opt" =~ ^x-systemd.requires-mounts-for= ]]; then
+ arg="$(opt_get_arg "$opt")"
+ grep -qE "^RequiresMountsFor=$arg$" "$out_dir/$unit"
+ elif [[ "$opt" == x-systemd.device-bound ]]; then
+ # This is implied for fstab mounts
+ :
+ elif [[ "$opt" == x-systemd.automount ]]; then
+ # The $unit should have an accompanying automount unit
+ supp="$(systemd-escape --suffix=automount --path "$where")"
+ test -e "$out_dir/$supp"
+ link_eq "$out_dir/local-fs.target.requires/$supp" "../$supp"
+ elif [[ "$opt" =~ ^x-systemd.idle-timeout= ]]; then
+ # The timeout applies to the automount unit, not the original
+ # mount one
+ arg="$(opt_get_arg "$opt")"
+ supp="$(systemd-escape --suffix=automount --path "$where")"
+ grep -qE "^TimeoutIdleSec=$arg$" "$out_dir/$supp"
+ elif [[ "$opt" =~ ^x-systemd.device-timeout= ]]; then
+ arg="$(opt_get_arg "$opt")"
+ device="$(systemd-escape --suffix=device --path "$what")"
+ grep -qE "^JobRunningTimeoutSec=$arg$" "$out_dir/${device}.d/50-device-timeout.conf"
+ elif [[ "$opt" == x-systemd.makefs ]]; then
+ service="$(systemd-escape --template=systemd-makefs@.service --path "$what")"
+ test -e "$out_dir/$service"
+ link_eq "$out_dir/${unit}.requires/$service" "../$service"
+ elif [[ "$opt" == x-systemd.rw-only ]]; then
+ grep -qE "^ReadWriteOnly=yes$" "$out_dir/$unit"
+ elif [[ "$opt" == x-systemd.growfs ]]; then
+ service="$(systemd-escape --template=systemd-growfs@.service --path "$where")"
+ link_endswith "$out_dir/${unit}.wants/$service" "/lib/systemd/system/systemd-growfs@.service"
+ elif [[ "$opt" == bg ]] && [[ "$fstype" =~ ^(nfs|nfs4)$ ]]; then
+ # We "convert" nfs bg mounts to fg, so we can do the job-control
+ # ourselves
+ grep -qE "^Options=.*\bx-systemd.mount-timeout=infinity\b" "$out_dir/$unit"
+ grep -qE "^Options=.*\bfg\b.*" "$out_dir/$unit"
+ elif [[ "$opt" =~ ^x-systemd\. ]]; then
+ echo >&2 "Unhandled mount option: $opt"
+ exit 1
+ fi
+ done
+ done
+}
+
+# TODO
+# - kernel arguments
+
+: "fstab-generator: regular"
+printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR"
+
+# Skip the rest when running in a container, as it makes little sense to check
+# initrd-related stuff there and fstab-generator might have a bit strange
+# behavior during certain tests, like https://github.com/systemd/systemd/issues/27156
+if in_container; then
+ echo "Running in a container, skipping the rest of the fstab-generator tests..."
+ exit 0
+fi
+
+# In this mode we treat the entries as "regular" ones
+: "fstab-generator: initrd - initrd fstab"
+printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null check_fstab_mount_units FSTAB_GENERAL "$OUT_DIR"
+
+# In this mode we prefix the mount target with /sysroot and ignore all mounts
+# that don't have the x-initrd.mount flag
+: "fstab-generator: initrd - host fstab"
+printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR"
+
+# Check the default stuff that we (almost) always create in initrd
+: "fstab-generator: initrd default"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+test -e "$OUT_DIR/sysroot.mount"
+test -e "$OUT_DIR/systemd-fsck-root.service"
+link_eq "$OUT_DIR/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount"
+link_eq "$OUT_DIR/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount"
+
+: "fstab-generator: run as systemd-sysroot-fstab-check in initrd"
+ln -svf "$GENERATOR_BIN" /tmp/systemd-sysroot-fstab-check
+(! /tmp/systemd-sysroot-fstab-check foo)
+(! SYSTEMD_IN_INITRD=0 /tmp/systemd-sysroot-fstab-check)
+printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_SYSROOT_FSTAB="$FSTAB" /tmp/systemd-sysroot-fstab-check
+
+: "fstab-generator: duplicate"
+printf "%s\n" "${FSTAB_DUPLICATE[@]}" >"$FSTAB"
+cat "$FSTAB"
+(! SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR")
+
+: "fstab-generator: invalid"
+printf "%s\n" "${FSTAB_INVALID[@]}" >"$FSTAB"
+cat "$FSTAB"
+# Don't care about the exit code here
+SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR" || :
+# No mounts should get created here
+[[ "$(find "$OUT_DIR" -name "*.mount" | wc -l)" -eq 0 ]]
+
+: "fstab-generator: kernel args - fstab=0"
+printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
+
+: "fstab-generator: kernel args - rd.fstab=0"
+printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-81-GENERATORS
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+: >/failed
+
+systemctl log-level debug
+
+for script in "${0%.sh}".*.sh; do
+ echo "Running $script"
+ "./$script"
+done
+
+touch /testok
+rm /failed
['systemd-reboot.service', ''],
['systemd-rfkill.socket', 'ENABLE_RFKILL'],
['systemd-sysext.service', 'ENABLE_SYSEXT'],
+ ['systemd-confext.service', 'ENABLE_SYSEXT'],
['systemd-sysupdate.timer', 'ENABLE_SYSUPDATE'],
['systemd-sysupdate-reboot.timer', 'ENABLE_SYSUPDATE'],
['systemd-sysusers.service', 'ENABLE_SYSUSERS',
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Merge System Configuration Images into /etc/
+Documentation=man:systemd-confext.service(8)
+
+ConditionCapability=CAP_SYS_ADMIN
+ConditionDirectoryNotEmpty=|/run/confexts
+ConditionDirectoryNotEmpty=|/var/lib/confexts
+ConditionDirectoryNotEmpty=|/usr/local/lib/confexts
+ConditionDirectoryNotEmpty=|/usr/lib/confexts
+
+DefaultDependencies=no
+After=local-fs.target
+Before=sysinit.target systemd-tmpfiles-setup.service
+Conflicts=shutdown.target initrd-switch-root.target
+Before=shutdown.target initrd-switch-root.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=systemd-confext refresh
+ExecReload=systemd-confext refresh
+ExecStop=systemd-confext unmerge
+
+[Install]
+WantedBy=sysinit.target
[Service]
Type=oneshot
RemainAfterExit=yes
-ExecStart=systemd-sysext merge
+ExecStart=systemd-sysext refresh
+ExecReload=systemd-sysext refresh
ExecStop=systemd-sysext unmerge
[Install]