1
0
mirror of https://github.com/systemd/systemd synced 2026-02-25 16:54:44 +01:00

Compare commits

..

29 Commits

Author SHA1 Message Date
Luca Boccassi
61b31f7999
sd-device,udev: several fixlets and cleanups for managing tags (#40786) 2026-02-23 21:53:28 +00:00
Lennart Poettering
ab83c7489d
sysupdated: Split Update() into Acquire() and Install() D-Bus methods (#40628)
These mirror the `sysupdate acquire` and `sysupdate install` verbs,
which are themselves a split of `sysupdate update` into downloading and
installing stages.

I have dropped the existing `Update()` method in the final commit, as
per the suggestion on
https://gitlab.gnome.org/GNOME/gnome-software/-/merge_requests/2004#note_2145880.
If the D-Bus API for `sysupdated` is considered stable now, though, I
can drop that commit.

CC @AdrianVovk

Fixes https://github.com/systemd/systemd/issues/34814
2026-02-23 22:08:37 +01:00
Daan De Meyer
868fdc5520
Several follow-ups for JournalAccess varlink interface (#40794) 2026-02-23 20:17:31 +01:00
Philip Withnall
8b5374d6c2
sysupdated: Drop the Update() D-Bus method
Now that it’s been replaced by `Acquire()` and `Install()`, there’s no
need to expose it as a separate method. Wrappers around `sysupdated` can
still expose an ‘update’ convenience verb by calling those two methods.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: https://github.com/systemd/systemd/issues/34814
2026-02-23 16:35:19 +00:00
Philip Withnall
7537717aed
test: Expand sysupdate test to test updatectl update
This further expands the sysupdate test to run the suite a fourth time,
testing that `updatectl update` behaves the same as `sysupdate update`
and `sysupdate acquire; sysupdate update --offline`.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
2026-02-23 16:35:15 +00:00
Philip Withnall
b0ca987cd9
updatectl: Expose partial/pending updates in the updatectl list output
And expand the tests to catch if any more JSON fields are added to the
sysupdated output in future without being added to `updatectl`’s parser.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: https://github.com/systemd/systemd/issues/34814
2026-02-23 16:35:10 +00:00
Philip Withnall
a5ff474a2b
updatectl: Use sysupdated’s Acquire() and Install() methods
Rather than calling `Update()`. This should allow us to eventually drop
`Update()`.

This should just be an internal change, and should not result in any
user-visible changes to the behaviour of `updatectl update`.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: https://github.com/systemd/systemd/issues/34814
2026-02-23 16:35:05 +00:00
Philip Withnall
f0b2ea63f4
sysupdated: Add Acquire() and Install() D-Bus methods
These mirror the `sysupdate acquire` and `sysupdate update --offline`
verbs, which are themselves a split of `sysupdate update` into
downloading and installing stages.

The existing `sysupdated` `Update()` method is kept for now, for
convenience. It might be dropped in future.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: https://github.com/systemd/systemd/issues/34814
2026-02-23 16:35:01 +00:00
Mike Yuan
d39b103719
core: Introduce ConditionPathIsSocket= (#40791) 2026-02-23 17:07:28 +01:00
DaanDeMeyer
f29edb227c repart: Use new mkfs.xfs support for populating from directories
xfsprogs 6.17.0 added support for populating xfs filesystems from
directories. As this supports extended attributes unlike our current
hack with protofiles. Let's make use of the new feature in mkfs-util.c

As there's no clean way to do feature detection on the mkfs.xfs binary,
we drop support for the old hack with protofiles that we had before.
2026-02-23 15:04:41 +00:00
Daan De Meyer
a51e5bd1f1 core: Introduce ConditionPathIsSocket= 2026-02-23 13:08:49 +01:00
Daan De Meyer
54b745d7bb test-condition: Migrate to new assertion macros 2026-02-23 12:28:10 +01:00
Mike Yuan
d8302c2fd9
journalctl-vl-server: allow querying units for specific uid in system scope
This also moves journal_add_unit_matches() back to
journalctl-filter.[ch], as I see more appropriate.
2026-02-23 10:43:00 +01:00
Mike Yuan
bd994b2f81
journalctl-vl-server: do not output in reverse order
While at it, do not log from varlink method callback,
as per our usual style.
2026-02-23 10:21:57 +01:00
Mike Yuan
9d193c8f13
journalctl-vl-server: 'entry' is never NULL 2026-02-23 10:21:57 +01:00
Mike Yuan
ffe69b3fa1
journalctl-vl-server: use SD_JSON_BUILD_PAIR_VARIANT 2026-02-23 10:21:57 +01:00
Mike Yuan
fd0b6187be
journalctl-vl-server: return proper error if unit pattern has no match
Note that SD_VARLINK_ERROR_INVALID_PARAMETERS requires
the invalid param name to be specified, hence we would
otherwise have reported an invalid INVALID_PARAMETERS
error ;)
2026-02-23 10:21:56 +01:00
Mike Yuan
4ed5e84bea
logs-show: clean up journal_entry_to_json() a bit
* Make sure ret is initialized on success return
* Drop unneeded 'object' variable
* No need to ref/unref json objects when constructing
  intermediary array
2026-02-23 10:21:56 +01:00
Mike Yuan
eaeceb5fb6
units/user/systemd-journalctl.socket: drop MaxConnectionsPerSource=
For AF_UNIX sockets connection sources are accounted for
based on UID, hence in user scope this effectively
limits total number of connections, which is not really
desirable.
2026-02-23 10:21:56 +01:00
Mike Yuan
2afe8e28f8
units/systemd-journalctl@.service: run with DynamicUser=yes
Follow-up for a109189fabe6a4c307528459f891c2d545361622

This follows the existing practice for
systemd-journal-{upload,gatewayd}.service,
as I think allocating a full-blown user
specifically for this purpose is an overkill.
And with DynamicUser=yes we can also take
advantage of implied sandboxing.
2026-02-23 10:21:56 +01:00
Mike Yuan
2619656950
units/systemd-journalctl@.service: require mount for /var/log/journal/ 2026-02-23 10:21:56 +01:00
Mike Yuan
5c247bbd57
units/systemd-pcrlock.socket: drop [Install] section
The socket is statically enabled.
2026-02-23 10:21:55 +01:00
Mike Yuan
c71aa4047b
TODO: drop completed entry 2026-02-23 10:21:55 +01:00
Yu Watanabe
aaadf3e907 udev/dump: also dump current tags
The "TAG" token in udev rules handles the current tags.
Let's also show the current tags.
2026-02-23 05:52:49 +09:00
Yu Watanabe
e0a2369dd1 sd-device: do not try to remove previous tag indexes
The removed code in device_tag_index() in fact does nothing,
as sd_device.all_tags is never cleared. Moreover, not only the code
is meaningless, but it is theoretically/logically wrong, as the symlinks
in /run/udev/tags/ should be 'sticky', hence we should even not try to
remove them.
2026-02-23 05:46:42 +09:00
Yu Watanabe
fc03d200c7 sd-device: do not clear sd_device.all_tags even on TAG="hoge"
The current tag concept has been introduced by
e77b146f825ef1bb63c297cc713962b94422d2c6 (v247) to make symlinks in
/run/udev/tags/ are 'sticky'.

However, when TAG= (rather than TAG+=) is specified, then the tags
assigned in the previous events were also cleared.
This fixes the issue and now symlinks in /run/udev/tags/ are really
'sticky'.

Fortunately, TAG= is mostly unused. So, the issue should not affect
and the fix should not change anything on almost all systems.
2026-02-23 05:46:42 +09:00
Yu Watanabe
1c867dc71a sd-device: move copy_all_tags() from udev
No functional change, preparation for the next commit.
2026-02-23 05:46:42 +09:00
Yu Watanabe
b65467b647 sd-device: shorten code a bit 2026-02-23 05:46:42 +09:00
Philip Withnall
dc3e59f50c
sysupdated: Factor out a common check about job types
This introduces no functional changes.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
2026-02-10 19:00:40 +00:00
41 changed files with 757 additions and 503 deletions

9
NEWS
View File

@ -55,7 +55,14 @@ CHANGES WITH 260 in spe:
Device nodes should not be owned by a non-system user/group. It is Device nodes should not be owned by a non-system user/group. It is
recommended to check udev rules files with 'udevadm verify' and/or recommended to check udev rules files with 'udevadm verify' and/or
'udevadm test' commands . 'udevadm test' commands.
* systemd-repart will now make use of mkfs.xfs's support for
populating XFS filesystems from a directory. This support was
added in xfsprogs 6.17.0 released 20 October 2025. As there is no
proper way to detect whether mkfs.xfs supports populating from a
directory or not, we make use of it unconditionally and have dropped
support for the old way using protofiles.
New system interfaces and components: New system interfaces and components:

1
TODO
View File

@ -967,7 +967,6 @@ Features:
* Varlinkification of the following command line tools, to open them up to * Varlinkification of the following command line tools, to open them up to
other programs via IPC: other programs via IPC:
- bootctl
- coredumpcl - coredumpcl
- systemd-bless-boot - systemd-bless-boot
- systemd-measure - systemd-measure

View File

@ -120,7 +120,12 @@ node /org/freedesktop/sysupdate1/target/host {
in t flags, in t flags,
out s json); out s json);
CheckNew(out s new_version); CheckNew(out s new_version);
Update(in s new_version, Acquire(in s new_version,
in t flags,
out s new_version,
out t job_id,
out o job_path);
Install(in s new_version,
in t flags, in t flags,
out s new_version, out s new_version,
out t job_id, out t job_id,
@ -163,7 +168,9 @@ node /org/freedesktop/sysupdate1/target/host {
<variablelist class="dbus-method" generated="True" extra-ref="CheckNew()"/> <variablelist class="dbus-method" generated="True" extra-ref="CheckNew()"/>
<variablelist class="dbus-method" generated="True" extra-ref="Update()"/> <variablelist class="dbus-method" generated="True" extra-ref="Acquire()"/>
<variablelist class="dbus-method" generated="True" extra-ref="Install()"/>
<variablelist class="dbus-method" generated="True" extra-ref="Vacuum()"/> <variablelist class="dbus-method" generated="True" extra-ref="Vacuum()"/>
@ -245,7 +252,8 @@ node /org/freedesktop/sysupdate1/target/host {
<listitem><para>A boolean indicating whether this version is incomplete, which means that it is <listitem><para>A boolean indicating whether this version is incomplete, which means that it is
missing some file. Note that only installed incomplete versions will be offered by the service; missing some file. Note that only installed incomplete versions will be offered by the service;
versions that are incomplete on the server-side are completely ignored. Incomplete versions can versions that are incomplete on the server-side are completely ignored. Incomplete versions can
be repaired in-place by calling <function>Update()</function> on that version.</para></listitem> be repaired in-place by calling <function>Acquire()</function> and <function>Install()</function>
on that version.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -261,16 +269,25 @@ node /org/freedesktop/sysupdate1/target/host {
<function>Describe()</function> to query more information about the version returned by this method. <function>Describe()</function> to query more information about the version returned by this method.
</para> </para>
<para><function>Update()</function> installs an update for this target. If a <para><function>Acquire()</function> downloads an update for this target, if one is available. If a
<varname>new_version</varname> is specified, that is the version that gets installed. Otherwise, the <varname>new_version</varname> is specified, that is the version that gets downloaded. Otherwise, the
latest version is installed. The <varname>flags</varname> argument is added for future latest version is downloaded. Call <function>Install()</function> to install the acquired update.
extensibility. No flags are currently defined, and the argument is required to be set to The <varname>flags</varname> argument is added for future extensibility. No flags are currently
<literal>0</literal>. Unlike all the other methods in this interface, <function>Update()</function> defined, and the argument is required to be set to <literal>0</literal>. This method pulls both
does not wait for its job to complete. Instead, it returns the job's numeric ID and object path as soon metadata and payload data from the network.</para>
as the job begins, so that the caller can listen for progress updates or cancel the operation. This
method also returns the version the target will be updated to, for cases where no version was specified <para><function>Install()</function> installs an already-acquired update for this target. If a
by the caller. This method pulls both metadata and payload data from the network. Listen for the <varname>new_version</varname> is specified, that is the version that gets installed, assuming it has
Manager's <function>JobRemoved()</function> signal to detect when the job is complete.</para> already been acquired. Otherwise, the latest acquired version is installed. The
<varname>flags</varname> argument is added for future extensibility. No flags are currently defined,
and the argument is required to be set to <literal>0</literal>.</para>
<para>Unlike all the other methods in this interface, <function>Acquire()</function> and
<function>Install()</function> do not wait for their jobs to complete. Instead, they return the job's
numeric ID and object path as soon as the job begins, so that the caller can listen for progress
updates or cancel the operation. These methods also return the version the target will be updated to,
for cases where no version was specified by the caller. Listen for the Manager's
<function>JobRemoved()</function> signal to detect when the job is complete.</para>
<para><function>Vacuum()</function> deletes old installed versions of this target to free up space. <para><function>Vacuum()</function> deletes old installed versions of this target to free up space.
It returns the number of instances that have been deleted.</para> It returns the number of instances that have been deleted.</para>
@ -347,7 +364,7 @@ node /org/freedesktop/sysupdate1/target/host {
preemptive decisions to be made about features that are planned to appear in future releases of the OS. preemptive decisions to be made about features that are planned to appear in future releases of the OS.
The drop-in will have a filename of <literal>50-systemd-sysupdate-enabled.conf</literal>. The drop-in will have a filename of <literal>50-systemd-sysupdate-enabled.conf</literal>.
This method only changes configuration files; to actually apply the changes, clients will need to This method only changes configuration files; to actually apply the changes, clients will need to
call <function>Update()</function>. call <function>Acquire()</function> and <function>Install()</function>.
Depending on the exact needs of the client, it can choose to update the system to the latest available Depending on the exact needs of the client, it can choose to update the system to the latest available
version, or it can extend the newest existing installation in-place (by passing in the version returned version, or it can extend the newest existing installation in-place (by passing in the version returned
by <varname>GetVersion()</varname>). by <varname>GetVersion()</varname>).
@ -397,7 +414,8 @@ node /org/freedesktop/sysupdate1/target/host {
use the polkit action <interfacename>org.freedesktop.sysupdate1.check</interfacename>. use the polkit action <interfacename>org.freedesktop.sysupdate1.check</interfacename>.
By default, this action is permitted without administrator authentication.</para> By default, this action is permitted without administrator authentication.</para>
<para><function>Update()</function> uses the polkit action <para><function>Acquire()</function> and <function>Install()</function>
use the polkit action
<interfacename>org.freedesktop.sysupdate1.update</interfacename> when no version is specified. <interfacename>org.freedesktop.sysupdate1.update</interfacename> when no version is specified.
By default, this action is permitted without administrator authentication. When a version is By default, this action is permitted without administrator authentication. When a version is
specified, <interfacename>org.freedesktop.sysupdate1.update-to-version</interfacename> is specified, <interfacename>org.freedesktop.sysupdate1.update-to-version</interfacename> is
@ -496,14 +514,15 @@ node /org/freedesktop/sysupdate1/job/_1 {
<para>The <varname>Type</varname> property exposes the type of operation (one of: <para>The <varname>Type</varname> property exposes the type of operation (one of:
<literal>list</literal>, <literal>describe</literal>, <literal>check-new</literal>, <literal>list</literal>, <literal>describe</literal>, <literal>check-new</literal>,
<literal>update</literal>, <literal>vacuum</literal>, or <literal>describe-feature</literal>).</para> <literal>acquire</literal>, <literal>install</literal>, <literal>vacuum</literal>, or
<literal>describe-feature</literal>).</para>
<para>The <varname>Offline</varname> property exposes whether the job is permitted to access <para>The <varname>Offline</varname> property exposes whether the job is permitted to access
the network or not.</para> the network or not.</para>
<para>The <varname>Progress</varname> property exposes the current progress of the job as a value <para>The <varname>Progress</varname> property exposes the current progress of the job as a value
between 0 and 100. It is only available for <literal>update</literal> jobs; for all other jobs between 0 and 100. It is only available for <literal>acquire</literal> and <literal>install</literal>
it is always 0.</para> jobs; for all other jobs it is always 0.</para>
</refsect2> </refsect2>
<refsect2> <refsect2>
@ -562,7 +581,8 @@ node /org/freedesktop/sysupdate1/job/_1 {
<para><function>List()</function>, <para><function>List()</function>,
<function>Describe()</function>, <function>Describe()</function>,
<function>CheckNew()</function>, <function>CheckNew()</function>,
<function>Update()</function>, <function>Acquire()</function>,
<function>Install()</function>,
<function>Vacuum()</function>, <function>Vacuum()</function>,
<function>GetAppStream()</function>, <function>GetAppStream()</function>,
<function>GetVersion()</function>, <function>GetVersion()</function>,

View File

@ -497,17 +497,6 @@
in a user namespace with the current user mapped to the root user to make sure the files and in a user namespace with the current user mapped to the root user to make sure the files and
directories in the image are owned by the root user.</para> directories in the image are owned by the root user.</para>
<para>Note that when populating XFS filesystems with <command>systemd-repart</command> and loop
devices are not available, populating XFS filesystems with files containing spaces, tabs or newlines
might fail on old versions of
<citerefentry project='man-pages'><refentrytitle>mkfs.xfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>
due to limitations of its protofile format.</para>
<para>Note that when populating XFS filesystems with <command>systemd-repart</command> and loop
devices are not available, extended attributes will not be copied into generated XFS filesystems
due to limitations <citerefentry project='man-pages'><refentrytitle>mkfs.xfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>'s
protofile format.</para>
<para>This option cannot be combined with <varname>CopyBlocks=</varname>.</para> <para>This option cannot be combined with <varname>CopyBlocks=</varname>.</para>
<para>When <para>When
@ -517,6 +506,9 @@
<option>--image=</option> or <option>--root=</option> switches are used, the source paths are taken <option>--image=</option> or <option>--root=</option> switches are used, the source paths are taken
relative to the specified root directory or disk image root.</para> relative to the specified root directory or disk image root.</para>
<para>Note that when <varname>CopyFiles=</varname> is used with <varname>Format=xfs</varname>,
<command>xfsprogs</command> 6.17 or newer is required.</para>
<xi:include href="version-info.xml" xpointer="v247"/></listitem> <xi:include href="version-info.xml" xpointer="v247"/></listitem>
</varlistentry> </varlistentry>

View File

@ -1797,6 +1797,17 @@
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>ConditionPathIsSocket=</varname></term>
<listitem><para><varname>ConditionPathIsSocket=</varname> is similar to
<varname>ConditionPathExists=</varname> but verifies that a certain path exists and is a
socket.</para>
<xi:include href="version-info.xml" xpointer="v260"/>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><varname>ConditionDirectoryNotEmpty=</varname></term> <term><varname>ConditionDirectoryNotEmpty=</varname></term>
@ -2052,6 +2063,7 @@
<term><varname>AssertPathIsMountPoint=</varname></term> <term><varname>AssertPathIsMountPoint=</varname></term>
<term><varname>AssertPathIsReadWrite=</varname></term> <term><varname>AssertPathIsReadWrite=</varname></term>
<term><varname>AssertPathIsEncrypted=</varname></term> <term><varname>AssertPathIsEncrypted=</varname></term>
<term><varname>AssertPathIsSocket=</varname></term>
<term><varname>AssertDirectoryNotEmpty=</varname></term> <term><varname>AssertDirectoryNotEmpty=</varname></term>
<term><varname>AssertFileNotEmpty=</varname></term> <term><varname>AssertFileNotEmpty=</varname></term>
<term><varname>AssertFileIsExecutable=</varname></term> <term><varname>AssertFileIsExecutable=</varname></term>

View File

@ -895,7 +895,6 @@ foreach option : ['adm-gid',
'video-gid', 'video-gid',
'wheel-gid', 'wheel-gid',
'systemd-journal-gid', 'systemd-journal-gid',
'systemd-journal-uid',
'systemd-network-uid', 'systemd-network-uid',
'systemd-resolve-uid', 'systemd-resolve-uid',
'systemd-timesync-uid'] 'systemd-timesync-uid']

View File

@ -328,8 +328,6 @@ option('wheel-gid', type : 'integer', value : 0,
description : 'soft-static allocation for the "wheel" group') description : 'soft-static allocation for the "wheel" group')
option('systemd-journal-gid', type : 'integer', value : 0, option('systemd-journal-gid', type : 'integer', value : 0,
description : 'soft-static allocation for the systemd-journal group') description : 'soft-static allocation for the systemd-journal group')
option('systemd-journal-uid', type : 'integer', value : 0,
description : 'soft-static allocation for the systemd-journal user')
option('systemd-network-uid', type : 'integer', value : 0, option('systemd-network-uid', type : 'integer', value : 0,
description : 'soft-static allocation for the systemd-network user') description : 'soft-static allocation for the systemd-network user')
option('systemd-resolve-uid', type : 'integer', value : 0, option('systemd-resolve-uid', type : 'integer', value : 0,

View File

@ -97,6 +97,7 @@ typedef enum RuntimeScope RuntimeScope;
typedef enum TimestampStyle TimestampStyle; typedef enum TimestampStyle TimestampStyle;
typedef enum UnitActiveState UnitActiveState; typedef enum UnitActiveState UnitActiveState;
typedef enum UnitDependency UnitDependency; typedef enum UnitDependency UnitDependency;
typedef enum UnitNameMangle UnitNameMangle;
typedef enum UnitType UnitType; typedef enum UnitType UnitType;
typedef enum WaitFlags WaitFlags; typedef enum WaitFlags WaitFlags;

View File

@ -366,6 +366,7 @@ Unit.ConditionPathIsSymbolicLink, config_parse_unit_condition_path,
Unit.ConditionPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, conditions) Unit.ConditionPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, conditions)
Unit.ConditionPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, conditions) Unit.ConditionPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, conditions)
Unit.ConditionPathIsEncrypted, config_parse_unit_condition_path, CONDITION_PATH_IS_ENCRYPTED, offsetof(Unit, conditions) Unit.ConditionPathIsEncrypted, config_parse_unit_condition_path, CONDITION_PATH_IS_ENCRYPTED, offsetof(Unit, conditions)
Unit.ConditionPathIsSocket, config_parse_unit_condition_path, CONDITION_PATH_IS_SOCKET, offsetof(Unit, conditions)
Unit.ConditionDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, conditions) Unit.ConditionDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, conditions)
Unit.ConditionFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, conditions) Unit.ConditionFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, conditions)
Unit.ConditionFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, conditions) Unit.ConditionFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, conditions)
@ -401,6 +402,7 @@ Unit.AssertPathIsSymbolicLink, config_parse_unit_condition_path,
Unit.AssertPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, asserts) Unit.AssertPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, asserts)
Unit.AssertPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, asserts) Unit.AssertPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, asserts)
Unit.AssertPathIsEncrypted, config_parse_unit_condition_path, CONDITION_PATH_IS_ENCRYPTED, offsetof(Unit, asserts) Unit.AssertPathIsEncrypted, config_parse_unit_condition_path, CONDITION_PATH_IS_ENCRYPTED, offsetof(Unit, asserts)
Unit.AssertPathIsSocket, config_parse_unit_condition_path, CONDITION_PATH_IS_SOCKET, offsetof(Unit, asserts)
Unit.AssertDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, asserts) Unit.AssertDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, asserts)
Unit.AssertFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, asserts) Unit.AssertFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, asserts)
Unit.AssertFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, asserts) Unit.AssertFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, asserts)

View File

@ -9,6 +9,7 @@
#include "chase.h" #include "chase.h"
#include "devnum-util.h" #include "devnum-util.h"
#include "fileio.h" #include "fileio.h"
#include "glob-util.h"
#include "journal-internal.h" #include "journal-internal.h"
#include "journalctl.h" #include "journalctl.h"
#include "journalctl-filter.h" #include "journalctl-filter.h"
@ -71,6 +72,116 @@ static int add_dmesg(sd_journal *j) {
return sd_journal_add_conjunction(j); return sd_journal_add_conjunction(j);
} }
int journal_add_unit_matches(
sd_journal *j,
MatchUnitFlag flags,
UnitNameMangle mangle_flags,
char * const *system_units,
uid_t uid,
char * const *user_units) {
_cleanup_strv_free_ char **patterns = NULL;
bool added = false;
int r;
assert(j);
if (strv_isempty(system_units) && strv_isempty(user_units))
return 0;
STRV_FOREACH(i, system_units) {
_cleanup_free_ char *u = NULL;
r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | mangle_flags, &u);
if (r < 0)
return r;
if (string_is_glob(u)) {
r = strv_consume(&patterns, TAKE_PTR(u));
if (r < 0)
return r;
} else {
r = add_matches_for_unit_full(j, flags, u);
if (r < 0)
return r;
r = sd_journal_add_disjunction(j);
if (r < 0)
return r;
added = true;
}
}
if (!strv_isempty(patterns)) {
_cleanup_set_free_ Set *units = NULL;
r = get_possible_units(j, SYSTEM_UNITS_FULL, patterns, &units);
if (r < 0)
return r;
char *u;
SET_FOREACH(u, units) {
r = add_matches_for_unit_full(j, flags, u);
if (r < 0)
return r;
r = sd_journal_add_disjunction(j);
if (r < 0)
return r;
added = true;
}
}
patterns = strv_free(patterns);
STRV_FOREACH(i, user_units) {
_cleanup_free_ char *u = NULL;
r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | mangle_flags, &u);
if (r < 0)
return r;
if (string_is_glob(u)) {
r = strv_consume(&patterns, TAKE_PTR(u));
if (r < 0)
return r;
} else {
r = add_matches_for_user_unit_full(j, flags, uid, u);
if (r < 0)
return r;
r = sd_journal_add_disjunction(j);
if (r < 0)
return r;
added = true;
}
}
if (!strv_isempty(patterns)) {
_cleanup_set_free_ Set *units = NULL;
r = get_possible_units(j, USER_UNITS_FULL, patterns, &units);
if (r < 0)
return r;
char *u;
SET_FOREACH(u, units) {
r = add_matches_for_user_unit_full(j, flags, uid, u);
if (r < 0)
return r;
r = sd_journal_add_disjunction(j);
if (r < 0)
return r;
added = true;
}
}
/* Complain if the user request matches but nothing whatsoever was found, since otherwise everything
* would be matched. */
if (!added)
return -ENODATA;
return sd_journal_add_conjunction(j);
}
static int add_units(sd_journal *j) { static int add_units(sd_journal *j) {
MatchUnitFlag flags = MATCH_UNIT_ALL; MatchUnitFlag flags = MATCH_UNIT_ALL;
@ -82,7 +193,9 @@ static int add_units(sd_journal *j) {
if (arg_directory || arg_root || arg_file_stdin || arg_file || arg_machine) if (arg_directory || arg_root || arg_file_stdin || arg_file || arg_machine)
flags &= ~MATCH_UNIT_COREDUMP_UID; flags &= ~MATCH_UNIT_COREDUMP_UID;
return journal_add_unit_matches(j, flags, arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, arg_system_units, arg_user_units); return journal_add_unit_matches(j, flags, arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
arg_system_units,
UID_INVALID, arg_user_units);
} }
static int add_syslog_identifier(sd_journal *j) { static int add_syslog_identifier(sd_journal *j) {

View File

@ -3,4 +3,12 @@
#include "shared-forward.h" #include "shared-forward.h"
int journal_add_unit_matches(
sd_journal *j,
MatchUnitFlag flags,
UnitNameMangle mangle_flags,
char * const *system_units,
uid_t uid,
char * const *user_units);
int add_filters(sd_journal *j, char **matches); int add_filters(sd_journal *j, char **matches);

View File

@ -177,108 +177,6 @@ int get_possible_units(
return 0; return 0;
} }
int journal_add_unit_matches(sd_journal *j, MatchUnitFlag flags, UnitNameMangle mangle_flags, char **system_units, char **user_units) {
_cleanup_strv_free_ char **patterns = NULL;
bool added = false;
int r;
assert(j);
if (strv_isempty(system_units) && strv_isempty(user_units))
return 0;
STRV_FOREACH(i, system_units) {
_cleanup_free_ char *u = NULL;
r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | mangle_flags, &u);
if (r < 0)
return r;
if (string_is_glob(u)) {
r = strv_consume(&patterns, TAKE_PTR(u));
if (r < 0)
return r;
} else {
r = add_matches_for_unit_full(j, flags, u);
if (r < 0)
return r;
r = sd_journal_add_disjunction(j);
if (r < 0)
return r;
added = true;
}
}
if (!strv_isempty(patterns)) {
_cleanup_set_free_ Set *units = NULL;
r = get_possible_units(j, SYSTEM_UNITS_FULL, patterns, &units);
if (r < 0)
return r;
char *u;
SET_FOREACH(u, units) {
r = add_matches_for_unit_full(j, flags, u);
if (r < 0)
return r;
r = sd_journal_add_disjunction(j);
if (r < 0)
return r;
added = true;
}
}
patterns = strv_free(patterns);
STRV_FOREACH(i, user_units) {
_cleanup_free_ char *u = NULL;
r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | mangle_flags, &u);
if (r < 0)
return r;
if (string_is_glob(u)) {
r = strv_consume(&patterns, TAKE_PTR(u));
if (r < 0)
return r;
} else {
r = add_matches_for_user_unit_full(j, flags, u);
if (r < 0)
return r;
r = sd_journal_add_disjunction(j);
if (r < 0)
return r;
added = true;
}
}
if (!strv_isempty(patterns)) {
_cleanup_set_free_ Set *units = NULL;
r = get_possible_units(j, USER_UNITS_FULL, patterns, &units);
if (r < 0)
return r;
char *u;
SET_FOREACH(u, units) {
r = add_matches_for_user_unit_full(j, flags, u);
if (r < 0)
return r;
r = sd_journal_add_disjunction(j);
if (r < 0)
return r;
added = true;
}
}
/* Complain if the user request matches but nothing whatsoever was found, since otherwise everything
* would be matched. */
if (!added)
return -ENODATA;
return sd_journal_add_conjunction(j);
}
int acquire_unit(sd_journal *j, const char *option_name, const char **ret_unit, LogIdType *ret_type) { int acquire_unit(sd_journal *j, const char *option_name, const char **ret_unit, LogIdType *ret_type) {
size_t n; size_t n;
int r; int r;

View File

@ -3,7 +3,6 @@
#include "shared-forward.h" #include "shared-forward.h"
#include "logs-show.h" #include "logs-show.h"
#include "unit-name.h"
/* The lists below are supposed to return the superset of unit names possibly matched by rules added with /* The lists below are supposed to return the superset of unit names possibly matched by rules added with
* add_matches_for_unit() and add_matches_for_user_unit(). */ * add_matches_for_unit() and add_matches_for_user_unit(). */
@ -32,6 +31,5 @@ int acquire_journal(sd_journal **ret);
bool journal_boot_has_effect(sd_journal *j); bool journal_boot_has_effect(sd_journal *j);
int journal_acquire_boot(sd_journal *j); int journal_acquire_boot(sd_journal *j);
int get_possible_units(sd_journal *j, const char *fields, char * const *patterns, Set **ret); int get_possible_units(sd_journal *j, const char *fields, char * const *patterns, Set **ret);
int journal_add_unit_matches(sd_journal *j, MatchUnitFlag flags, UnitNameMangle mangle_flags, char **system_units, char **user_units);
int acquire_unit(sd_journal *j, const char *option_name, const char **ret_unit, LogIdType *ret_type); int acquire_unit(sd_journal *j, const char *option_name, const char **ret_unit, LogIdType *ret_type);
int journal_acquire_invocation(sd_journal *j); int journal_acquire_invocation(sd_journal *j);

View File

@ -4,25 +4,30 @@
#include "sd-varlink.h" #include "sd-varlink.h"
#include "journal-internal.h" #include "journal-internal.h"
#include "journalctl-util.h" #include "journalctl.h"
#include "journalctl-filter.h"
#include "journalctl-varlink-server.h" #include "journalctl-varlink-server.h"
#include "json-util.h" #include "json-util.h"
#include "log.h"
#include "logs-show.h" #include "logs-show.h"
#include "output-mode.h" #include "output-mode.h"
#include "runtime-scope.h"
#include "strv.h" #include "strv.h"
#include "unit-name.h" /* IWYU pragma: keep */
#include "user-util.h"
#include "varlink-util.h" #include "varlink-util.h"
typedef struct GetEntriesParameters { typedef struct GetEntriesParameters {
char **units; char **units;
char **user_units; char **user_units;
const char *namespace; const char *namespace;
uid_t uid;
int priority; int priority;
uint64_t limit; uint64_t limit;
} GetEntriesParameters; } GetEntriesParameters;
static void get_entries_parameters_done(GetEntriesParameters *p) { static void get_entries_parameters_done(GetEntriesParameters *p) {
assert(p); assert(p);
p->units = strv_free(p->units); p->units = strv_free(p->units);
p->user_units = strv_free(p->user_units); p->user_units = strv_free(p->user_units);
} }
@ -31,6 +36,7 @@ int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varl
static const sd_json_dispatch_field dispatch_table[] = { static const sd_json_dispatch_field dispatch_table[] = {
{ "units", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(GetEntriesParameters, units), 0 }, { "units", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(GetEntriesParameters, units), 0 },
{ "uid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(GetEntriesParameters, uid), 0 },
{ "userUnits", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(GetEntriesParameters, user_units), 0 }, { "userUnits", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(GetEntriesParameters, user_units), 0 },
{ "namespace", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(GetEntriesParameters, namespace), 0 }, { "namespace", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(GetEntriesParameters, namespace), 0 },
{ "priority", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_log_level, offsetof(GetEntriesParameters, priority), 0 }, { "priority", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_log_level, offsetof(GetEntriesParameters, priority), 0 },
@ -39,6 +45,7 @@ int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varl
}; };
_cleanup_(get_entries_parameters_done) GetEntriesParameters p = { _cleanup_(get_entries_parameters_done) GetEntriesParameters p = {
.uid = UID_INVALID,
.priority = -1, .priority = -1,
}; };
_cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_(sd_journal_closep) sd_journal *j = NULL;
@ -51,23 +58,26 @@ int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varl
if (r != 0) if (r != 0)
return r; return r;
if (arg_varlink_runtime_scope == RUNTIME_SCOPE_SYSTEM && p.user_units && !uid_is_valid(p.uid))
return sd_varlink_error_invalid_parameter_name(link, "uid");
/* systemd ships with sensible defaults for the system/user services and the socket permissions so we /* systemd ships with sensible defaults for the system/user services and the socket permissions so we
* do not need to do extra sd_varlink_get_peer_uid() or policykit checks here */ * do not need to do extra sd_varlink_get_peer_uid() or policykit checks here */
r = sd_journal_open_namespace(&j, p.namespace, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE); r = sd_journal_open_namespace(&j, p.namespace, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to open journal: %m"); return r;
r = journal_add_unit_matches(j, MATCH_UNIT_ALL, /* mangle_flags= */ 0, p.units, p.user_units); r = journal_add_unit_matches(j, MATCH_UNIT_ALL, /* mangle_flags= */ 0, p.units, p.uid, p.user_units);
if (r == -ENODATA) if (r == -ENODATA)
return sd_varlink_error(link, SD_VARLINK_ERROR_INVALID_PARAMETER, NULL); return sd_varlink_error(link, "io.systemd.JournalAccess.NoMatches", NULL);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to add unit matches: %m"); return r;
if (p.priority >= 0) { if (p.priority >= 0) {
for (int i = 0; i <= p.priority; i++) { for (int i = 0; i <= p.priority; i++) {
r = journal_add_matchf(j, "PRIORITY=%d", i); r = journal_add_matchf(j, "PRIORITY=%d", i);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to add priority match: %m"); return r;
} }
r = sd_journal_add_conjunction(j); r = sd_journal_add_conjunction(j);
@ -78,7 +88,7 @@ int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varl
/* this simulates "journalctl -n $p.limit" */ /* this simulates "journalctl -n $p.limit" */
r = sd_journal_seek_tail(j); r = sd_journal_seek_tail(j);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to seek to tail: %m"); return r;
/* FIXME: this restriction should be removed eventually */ /* FIXME: this restriction should be removed eventually */
if (p.limit > 10000) if (p.limit > 10000)
@ -86,6 +96,10 @@ int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varl
uint64_t n = p.limit == 0 ? 100 : p.limit; uint64_t n = p.limit == 0 ? 100 : p.limit;
r = sd_journal_previous_skip(j, n + 1);
if (r < 0)
return r;
r = varlink_set_sentinel(link, "io.systemd.JournalAccess.NoEntries"); r = varlink_set_sentinel(link, "io.systemd.JournalAccess.NoEntries");
if (r < 0) if (r < 0)
return r; return r;
@ -93,9 +107,9 @@ int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varl
for (uint64_t i = 0; i < n; i++) { for (uint64_t i = 0; i < n; i++) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL;
r = sd_journal_previous(j); r = sd_journal_next(j);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to iterate journal: %m"); return r;
if (r == 0) if (r == 0)
break; break;
@ -105,7 +119,7 @@ int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varl
if (r == 0) if (r == 0)
continue; /* skip corrupted entry */ continue; /* skip corrupted entry */
r = sd_varlink_replybo(link, SD_JSON_BUILD_PAIR("entry", SD_JSON_BUILD_VARIANT(entry))); r = sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_VARIANT("entry", entry));
if (r < 0) if (r < 0)
return r; return r;
} }

View File

@ -30,6 +30,7 @@
#include "parse-util.h" #include "parse-util.h"
#include "pcre2-util.h" #include "pcre2-util.h"
#include "pretty-print.h" #include "pretty-print.h"
#include "runtime-scope.h"
#include "set.h" #include "set.h"
#include "static-destruct.h" #include "static-destruct.h"
#include "string-table.h" #include "string-table.h"
@ -111,7 +112,9 @@ pcre2_code *arg_compiled_pattern = NULL;
PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO; PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO;
ImagePolicy *arg_image_policy = NULL; ImagePolicy *arg_image_policy = NULL;
bool arg_synchronize_on_exit = false; bool arg_synchronize_on_exit = false;
static bool arg_varlink = false; static bool arg_varlink = false;
RuntimeScope arg_varlink_runtime_scope = _RUNTIME_SCOPE_INVALID;
STATIC_DESTRUCTOR_REGISTER(arg_cursor, freep); STATIC_DESTRUCTOR_REGISTER(arg_cursor, freep);
STATIC_DESTRUCTOR_REGISTER(arg_cursor_file, freep); STATIC_DESTRUCTOR_REGISTER(arg_cursor_file, freep);
@ -486,7 +489,35 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
if (r > 0) { if (r > 0) {
arg_varlink = true; arg_varlink = true;
arg_pager_flags |= PAGER_DISABLE;
static const struct option varlink_options[] = {
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{}
};
while ((c = getopt_long(argc, argv, "", varlink_options, NULL)) >= 0)
switch (c) {
case ARG_SYSTEM:
arg_varlink_runtime_scope = RUNTIME_SCOPE_SYSTEM;
break;
case ARG_USER:
arg_varlink_runtime_scope = RUNTIME_SCOPE_USER;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
if (arg_varlink_runtime_scope < 0)
return log_error_errno(arg_varlink_runtime_scope, "Cannot run in Varlink mode with no runtime scope specified.");
return 1; return 1;
} }

View File

@ -94,3 +94,6 @@ extern bool arg_synchronize_on_exit;
static inline bool arg_lines_needs_seek_end(void) { static inline bool arg_lines_needs_seek_end(void) {
return arg_lines >= 0 && !arg_lines_oldest; return arg_lines >= 0 && !arg_lines_oldest;
} }
/* Only used for varlink server invocation */
extern RuntimeScope arg_varlink_runtime_scope;

View File

@ -693,20 +693,46 @@ int device_clone_with_db(sd_device *device, sd_device **ret) {
return 0; return 0;
} }
void device_cleanup_tags(sd_device *device) { int device_copy_all_tags(sd_device *dest, sd_device *src) {
int r;
assert(dest);
if (!src)
return 0;
FOREACH_DEVICE_TAG(src, tag) {
r = device_add_tag(dest, tag, /* both= */ false);
if (r < 0)
return r;
}
return 0;
}
int device_cleanup_tags(sd_device *device, sd_device *original) {
int r;
assert(device); assert(device);
device->all_tags = set_free(device->all_tags); _cleanup_set_free_ Set *saved = TAKE_PTR(device->all_tags);
device->current_tags = set_free(device->current_tags); device->current_tags = set_free(device->current_tags);
device->property_tags_outdated = true; device->property_tags_outdated = true;
device->tags_generation++; device->tags_generation++;
r = device_copy_all_tags(device, original);
if (r < 0) {
set_free_and_replace(device->all_tags, saved);
return r;
}
return 0;
} }
void device_cleanup_devlinks(sd_device *device) { void device_cleanup_devlinks(sd_device *device) {
assert(device); assert(device);
set_free(device->devlinks); device->devlinks = set_free(device->devlinks);
device->devlinks = NULL;
device->property_devlinks_outdated = true; device->property_devlinks_outdated = true;
device->devlinks_generation++; device->devlinks_generation++;
} }
@ -743,15 +769,9 @@ static int device_tag(sd_device *device, const char *tag, bool add) {
return 0; return 0;
} }
int device_tag_index(sd_device *device, sd_device *device_old, bool add) { int device_tag_index(sd_device *device, bool add) {
int r = 0; int r = 0;
if (add && device_old)
/* delete possible left-over tags */
FOREACH_DEVICE_TAG(device_old, tag)
if (!sd_device_has_tag(device, tag))
RET_GATHER(r, device_tag(device_old, tag, false));
FOREACH_DEVICE_TAG(device, tag) FOREACH_DEVICE_TAG(device, tag)
RET_GATHER(r, device_tag(device, tag, add)); RET_GATHER(r, device_tag(device, tag, add));

View File

@ -45,7 +45,8 @@ int device_add_property(sd_device *device, const char *key, const char *value);
int device_add_propertyf(sd_device *device, const char *key, const char *format, ...) _printf_(3, 4); int device_add_propertyf(sd_device *device, const char *key, const char *format, ...) _printf_(3, 4);
int device_add_tag(sd_device *device, const char *tag, bool both); int device_add_tag(sd_device *device, const char *tag, bool both);
void device_remove_tag(sd_device *device, const char *tag); void device_remove_tag(sd_device *device, const char *tag);
void device_cleanup_tags(sd_device *device); int device_copy_all_tags(sd_device *dest, sd_device *src);
int device_cleanup_tags(sd_device *device, sd_device *original);
void device_cleanup_devlinks(sd_device *device); void device_cleanup_devlinks(sd_device *device);
uint64_t device_get_properties_generation(sd_device *device); uint64_t device_get_properties_generation(sd_device *device);
@ -58,7 +59,7 @@ int device_get_properties_strv(sd_device *device, char ***ret);
int device_clone_with_db(sd_device *device, sd_device **ret); int device_clone_with_db(sd_device *device, sd_device **ret);
int device_tag_index(sd_device *device, sd_device *device_old, bool add); int device_tag_index(sd_device *device, bool add);
bool device_should_have_db(sd_device *device); bool device_should_have_db(sd_device *device);
int device_has_db(sd_device *device); int device_has_db(sd_device *device);
int device_update_db(sd_device *device); int device_update_db(sd_device *device);

View File

@ -992,6 +992,14 @@ static int condition_test_path_is_encrypted(Condition *c, char **env) {
return r > 0; return r > 0;
} }
static int condition_test_path_is_socket(Condition *c, char **env) {
assert(c);
assert(c->parameter);
assert(c->type == CONDITION_PATH_IS_SOCKET);
return is_socket(c->parameter) > 0;
}
static int condition_test_directory_not_empty(Condition *c, char **env) { static int condition_test_directory_not_empty(Condition *c, char **env) {
int r; int r;
@ -1233,6 +1241,7 @@ int condition_test(Condition *c, char **env) {
[CONDITION_PATH_IS_MOUNT_POINT] = condition_test_path_is_mount_point, [CONDITION_PATH_IS_MOUNT_POINT] = condition_test_path_is_mount_point,
[CONDITION_PATH_IS_READ_WRITE] = condition_test_path_is_read_write, [CONDITION_PATH_IS_READ_WRITE] = condition_test_path_is_read_write,
[CONDITION_PATH_IS_ENCRYPTED] = condition_test_path_is_encrypted, [CONDITION_PATH_IS_ENCRYPTED] = condition_test_path_is_encrypted,
[CONDITION_PATH_IS_SOCKET] = condition_test_path_is_socket,
[CONDITION_DIRECTORY_NOT_EMPTY] = condition_test_directory_not_empty, [CONDITION_DIRECTORY_NOT_EMPTY] = condition_test_directory_not_empty,
[CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty, [CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty,
[CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable, [CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable,
@ -1370,6 +1379,7 @@ static const char* const _condition_type_table[_CONDITION_TYPE_MAX] = {
[CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint", [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint",
[CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite", [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite",
[CONDITION_PATH_IS_ENCRYPTED] = "ConditionPathIsEncrypted", [CONDITION_PATH_IS_ENCRYPTED] = "ConditionPathIsEncrypted",
[CONDITION_PATH_IS_SOCKET] = "ConditionPathIsSocket",
[CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty", [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty",
[CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty", [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty",
[CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable", [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable",
@ -1425,6 +1435,7 @@ static const char* const _assert_type_table[_CONDITION_TYPE_MAX] = {
[CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint", [CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint",
[CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite", [CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite",
[CONDITION_PATH_IS_ENCRYPTED] = "AssertPathIsEncrypted", [CONDITION_PATH_IS_ENCRYPTED] = "AssertPathIsEncrypted",
[CONDITION_PATH_IS_SOCKET] = "AssertPathIsSocket",
[CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty", [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty",
[CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty", [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty",
[CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable", [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable",

View File

@ -34,6 +34,7 @@ typedef enum ConditionType {
CONDITION_PATH_IS_MOUNT_POINT, CONDITION_PATH_IS_MOUNT_POINT,
CONDITION_PATH_IS_READ_WRITE, CONDITION_PATH_IS_READ_WRITE,
CONDITION_PATH_IS_ENCRYPTED, CONDITION_PATH_IS_ENCRYPTED,
CONDITION_PATH_IS_SOCKET,
CONDITION_DIRECTORY_NOT_EMPTY, CONDITION_DIRECTORY_NOT_EMPTY,
CONDITION_FILE_NOT_EMPTY, CONDITION_FILE_NOT_EMPTY,
CONDITION_FILE_IS_EXECUTABLE, CONDITION_FILE_IS_EXECUTABLE,
@ -104,6 +105,7 @@ static inline bool condition_takes_path(ConditionType t) {
CONDITION_PATH_IS_MOUNT_POINT, CONDITION_PATH_IS_MOUNT_POINT,
CONDITION_PATH_IS_READ_WRITE, CONDITION_PATH_IS_READ_WRITE,
CONDITION_PATH_IS_ENCRYPTED, CONDITION_PATH_IS_ENCRYPTED,
CONDITION_PATH_IS_SOCKET,
CONDITION_DIRECTORY_NOT_EMPTY, CONDITION_DIRECTORY_NOT_EMPTY,
CONDITION_FILE_NOT_EMPTY, CONDITION_FILE_NOT_EMPTY,
CONDITION_FILE_IS_EXECUTABLE, CONDITION_FILE_IS_EXECUTABLE,

View File

@ -1193,17 +1193,20 @@ static int update_json_data_split(
return update_json_data(h, flags, name, eq + 1, size - fieldlen - 1); return update_json_data(h, flags, name, eq + 1, size - fieldlen - 1);
} }
int journal_entry_to_json(sd_journal *j, OutputFlags flags, const Set *output_fields, sd_json_variant **ret) { int journal_entry_to_json(
sd_journal *j,
OutputFlags flags,
const Set *output_fields,
sd_json_variant **ret) {
char usecbuf[CONST_MAX(DECIMAL_STR_MAX(usec_t), DECIMAL_STR_MAX(uint64_t))]; char usecbuf[CONST_MAX(DECIMAL_STR_MAX(usec_t), DECIMAL_STR_MAX(uint64_t))];
_cleanup_(sd_json_variant_unrefp) sd_json_variant *object = NULL;
_cleanup_hashmap_free_ Hashmap *h = NULL;
sd_id128_t journal_boot_id, seqnum_id; sd_id128_t journal_boot_id, seqnum_id;
_cleanup_free_ char *cursor = NULL; _cleanup_free_ char *cursor = NULL;
usec_t realtime, monotonic; usec_t realtime, monotonic;
sd_json_variant **array = NULL;
JsonData *d;
uint64_t seqnum; uint64_t seqnum;
const char *corrupted_what = NULL;
_cleanup_hashmap_free_ Hashmap *h = NULL;
_cleanup_free_ sd_json_variant **array = NULL;
size_t n = 0; size_t n = 0;
int r; int r;
@ -1214,32 +1217,32 @@ int journal_entry_to_json(sd_journal *j, OutputFlags flags, const Set *output_fi
r = sd_journal_get_cursor(j, &cursor); r = sd_journal_get_cursor(j, &cursor);
if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) { if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) {
log_debug_errno(r, "Unable to determine cursor of entry, assuming bad or partially written entry: %m"); corrupted_what = "cursor";
return 0; goto corrupted_skip;
} }
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to get cursor: %m"); return log_error_errno(r, "Failed to get cursor: %m");
r = sd_journal_get_realtime_usec(j, &realtime); r = sd_journal_get_realtime_usec(j, &realtime);
if (r == -EBADMSG) { if (r == -EBADMSG) {
log_debug_errno(r, "Unable to read realtime timestamp of entry, assuming bad or partially written entry: %m"); corrupted_what = "realtime timestamp";
return 0; goto corrupted_skip;
} }
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to get realtime timestamp: %m"); return log_error_errno(r, "Failed to get realtime timestamp: %m");
r = sd_journal_get_monotonic_usec(j, &monotonic, &journal_boot_id); r = sd_journal_get_monotonic_usec(j, &monotonic, &journal_boot_id);
if (r == -EBADMSG) { if (r == -EBADMSG) {
log_debug_errno(r, "Unable to read monotonic timestamp of entry, assuming bad or partially written entry: %m"); corrupted_what = "monotonic timestamp";
return 0; goto corrupted_skip;
} }
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to get monotonic timestamp: %m"); return log_error_errno(r, "Failed to get monotonic timestamp: %m");
r = sd_journal_get_seqnum(j, &seqnum, &seqnum_id); r = sd_journal_get_seqnum(j, &seqnum, &seqnum_id);
if (r == -EBADMSG) { if (r == -EBADMSG) {
log_debug_errno(r, "Unable to read sequence number of entry, assuming bad or partially written entry: %m"); corrupted_what = "sequence number";
return 0; goto corrupted_skip;
} }
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to get seqnum: %m"); return log_error_errno(r, "Failed to get seqnum: %m");
@ -1294,29 +1297,32 @@ int journal_entry_to_json(sd_journal *j, OutputFlags flags, const Set *output_fi
return r; return r;
} }
array = new(sd_json_variant*, hashmap_size(h)*2); array = new(sd_json_variant*, hashmap_size(h) * 2);
if (!array) if (!array)
return log_oom(); return log_oom();
CLEANUP_ARRAY(array, n, sd_json_variant_unref_many); JsonData *d;
HASHMAP_FOREACH(d, h) { HASHMAP_FOREACH(d, h) {
assert(sd_json_variant_elements(d->values) > 0); assert(sd_json_variant_elements(d->values) > 0);
array[n++] = sd_json_variant_ref(d->name); array[n++] = d->name;
if (sd_json_variant_elements(d->values) == 1) if (sd_json_variant_elements(d->values) == 1)
array[n++] = sd_json_variant_ref(sd_json_variant_by_index(d->values, 0)); array[n++] = sd_json_variant_by_index(d->values, 0);
else else
array[n++] = sd_json_variant_ref(d->values); array[n++] = d->values;
} }
r = sd_json_variant_new_object(&object, array, n); r = sd_json_variant_new_object(ret, array, n);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to allocate JSON object: %m"); return log_error_errno(r, "Failed to allocate JSON object: %m");
*ret = TAKE_PTR(object);
return 1; return 1;
corrupted_skip:
log_debug_errno(r, "Unable to determine %s of entry, assuming bad or partially written entry: %m", ASSERT_PTR(corrupted_what));
*ret = NULL;
return 0;
} }
static int output_json( static int output_json(
@ -1761,13 +1767,15 @@ int add_matches_for_unit_full(sd_journal *j, MatchUnitFlag flags, const char *un
return r; return r;
} }
int add_matches_for_user_unit_full(sd_journal *j, MatchUnitFlag flags, const char *unit) { int add_matches_for_user_unit_full(sd_journal *j, MatchUnitFlag flags, uid_t uid, const char *unit) {
uid_t uid = getuid();
int r; int r;
assert(j); assert(j);
assert(unit); assert(unit);
if (uid == UID_INVALID)
uid = getuid();
(void) ( (void) (
/* Look for messages from the user service itself */ /* Look for messages from the user service itself */
(r = journal_add_match_pair(j, "_SYSTEMD_USER_UNIT", unit)) || (r = journal_add_match_pair(j, "_SYSTEMD_USER_UNIT", unit)) ||
@ -2004,7 +2012,7 @@ static int set_matches_for_discover_id(
return add_matches_for_unit_full(j, /* flags= */ 0, unit); return add_matches_for_unit_full(j, /* flags= */ 0, unit);
if (type == LOG_USER_UNIT_INVOCATION_ID) if (type == LOG_USER_UNIT_INVOCATION_ID)
return add_matches_for_user_unit_full(j, /* flags= */ 0, unit); return add_matches_for_user_unit_full(j, /* flags= */ 0, UID_INVALID, unit);
return -EINVAL; return -EINVAL;
} }

View File

@ -57,9 +57,9 @@ int add_matches_for_unit_full(sd_journal *j, MatchUnitFlag flags, const char *un
static inline int add_matches_for_unit(sd_journal *j, const char *unit) { static inline int add_matches_for_unit(sd_journal *j, const char *unit) {
return add_matches_for_unit_full(j, MATCH_UNIT_ALL, unit); return add_matches_for_unit_full(j, MATCH_UNIT_ALL, unit);
} }
int add_matches_for_user_unit_full(sd_journal *j, MatchUnitFlag flags, const char *unit); int add_matches_for_user_unit_full(sd_journal *j, MatchUnitFlag flags, uid_t uid, const char *unit);
static inline int add_matches_for_user_unit(sd_journal *j, const char *unit) { static inline int add_matches_for_user_unit(sd_journal *j, const char *unit) {
return add_matches_for_user_unit_full(j, MATCH_UNIT_ALL, unit); return add_matches_for_user_unit_full(j, MATCH_UNIT_ALL, UID_INVALID, unit);
} }
int show_journal_by_unit( int show_journal_by_unit(

View File

@ -4,10 +4,6 @@
#include <sys/mount.h> #include <sys/mount.h>
#include <unistd.h> #include <unistd.h>
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
#include "log.h" #include "log.h"
#include "mkfs-util.h" #include "mkfs-util.h"
#include "mount-util.h" #include "mount-util.h"
@ -15,12 +11,10 @@
#include "path-util.h" #include "path-util.h"
#include "process-util.h" #include "process-util.h"
#include "recurse-dir.h" #include "recurse-dir.h"
#include "rm-rf.h"
#include "stat-util.h" #include "stat-util.h"
#include "stdio-util.h" #include "stdio-util.h"
#include "string-util.h" #include "string-util.h"
#include "strv.h" #include "strv.h"
#include "tmpfile-util.h"
#include "utf8.h" #include "utf8.h"
int mkfs_exists(const char *fstype) { int mkfs_exists(const char *fstype) {
@ -171,154 +165,6 @@ static int do_mcopy(const char *node, const char *root) {
return 0; return 0;
} }
typedef struct ProtofileData {
FILE *file;
bool has_filename_with_spaces;
const char *tmpdir;
} ProtofileData;
static int protofile_print_item(
RecurseDirEvent event,
const char *path,
int dir_fd,
int inode_fd,
const struct dirent *de,
const struct statx *sx,
void *userdata) {
ProtofileData *data = ASSERT_PTR(userdata);
_cleanup_free_ char *copy = NULL;
int r;
if (event == RECURSE_DIR_LEAVE) {
fputs("$\n", data->file);
return 0;
}
if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
return RECURSE_DIR_CONTINUE;
char type = S_ISDIR(sx->stx_mode) ? 'd' :
S_ISREG(sx->stx_mode) ? '-' :
S_ISLNK(sx->stx_mode) ? 'l' :
S_ISFIFO(sx->stx_mode) ? 'p' :
S_ISBLK(sx->stx_mode) ? 'b' :
S_ISCHR(sx->stx_mode) ? 'c' : 0;
if (type == 0)
return RECURSE_DIR_CONTINUE;
/* The protofile format does not support spaces in filenames as whitespace is used as a token
* delimiter. To work around this limitation, mkfs.xfs allows escaping whitespace by using the /
* character (which isn't allowed in filenames and as such can be used to escape whitespace). See
* https://lore.kernel.org/linux-xfs/20230222090303.h6tujm7y32gjhgal@andromeda/T/#m8066b3e7d62a080ee7434faac4861d944e64493b
* for more information. */
if (strchr(de->d_name, ' ')) {
copy = strdup(de->d_name);
if (!copy)
return log_oom();
string_replace_char(copy, ' ', '/');
data->has_filename_with_spaces = true;
}
fprintf(data->file, "%s %c%c%c%03o "UID_FMT" "GID_FMT" ",
copy ?: de->d_name,
type,
sx->stx_mode & S_ISUID ? 'u' : '-',
sx->stx_mode & S_ISGID ? 'g' : '-',
(unsigned) (sx->stx_mode & 0777),
sx->stx_uid, sx->stx_gid);
if (S_ISREG(sx->stx_mode)) {
_cleanup_free_ char *p = NULL;
/* While we can escape whitespace in the filename, we cannot escape whitespace in the source
* path, so hack around that by creating a symlink to the path in a temporary directory and
* using the symlink as the source path instead. */
if (strchr(path, ' ')) {
r = tempfn_random_child(data->tmpdir, "mkfs-xfs", &p);
if (r < 0)
return log_error_errno(r, "Failed to generate random child name in %s: %m", data->tmpdir);
if (symlink(path, p) < 0)
return log_error_errno(errno, "Failed to symlink %s to %s: %m", p, path);
}
fputs(p ?: path, data->file);
} else if (S_ISLNK(sx->stx_mode)) {
_cleanup_free_ char *p = NULL;
r = readlinkat_malloc(dir_fd, de->d_name, &p);
if (r < 0)
return log_error_errno(r, "Failed to read symlink %s: %m", path);
/* If we have a symlink to a path with whitespace in it, we're out of luck, as there's no way
* to encode that in the mkfs.xfs protofile format. */
if (strchr(p, ' '))
return log_error_errno(r, "Symlinks to paths containing whitespace are not supported by mkfs.xfs: %m");
fputs(p, data->file);
} else if (S_ISBLK(sx->stx_mode) || S_ISCHR(sx->stx_mode))
fprintf(data->file, "%" PRIu32 " %" PRIu32, sx->stx_rdev_major, sx->stx_rdev_minor);
fputc('\n', data->file);
return RECURSE_DIR_CONTINUE;
}
static int make_protofile(const char *root, char **ret_path, bool *ret_has_filename_with_spaces, char **ret_tmpdir) {
_cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
_cleanup_fclose_ FILE *f = NULL;
_cleanup_(unlink_and_freep) char *p = NULL;
struct ProtofileData data = {};
const char *vt;
int r;
assert(ret_path);
assert(ret_has_filename_with_spaces);
assert(ret_tmpdir);
r = var_tmp_dir(&vt);
if (r < 0)
return log_error_errno(r, "Failed to get persistent temporary directory: %m");
r = fopen_temporary_child(vt, &f, &p);
if (r < 0)
return log_error_errno(r, "Failed to open temporary file: %m");
/* Explicitly use /tmp here because this directory cannot have spaces its path. */
r = mkdtemp_malloc("/tmp/systemd-mkfs-XXXXXX", &tmpdir);
if (r < 0)
return log_error_errno(r, "Failed to create temporary directory: %m");
data.file = f;
data.tmpdir = tmpdir;
fputs("/\n"
"0 0\n"
"d--755 0 0\n", f);
r = recurse_dir_at(AT_FDCWD, root, STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID, UINT_MAX,
RECURSE_DIR_SORT, protofile_print_item, &data);
if (r < 0)
return log_error_errno(r, "Failed to recurse through %s: %m", root);
fputs("$\n", f);
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to flush %s: %m", p);
*ret_path = TAKE_PTR(p);
*ret_has_filename_with_spaces = data.has_filename_with_spaces;
*ret_tmpdir = TAKE_PTR(tmpdir);
return 0;
}
int make_filesystem( int make_filesystem(
const char *node, const char *node,
const char *fstype, const char *fstype,
@ -333,8 +179,6 @@ int make_filesystem(
_cleanup_free_ char *mkfs = NULL, *mangled_label = NULL; _cleanup_free_ char *mkfs = NULL, *mangled_label = NULL;
_cleanup_strv_free_ char **argv = NULL, **env = NULL; _cleanup_strv_free_ char **argv = NULL, **env = NULL;
_cleanup_(rm_rf_physical_and_freep) char *protofile_tmpdir = NULL;
_cleanup_(unlink_and_freep) char *protofile = NULL;
char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {}; char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {};
int stdio_fds[3] = { -EBADF, STDERR_FILENO, STDERR_FILENO}; int stdio_fds[3] = { -EBADF, STDERR_FILENO, STDERR_FILENO};
ForkFlags fork_flags = FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT| ForkFlags fork_flags = FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|
@ -560,26 +404,8 @@ int make_filesystem(
if (!FLAGS_SET(flags, MKFS_DISCARD) && strv_extend(&argv, "-K") < 0) if (!FLAGS_SET(flags, MKFS_DISCARD) && strv_extend(&argv, "-K") < 0)
return log_oom(); return log_oom();
if (root) { if (root && strv_extend_many(&argv, "-p", root) < 0)
bool has_filename_with_spaces = false;
_cleanup_free_ char *protofile_with_opt = NULL;
r = make_protofile(root, &protofile, &has_filename_with_spaces, &protofile_tmpdir);
if (r < 0)
return r;
/* Gross hack to make mkfs.xfs interpret slashes as spaces so we can encode filenames
* with spaces in the protofile format. */
if (has_filename_with_spaces)
protofile_with_opt = strjoin("slashes_are_spaces=1,", protofile);
else
protofile_with_opt = strdup(protofile);
if (!protofile_with_opt)
return -ENOMEM;
if (strv_extend_many(&argv, "-p", protofile_with_opt) < 0)
return log_oom(); return log_oom();
}
if (sector_size > 0) { if (sector_size > 0) {
if (strv_extend(&argv, "-s") < 0) if (strv_extend(&argv, "-s") < 0)

View File

@ -26,6 +26,7 @@ typedef enum DnssecMode DnssecMode;
typedef enum Fido2EnrollFlags Fido2EnrollFlags; typedef enum Fido2EnrollFlags Fido2EnrollFlags;
typedef enum KeySourceType KeySourceType; typedef enum KeySourceType KeySourceType;
typedef enum LabelFixFlags LabelFixFlags; typedef enum LabelFixFlags LabelFixFlags;
typedef enum MatchUnitFlag MatchUnitFlag;
typedef enum MountInNamespaceFlags MountInNamespaceFlags; typedef enum MountInNamespaceFlags MountInNamespaceFlags;
typedef enum NamePolicy NamePolicy; typedef enum NamePolicy NamePolicy;
typedef enum OutputFlags OutputFlags; typedef enum OutputFlags OutputFlags;

View File

@ -274,6 +274,29 @@ _noreturn_ void log_test_failed_internal(const char *file, int line, const char
}) })
#endif #endif
#ifdef __COVERITY__
# define ASSERT_OK_NE(expr1, expr2) \
({ \
typeof(expr1) _expr1 = (expr1); \
typeof(expr2) _expr2 = (expr2); \
__coverity_check__(_expr1 != _expr2); \
_expr1; \
})
#else
# define ASSERT_OK_NE(expr1, expr2) \
({ \
typeof(expr1) _expr1 = (expr1); \
typeof(expr2) _expr2 = (expr2); \
if (_expr1 < 0) \
log_test_failed("Expected \"%s\" to succeed, but got error: %"PRIiMAX"/%s", \
#expr1, (intmax_t) _expr1, ERRNO_NAME(_expr1)); \
if (_expr1 == _expr2) \
log_test_failed("Expected \"%s != %s\", got %"PRIiMAX" != %"PRIiMAX, \
#expr1, #expr2, (intmax_t) _expr1, (intmax_t) _expr2); \
_expr1; \
})
#endif
/* For functions that return a boolean on success and set errno on failure. */ /* For functions that return a boolean on success and set errno on failure. */
#ifdef __COVERITY__ #ifdef __COVERITY__
# define ASSERT_OK_ERRNO(expr) \ # define ASSERT_OK_ERRNO(expr) \

View File

@ -7,6 +7,8 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
SD_VARLINK_REQUIRES_MORE, SD_VARLINK_REQUIRES_MORE,
SD_VARLINK_FIELD_COMMENT("Show messages for the specified systemd units (e.g. ['foo.service'])."), SD_VARLINK_FIELD_COMMENT("Show messages for the specified systemd units (e.g. ['foo.service'])."),
SD_VARLINK_DEFINE_INPUT(units, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), SD_VARLINK_DEFINE_INPUT(units, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("UID to match user units for"),
SD_VARLINK_DEFINE_INPUT(uid, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Show messages for the specified user units (e.g. ['foo.service'])."), SD_VARLINK_FIELD_COMMENT("Show messages for the specified user units (e.g. ['foo.service'])."),
SD_VARLINK_DEFINE_INPUT(userUnits, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), SD_VARLINK_DEFINE_INPUT(userUnits, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("If specified, shows the log data of the specified namespace, otherwise the default namespace."), SD_VARLINK_FIELD_COMMENT("If specified, shows the log data of the specified namespace, otherwise the default namespace."),
@ -16,8 +18,9 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
SD_VARLINK_FIELD_COMMENT("Maximum number of entries to return. Defaults to 100, capped at 10000."), SD_VARLINK_FIELD_COMMENT("Maximum number of entries to return. Defaults to 100, capped at 10000."),
SD_VARLINK_DEFINE_INPUT(limit, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(limit, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The journal entry in flat JSON format, matching journalctl --output=json."), SD_VARLINK_FIELD_COMMENT("The journal entry in flat JSON format, matching journalctl --output=json."),
SD_VARLINK_DEFINE_OUTPUT(entry, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE)); SD_VARLINK_DEFINE_OUTPUT(entry, SD_VARLINK_OBJECT, 0));
static SD_VARLINK_DEFINE_ERROR(NoMatches);
static SD_VARLINK_DEFINE_ERROR(NoEntries); static SD_VARLINK_DEFINE_ERROR(NoEntries);
SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_DEFINE_INTERFACE(
@ -26,5 +29,7 @@ SD_VARLINK_DEFINE_INTERFACE(
SD_VARLINK_INTERFACE_COMMENT("Journal log read APIs"), SD_VARLINK_INTERFACE_COMMENT("Journal log read APIs"),
SD_VARLINK_SYMBOL_COMMENT("Retrieve journal log entries, optionally filtered by unit, priority, etc."), SD_VARLINK_SYMBOL_COMMENT("Retrieve journal log entries, optionally filtered by unit, priority, etc."),
&vl_method_GetEntries, &vl_method_GetEntries,
SD_VARLINK_SYMBOL_COMMENT("No matches found for specified unit patterns"),
&vl_error_NoMatches,
SD_VARLINK_SYMBOL_COMMENT("No journal entries matched the specified filters."), SD_VARLINK_SYMBOL_COMMENT("No journal entries matched the specified filters."),
&vl_error_NoEntries); &vl_error_NoEntries);

View File

@ -64,7 +64,11 @@
<allow send_destination="org.freedesktop.sysupdate1" <allow send_destination="org.freedesktop.sysupdate1"
send_interface="org.freedesktop.sysupdate1.Target" send_interface="org.freedesktop.sysupdate1.Target"
send_member="Update"/> send_member="Acquire"/>
<allow send_destination="org.freedesktop.sysupdate1"
send_interface="org.freedesktop.sysupdate1.Target"
send_member="Install"/>
<allow send_destination="org.freedesktop.sysupdate1" <allow send_destination="org.freedesktop.sysupdate1"
send_interface="org.freedesktop.sysupdate1.Target" send_interface="org.freedesktop.sysupdate1.Target"

View File

@ -100,7 +100,8 @@ typedef enum JobType {
JOB_LIST, JOB_LIST,
JOB_DESCRIBE, JOB_DESCRIBE,
JOB_CHECK_NEW, JOB_CHECK_NEW,
JOB_UPDATE, JOB_ACQUIRE,
JOB_INSTALL,
JOB_VACUUM, JOB_VACUUM,
JOB_DESCRIBE_FEATURE, JOB_DESCRIBE_FEATURE,
_JOB_TYPE_MAX, _JOB_TYPE_MAX,
@ -121,7 +122,7 @@ struct Job {
JobType type; JobType type;
bool offline; bool offline;
char *version; /* Passed into sysupdate for JOB_DESCRIBE and JOB_UPDATE */ char *version; /* Passed into sysupdate for JOB_DESCRIBE, JOB_ACQUIRE and JOB_INSTALL */
char *feature; /* Passed into sysupdate for JOB_DESCRIBE_FEATURE */ char *feature; /* Passed into sysupdate for JOB_DESCRIBE_FEATURE */
unsigned progress_percent; unsigned progress_percent;
@ -153,7 +154,8 @@ static const char* const job_type_table[_JOB_TYPE_MAX] = {
[JOB_LIST] = "list", [JOB_LIST] = "list",
[JOB_DESCRIBE] = "describe", [JOB_DESCRIBE] = "describe",
[JOB_CHECK_NEW] = "check-new", [JOB_CHECK_NEW] = "check-new",
[JOB_UPDATE] = "update", [JOB_ACQUIRE] = "acquire",
[JOB_INSTALL] = "install",
[JOB_VACUUM] = "vacuum", [JOB_VACUUM] = "vacuum",
[JOB_DESCRIBE_FEATURE] = "describe-feature", [JOB_DESCRIBE_FEATURE] = "describe-feature",
}; };
@ -220,6 +222,11 @@ static int job_new(JobType type, Target *t, sd_bus_message *msg, JobComplete com
return 0; return 0;
} }
/* Is Job in the set of jobs which require Target.busy to be set so they run exclusively? */
static bool job_requires_busy(Job *j) {
return IN_SET(j->type, JOB_ACQUIRE, JOB_INSTALL, JOB_VACUUM);
}
static int job_parse_child_output(int _fd, sd_json_variant **ret) { static int job_parse_child_output(int _fd, sd_json_variant **ret) {
_cleanup_close_ int fd = ASSERT_FD(_fd); /* Take ownership of the passed fd */ _cleanup_close_ int fd = ASSERT_FD(_fd); /* Take ownership of the passed fd */
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
@ -332,7 +339,7 @@ static int job_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata)
assert(s); assert(s);
assert(si); assert(si);
if (IN_SET(j->type, JOB_UPDATE, JOB_VACUUM)) { if (job_requires_busy(j)) {
assert(j->target->busy); assert(j->target->busy);
j->target->busy = false; j->target->busy = false;
} }
@ -430,7 +437,7 @@ static int job_start(Job *j) {
assert(j); assert(j);
if (IN_SET(j->type, JOB_UPDATE, JOB_VACUUM) && j->target->busy) if (job_requires_busy(j) && j->target->busy)
return log_notice_errno(SYNTHETIC_ERRNO(EBUSY), "Target %s busy, ignoring job.", j->target->name); return log_notice_errno(SYNTHETIC_ERRNO(EBUSY), "Target %s busy, ignoring job.", j->target->name);
stdout_fd = memfd_new("sysupdate-stdout"); stdout_fd = memfd_new("sysupdate-stdout");
@ -453,8 +460,8 @@ static int job_start(Job *j) {
NULL, /* maybe --verify=no */ NULL, /* maybe --verify=no */
NULL, /* maybe --component=, --root=, or --image= */ NULL, /* maybe --component=, --root=, or --image= */
NULL, /* maybe --offline */ NULL, /* maybe --offline */
NULL, /* list, check-new, update, vacuum, features */ NULL, /* list, check-new, acquire, update, vacuum, features */
NULL, /* maybe version (for list, update), maybe feature (features) */ NULL, /* maybe version (for list, acquire, update), maybe feature (features) */
NULL NULL
}; };
size_t k = 2; size_t k = 2;
@ -479,7 +486,7 @@ static int job_start(Job *j) {
if (target_arg) if (target_arg)
cmd[k++] = target_arg; cmd[k++] = target_arg;
if (j->offline) if (j->offline || j->type == JOB_INSTALL) /* install is implemented as `update --offline` */
cmd[k++] = "--offline"; cmd[k++] = "--offline";
switch (j->type) { switch (j->type) {
@ -497,8 +504,13 @@ static int job_start(Job *j) {
cmd[k++] = "check-new"; cmd[k++] = "check-new";
break; break;
case JOB_UPDATE: case JOB_ACQUIRE:
cmd[k++] = "update"; cmd[k++] = "acquire";
cmd[k++] = empty_to_null(j->version);
break;
case JOB_INSTALL:
cmd[k++] = "update"; /* install is implemented as `update --offline` */
cmd[k++] = empty_to_null(j->version); cmd[k++] = empty_to_null(j->version);
break; break;
@ -547,7 +559,7 @@ static int job_start(Job *j) {
j->stdout_fd = TAKE_FD(stdout_fd); j->stdout_fd = TAKE_FD(stdout_fd);
if (IN_SET(j->type, JOB_UPDATE, JOB_VACUUM)) if (job_requires_busy(j))
j->target->busy = true; j->target->busy = true;
return 0; return 0;
@ -581,7 +593,8 @@ static int job_method_cancel(sd_bus_message *msg, void *userdata, sd_bus_error *
action = "org.freedesktop.sysupdate1.check"; action = "org.freedesktop.sysupdate1.check";
break; break;
case JOB_UPDATE: case JOB_ACQUIRE:
case JOB_INSTALL:
if (j->version) if (j->version)
action = "org.freedesktop.sysupdate1.update-to-version"; action = "org.freedesktop.sysupdate1.update-to-version";
else else
@ -1065,7 +1078,7 @@ static int target_method_check_new(sd_bus_message *msg, void *userdata, sd_bus_e
return 1; return 1;
} }
static int target_method_update_finished_early( static int target_method_acquire_finished_early(
sd_bus_message *msg, sd_bus_message *msg,
const Job *j, const Job *j,
sd_json_variant *json, sd_json_variant *json,
@ -1073,12 +1086,12 @@ static int target_method_update_finished_early(
/* Called when job finishes w/ a successful exit code, but before any work begins. /* Called when job finishes w/ a successful exit code, but before any work begins.
* This happens when there is no candidate (i.e. we're already up-to-date), or * This happens when there is no candidate (i.e. we're already up-to-date), or
* specified update is already installed. */ * specified update is already acquired. */
return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE, return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE,
"Job exited successfully with no work to do, assume already updated"); "Job exited successfully with no work to do, assume already acquired");
} }
static int target_method_update_detach(sd_bus_message *msg, const Job *j) { static int target_method_acquire_detach(sd_bus_message *msg, const Job *j) {
int r; int r;
assert(msg); assert(msg);
@ -1091,7 +1104,7 @@ static int target_method_update_detach(sd_bus_message *msg, const Job *j) {
return 0; return 0;
} }
static int target_method_update(sd_bus_message *msg, void *userdata, sd_bus_error *error) { static int target_method_acquire(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
Target *t = ASSERT_PTR(userdata); Target *t = ASSERT_PTR(userdata);
_cleanup_(job_freep) Job *j = NULL; _cleanup_(job_freep) Job *j = NULL;
const char *version, *action; const char *version, *action;
@ -1107,6 +1120,8 @@ static int target_method_update(sd_bus_message *msg, void *userdata, sd_bus_erro
if (flags != 0) if (flags != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0"); return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
/* We dont have a separate polkit action for acquire/install as they are both effectively (part of)
* an update anyway. */
if (isempty(version)) if (isempty(version))
action = "org.freedesktop.sysupdate1.update"; action = "org.freedesktop.sysupdate1.update";
else else
@ -1130,10 +1145,94 @@ static int target_method_update(sd_bus_message *msg, void *userdata, sd_bus_erro
if (r == 0) if (r == 0)
return 1; /* Will call us back */ return 1; /* Will call us back */
r = job_new(JOB_UPDATE, t, msg, target_method_update_finished_early, &j); r = job_new(JOB_ACQUIRE, t, msg, target_method_acquire_finished_early, &j);
if (r < 0) if (r < 0)
return r; return r;
j->detach_cb = target_method_update_detach; j->detach_cb = target_method_acquire_detach;
j->version = strdup(version);
if (!j->version)
return -ENOMEM;
r = job_start(j);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
TAKE_PTR(j);
return 1;
}
static int target_method_install_finished_early(
sd_bus_message *msg,
const Job *j,
sd_json_variant *json,
sd_bus_error *error) {
/* Called when job finishes w/ a successful exit code, but before any work begins.
* This happens when there is no candidate (i.e. we're already up-to-date), or
* specified update is already installed. */
return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE,
"Job exited successfully with no work to do, assume already installed");
}
static int target_method_install_detach(sd_bus_message *msg, const Job *j) {
int r;
assert(msg);
assert(j);
r = sd_bus_reply_method_return(msg, "sto", j->version, j->id, j->object_path);
if (r < 0)
return bus_log_parse_error(r);
return 0;
}
static int target_method_install(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
Target *t = ASSERT_PTR(userdata);
_cleanup_(job_freep) Job *j = NULL;
const char *version, *action;
uint64_t flags;
int r;
assert(msg);
r = sd_bus_message_read(msg, "st", &version, &flags);
if (r < 0)
return r;
if (flags != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
/* We dont have a separate polkit action for acquire/install as they are both effectively (part of)
* an update anyway. */
if (isempty(version))
action = "org.freedesktop.sysupdate1.update";
else
action = "org.freedesktop.sysupdate1.update-to-version";
const char *details[] = {
"class", target_class_to_string(t->class),
"name", t->name,
"version", version,
NULL
};
r = bus_verify_polkit_async(
msg,
action,
details,
&t->manager->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = job_new(JOB_INSTALL, t, msg, target_method_install_finished_early, &j);
if (r < 0)
return r;
j->detach_cb = target_method_install_detach;
j->version = strdup(version); j->version = strdup(version);
if (!j->version) if (!j->version)
@ -1580,10 +1679,16 @@ static const sd_bus_vtable target_vtable[] = {
target_method_check_new, target_method_check_new,
SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("Update", SD_BUS_METHOD_WITH_ARGS("Acquire",
SD_BUS_ARGS("s", new_version, "t", flags), SD_BUS_ARGS("s", new_version, "t", flags),
SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path), SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
target_method_update, target_method_acquire,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("Install",
SD_BUS_ARGS("s", new_version, "t", flags),
SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
target_method_install,
SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("Vacuum", SD_BUS_METHOD_WITH_ARGS("Vacuum",

View File

@ -72,6 +72,9 @@ typedef struct Operation {
sd_event_source *job_interrupt_source; sd_event_source *job_interrupt_source;
sd_bus_slot *job_properties_slot; sd_bus_slot *job_properties_slot;
sd_bus_slot *job_finished_slot; sd_bus_slot *job_finished_slot;
/* Only used for Acquire()/Install() operations: */
char *acquired_version;
} Operation; } Operation;
static Operation* operation_free(Operation *p) { static Operation* operation_free(Operation *p) {
@ -86,6 +89,7 @@ static Operation* operation_free(Operation *p) {
assert_se(sd_event_exit(p->event, 0) >= 0); assert_se(sd_event_exit(p->event, 0) >= 0);
free(p->job_path); free(p->job_path);
free(p->acquired_version);
sd_event_source_disable_unref(p->job_interrupt_source); sd_event_source_disable_unref(p->job_interrupt_source);
sd_bus_slot_unref(p->job_properties_slot); sd_bus_slot_unref(p->job_properties_slot);
@ -334,6 +338,8 @@ typedef struct DescribeParams {
bool newest; bool newest;
bool available; bool available;
bool installed; bool installed;
bool partial;
bool pending;
bool obsolete; bool obsolete;
bool protected; bool protected;
bool incomplete; bool incomplete;
@ -369,6 +375,8 @@ static int parse_describe(sd_bus_message *reply, Version *ret) {
{ "newest", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, newest), 0 }, { "newest", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, newest), 0 },
{ "available", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, available), 0 }, { "available", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, available), 0 },
{ "installed", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, installed), 0 }, { "installed", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, installed), 0 },
{ "partial", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, partial), 0 },
{ "pending", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, pending), 0 },
{ "obsolete", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, obsolete), 0 }, { "obsolete", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, obsolete), 0 },
{ "protected", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, protected), 0 }, { "protected", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, protected), 0 },
{ "incomplete", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, incomplete), 0 }, { "incomplete", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(DescribeParams, incomplete), 0 },
@ -386,6 +394,8 @@ static int parse_describe(sd_bus_message *reply, Version *ret) {
SET_FLAG(p.v.flags, UPDATE_NEWEST, p.newest); SET_FLAG(p.v.flags, UPDATE_NEWEST, p.newest);
SET_FLAG(p.v.flags, UPDATE_AVAILABLE, p.available); SET_FLAG(p.v.flags, UPDATE_AVAILABLE, p.available);
SET_FLAG(p.v.flags, UPDATE_INSTALLED, p.installed); SET_FLAG(p.v.flags, UPDATE_INSTALLED, p.installed);
SET_FLAG(p.v.flags, UPDATE_PARTIAL, p.partial);
SET_FLAG(p.v.flags, UPDATE_PENDING, p.pending);
SET_FLAG(p.v.flags, UPDATE_OBSOLETE, p.obsolete); SET_FLAG(p.v.flags, UPDATE_OBSOLETE, p.obsolete);
SET_FLAG(p.v.flags, UPDATE_PROTECTED, p.protected); SET_FLAG(p.v.flags, UPDATE_PROTECTED, p.protected);
SET_FLAG(p.v.flags, UPDATE_INCOMPLETE, p.incomplete); SET_FLAG(p.v.flags, UPDATE_INCOMPLETE, p.incomplete);
@ -804,6 +814,7 @@ static int verb_check(int argc, char **argv, void *userdata) {
} }
#define UPDATE_PROGRESS_FAILED INT_MIN #define UPDATE_PROGRESS_FAILED INT_MIN
#define UPDATE_PROGRESS_ACQUIRED (INT_MAX - 1)
#define UPDATE_PROGRESS_DONE INT_MAX #define UPDATE_PROGRESS_DONE INT_MAX
/* Make sure it doesn't overlap w/ errno values */ /* Make sure it doesn't overlap w/ errno values */
assert_cc(UPDATE_PROGRESS_FAILED < -ERRNO_MAX); assert_cc(UPDATE_PROGRESS_FAILED < -ERRNO_MAX);
@ -853,6 +864,10 @@ static int update_render_progress(sd_event_source *source, void *userdata) {
clear_progress_bar_unbuffered(target); clear_progress_bar_unbuffered(target);
fprintf(stderr, "%s: %s %s\n", target, RED_CROSS_MARK(), STRERROR(progress)); fprintf(stderr, "%s: %s %s\n", target, RED_CROSS_MARK(), STRERROR(progress));
total += 100; total += 100;
} else if (progress == UPDATE_PROGRESS_ACQUIRED) {
clear_progress_bar_unbuffered(target);
fprintf(stderr, "%s: %s Installing\n", target, glyph(GLYPH_DOWNLOAD));
total += 100;
} else if (progress == UPDATE_PROGRESS_DONE) { } else if (progress == UPDATE_PROGRESS_DONE) {
clear_progress_bar_unbuffered(target); clear_progress_bar_unbuffered(target);
fprintf(stderr, "%s: %s Done\n", target, GREEN_CHECK_MARK()); fprintf(stderr, "%s: %s Done\n", target, GREEN_CHECK_MARK());
@ -920,7 +935,60 @@ static int update_properties_changed(sd_bus_message *m, void *userdata, sd_bus_e
return 0; return 0;
} }
static int update_finished(sd_bus_message *m, void *userdata, sd_bus_error *error) { static int update_install_started(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
_cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
OrderedHashmap *map = ASSERT_PTR(op->userdata);
const sd_bus_error *e;
const char *job_path;
int r;
assert(reply);
e = sd_bus_message_get_error(reply);
if (e) {
r = -sd_bus_error_get_errno(e);
r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(r));
if (r < 0)
log_debug_errno(r, "Failed to update hashmap: %m");
return 0;
}
r = sd_bus_message_read(reply, "sto", NULL, &op->job_id, &job_path);
if (r < 0)
return bus_log_parse_error(r);
r = free_and_strdup_warn(&op->job_path, job_path);
if (r < 0)
return r;
/* Update this job in the hashmap. */
r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(UPDATE_PROGRESS_ACQUIRED));
if (r < 0)
log_debug_errno(r, "Failed to update hashmap: %m");
/* Register for progress notifications for this Install() D-Bus call; previously
* op->job_properties_slot was registered for progress notifications for the Acquire() D-Bus call. */
sd_bus_slot_unref(TAKE_PTR(op->job_properties_slot));
r = sd_bus_match_signal_async(
op->bus,
&op->job_properties_slot,
bus_sysupdate_mgr->destination,
job_path,
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
update_properties_changed,
NULL,
op);
if (r < 0)
return log_bus_error(r, NULL, op->target_id, "listen for PropertiesChanged");
TAKE_PTR(op); /* update_install_finished/update_interrupted take ownership of the data */
return 0;
}
static int update_install_finished(sd_bus_message *m, void *userdata, sd_bus_error *error) {
_cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata); _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
OrderedHashmap *map = ASSERT_PTR(op->userdata); OrderedHashmap *map = ASSERT_PTR(op->userdata);
uint64_t id; uint64_t id;
@ -951,6 +1019,64 @@ static int update_finished(sd_bus_message *m, void *userdata, sd_bus_error *erro
return 0; return 0;
} }
static int update_acquire_finished(sd_bus_message *m, void *userdata, sd_bus_error *error) {
_cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
OrderedHashmap *map = ASSERT_PTR(op->userdata);
uint64_t id;
int r, status;
assert(m);
r = sd_bus_message_read(m, "toi", &id, NULL, &status);
if (r < 0) {
bus_log_parse_error_debug(r);
return 0;
}
if (id != op->job_id) {
TAKE_PTR(op);
return 0;
}
if (status == 0) /* success */
status = UPDATE_PROGRESS_ACQUIRED;
else if (status > 0) /* exit status without errno */
status = UPDATE_PROGRESS_FAILED; /* i.e. EXIT_FAILURE */
/* else errno */
r = ordered_hashmap_replace(map, op->target_id, INT_TO_PTR(status));
if (r < 0)
log_debug_errno(r, "Failed to update hashmap: %m");
/* Renew the JobRemoved notification for the Install() call instead. */
sd_bus_slot_unref(op->job_finished_slot);
r = bus_match_signal_async(
op->bus, &op->job_finished_slot, bus_sysupdate_mgr, "JobRemoved", update_install_finished, NULL, op);
if (r < 0)
return log_bus_error(r, NULL, op->target_id, "listen for JobRemoved");
/* With the Acquire() call finished, immediately call Install() to deploy the downloaded update.
* This reuses the same Operation struct so the progress reporting continues to be done in the same
* slot in the terminal. */
r = sd_bus_call_method_async(
op->bus,
NULL,
bus_sysupdate_mgr->destination,
op->target_path,
SYSUPDATE_TARGET_INTERFACE,
"Install",
update_install_started,
op,
"st",
op->acquired_version,
0LU);
if (r < 0)
return log_bus_error(r, NULL, op->target_id, "call Install");
TAKE_PTR(op);
return 0;
}
static int update_interrupted(sd_event_source *source, void *userdata) { static int update_interrupted(sd_event_source *source, void *userdata) {
/* Since the event loop is exiting, we will never receive the JobRemoved /* Since the event loop is exiting, we will never receive the JobRemoved
* signal. So, we must free the userdata here. */ * signal. So, we must free the userdata here. */
@ -959,6 +1085,8 @@ static int update_interrupted(sd_event_source *source, void *userdata) {
OrderedHashmap *map = ASSERT_PTR(op->userdata); OrderedHashmap *map = ASSERT_PTR(op->userdata);
int r; int r;
/* This call should work regardless of whether were cancelling the Acquire() call or the Install()
* call. */
r = sd_bus_call_method(op->bus, r = sd_bus_call_method(op->bus,
bus_sysupdate_mgr->destination, bus_sysupdate_mgr->destination,
op->job_path, op->job_path,
@ -977,7 +1105,7 @@ static int update_interrupted(sd_event_source *source, void *userdata) {
return 0; return 0;
} }
static int update_started(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { static int update_acquire_started(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) {
_cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata); _cleanup_(operation_freep) Operation *op = ASSERT_PTR(userdata);
OrderedHashmap *map = ASSERT_PTR(op->userdata); OrderedHashmap *map = ASSERT_PTR(op->userdata);
const sd_bus_error *e; const sd_bus_error *e;
@ -1008,6 +1136,12 @@ static int update_started(sd_bus_message *reply, void *userdata, sd_bus_error *r
op->job_path = strdup(job_path); op->job_path = strdup(job_path);
if (!op->job_path) if (!op->job_path)
return log_oom(); return log_oom();
/* Store the version for the subsequent Install() call */
op->acquired_version = strdup(new_version);
if (!op->acquired_version)
return log_oom();
if (isempty(new_version)) if (isempty(new_version))
new_version = "latest"; new_version = "latest";
@ -1047,7 +1181,7 @@ static int update_started(sd_bus_message *reply, void *userdata, sd_bus_error *r
if (r < 0) if (r < 0)
return log_bus_error(r, NULL, op->target_id, "listen for PropertiesChanged"); return log_bus_error(r, NULL, op->target_id, "listen for PropertiesChanged");
TAKE_PTR(op); /* update_finished/update_interrupted take ownership of the data */ TAKE_PTR(op); /* update_acquire_finished/update_interrupted take ownership of the data */
return 0; return 0;
} }
@ -1094,7 +1228,7 @@ static int do_update(sd_bus *bus, char **targets) {
/* Sign up for notification when the associated job finishes */ /* Sign up for notification when the associated job finishes */
r = bus_match_signal_async( r = bus_match_signal_async(
op->bus, &op->job_finished_slot, bus_sysupdate_mgr, "JobRemoved", update_finished, NULL, op); op->bus, &op->job_finished_slot, bus_sysupdate_mgr, "JobRemoved", update_acquire_finished, NULL, op);
if (r < 0) if (r < 0)
return log_bus_error(r, NULL, op->target_id, "listen for JobRemoved"); return log_bus_error(r, NULL, op->target_id, "listen for JobRemoved");
@ -1104,14 +1238,14 @@ static int do_update(sd_bus *bus, char **targets) {
bus_sysupdate_mgr->destination, bus_sysupdate_mgr->destination,
target_paths[i], target_paths[i],
SYSUPDATE_TARGET_INTERFACE, SYSUPDATE_TARGET_INTERFACE,
"Update", "Acquire",
update_started, update_acquire_started,
op, op,
"st", "st",
versions[i], versions[i],
0LU); 0LU);
if (r < 0) if (r < 0)
return log_bus_error(r, NULL, targets[i], "call Update"); return log_bus_error(r, NULL, targets[i], "call Acquire");
TAKE_PTR(op); TAKE_PTR(op);
remaining++; remaining++;

View File

@ -1105,6 +1105,7 @@ TEST(unit_properties) {
"ConditionPathIsMountPoint=|foo", "ConditionPathIsMountPoint=|foo",
"ConditionPathIsReadWrite=|foo", "ConditionPathIsReadWrite=|foo",
"ConditionPathIsEncrypted=|foo", "ConditionPathIsEncrypted=|foo",
"ConditionPathIsSocket=|foo",
"ConditionDirectoryNotEmpty=|foo", "ConditionDirectoryNotEmpty=|foo",
"ConditionFileNotEmpty=|foo", "ConditionFileNotEmpty=|foo",
"ConditionFileIsExecutable=|foo", "ConditionFileIsExecutable=|foo",
@ -1139,6 +1140,7 @@ TEST(unit_properties) {
"AssertPathIsMountPoint=|foo", "AssertPathIsMountPoint=|foo",
"AssertPathIsReadWrite=|foo", "AssertPathIsReadWrite=|foo",
"AssertPathIsEncrypted=|foo", "AssertPathIsEncrypted=|foo",
"AssertPathIsSocket=|foo",
"AssertDirectoryNotEmpty=|foo", "AssertDirectoryNotEmpty=|foo",
"AssertFileNotEmpty=|foo", "AssertFileNotEmpty=|foo",
"AssertFileIsExecutable=|foo", "AssertFileIsExecutable=|foo",

View File

@ -110,6 +110,16 @@ TEST(condition_test_path) {
ASSERT_OK_ZERO(condition_test(condition, environ)); ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition); condition_free(condition);
if (access("/run/dbus/system_bus_socket", F_OK) >= 0) {
ASSERT_NOT_NULL((condition = condition_new(CONDITION_PATH_IS_SOCKET, "/run/dbus/system_bus_socket", false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
}
ASSERT_NOT_NULL((condition = condition_new(CONDITION_PATH_IS_SOCKET, "/sys", false, false)));
ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_PATH_IS_SYMBOLIC_LINK, "/dev/stdout", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_PATH_IS_SYMBOLIC_LINK, "/dev/stdout", false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ)); ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition); condition_free(condition);
@ -187,15 +197,15 @@ TEST(condition_test_ac_power) {
Condition *condition; Condition *condition;
ASSERT_NOT_NULL((condition = condition_new(CONDITION_AC_POWER, "true", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_AC_POWER, "true", false, false)));
assert_se(condition_test(condition, environ) == on_ac_power()); ASSERT_OK_EQ(condition_test(condition, environ), on_ac_power());
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_AC_POWER, "false", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_AC_POWER, "false", false, false)));
assert_se(condition_test(condition, environ) != on_ac_power()); ASSERT_OK_NE(condition_test(condition, environ), on_ac_power());
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_AC_POWER, "false", false, true))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_AC_POWER, "false", false, true)));
assert_se(condition_test(condition, environ) == on_ac_power()); ASSERT_OK_EQ(condition_test(condition, environ), on_ac_power());
condition_free(condition); condition_free(condition);
} }
@ -714,8 +724,8 @@ TEST(condition_test_credential) {
_cleanup_free_ char *d1 = NULL, *d2 = NULL, *j = NULL; _cleanup_free_ char *d1 = NULL, *d2 = NULL, *j = NULL;
Condition *condition; Condition *condition;
assert_se(free_and_strdup(&d1, getenv("CREDENTIALS_DIRECTORY")) >= 0); ASSERT_OK(free_and_strdup(&d1, getenv("CREDENTIALS_DIRECTORY")));
assert_se(free_and_strdup(&d2, getenv("ENCRYPTED_CREDENTIALS_DIRECTORY")) >= 0); ASSERT_OK(free_and_strdup(&d2, getenv("ENCRYPTED_CREDENTIALS_DIRECTORY")));
ASSERT_OK_ERRNO(unsetenv("CREDENTIALS_DIRECTORY")); ASSERT_OK_ERRNO(unsetenv("CREDENTIALS_DIRECTORY"));
ASSERT_OK_ERRNO(unsetenv("ENCRYPTED_CREDENTIALS_DIRECTORY")); ASSERT_OK_ERRNO(unsetenv("ENCRYPTED_CREDENTIALS_DIRECTORY"));
@ -729,8 +739,8 @@ TEST(condition_test_credential) {
ASSERT_OK_ZERO(condition_test(condition, environ)); ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition); condition_free(condition);
assert_se(mkdtemp_malloc(NULL, &n1) >= 0); ASSERT_OK(mkdtemp_malloc(NULL, &n1));
assert_se(mkdtemp_malloc(NULL, &n2) >= 0); ASSERT_OK(mkdtemp_malloc(NULL, &n2));
ASSERT_OK_ERRNO(setenv("CREDENTIALS_DIRECTORY", n1, /* overwrite= */ true)); ASSERT_OK_ERRNO(setenv("CREDENTIALS_DIRECTORY", n1, /* overwrite= */ true));
ASSERT_OK_ERRNO(setenv("ENCRYPTED_CREDENTIALS_DIRECTORY", n2, /* overwrite= */ true)); ASSERT_OK_ERRNO(setenv("ENCRYPTED_CREDENTIALS_DIRECTORY", n2, /* overwrite= */ true));
@ -740,20 +750,20 @@ TEST(condition_test_credential) {
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((j = path_join(n1, "existing"))); ASSERT_NOT_NULL((j = path_join(n1, "existing")));
assert_se(touch(j) >= 0); ASSERT_OK(touch(j));
ASSERT_NOT_NULL((condition = condition_new(CONDITION_CREDENTIAL, "existing", /* trigger= */ false, /* negate= */ false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_CREDENTIAL, "existing", /* trigger= */ false, /* negate= */ false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ)); ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition); condition_free(condition);
free(j); free(j);
ASSERT_NOT_NULL((j = path_join(n2, "existing-encrypted"))); ASSERT_NOT_NULL((j = path_join(n2, "existing-encrypted")));
assert_se(touch(j) >= 0); ASSERT_OK(touch(j));
ASSERT_NOT_NULL((condition = condition_new(CONDITION_CREDENTIAL, "existing-encrypted", /* trigger= */ false, /* negate= */ false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_CREDENTIAL, "existing-encrypted", /* trigger= */ false, /* negate= */ false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ)); ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition); condition_free(condition);
assert_se(set_unset_env("CREDENTIALS_DIRECTORY", d1, /* overwrite= */ true) >= 0); ASSERT_OK(set_unset_env("CREDENTIALS_DIRECTORY", d1, /* overwrite= */ true));
assert_se(set_unset_env("ENCRYPTED_CREDENTIALS_DIRECTORY", d2, /* overwrite= */ true) >= 0); ASSERT_OK(set_unset_env("ENCRYPTED_CREDENTIALS_DIRECTORY", d2, /* overwrite= */ true));
} }
#if defined(__i386__) || defined(__x86_64__) #if defined(__i386__) || defined(__x86_64__)
@ -782,35 +792,35 @@ TEST(condition_test_security) {
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "selinux", false, true))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "selinux", false, true)));
assert_se(condition_test(condition, environ) != mac_selinux_use()); ASSERT_OK_NE(condition_test(condition, environ), mac_selinux_use());
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "apparmor", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "apparmor", false, false)));
assert_se(condition_test(condition, environ) == mac_apparmor_use()); ASSERT_OK_EQ(condition_test(condition, environ), mac_apparmor_use());
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "tomoyo", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "tomoyo", false, false)));
assert_se(condition_test(condition, environ) == mac_tomoyo_use()); ASSERT_OK_EQ(condition_test(condition, environ), mac_tomoyo_use());
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "ima", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "ima", false, false)));
assert_se(condition_test(condition, environ) == use_ima()); ASSERT_OK_EQ(condition_test(condition, environ), use_ima());
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "smack", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "smack", false, false)));
assert_se(condition_test(condition, environ) == mac_smack_use()); ASSERT_OK_EQ(condition_test(condition, environ), mac_smack_use());
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "audit", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "audit", false, false)));
assert_se(condition_test(condition, environ) == use_audit()); ASSERT_OK_EQ(condition_test(condition, environ), use_audit());
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "uefi-secureboot", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "uefi-secureboot", false, false)));
assert_se(condition_test(condition, environ) == is_efi_secure_boot()); ASSERT_OK_EQ(condition_test(condition, environ), is_efi_secure_boot());
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "cvm", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_SECURITY, "cvm", false, false)));
assert_se(condition_test(condition, environ) == ASSERT_OK_EQ(condition_test(condition, environ),
(detect_confidential_virtualization() != CONFIDENTIAL_VIRTUALIZATION_NONE)); (detect_confidential_virtualization() != CONFIDENTIAL_VIRTUALIZATION_NONE));
condition_free(condition); condition_free(condition);
} }
@ -844,19 +854,19 @@ TEST(condition_test_virtualization) {
ASSERT_NOT_NULL((condition = condition_new(CONDITION_VIRTUALIZATION, "container", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_VIRTUALIZATION, "container", false, false)));
r = condition_test(condition, environ); r = condition_test(condition, environ);
log_info("ConditionVirtualization=container → %i", r); log_info("ConditionVirtualization=container → %i", r);
assert_se(r == !!detect_container()); ASSERT_OK_EQ(r, !!detect_container());
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_VIRTUALIZATION, "vm", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_VIRTUALIZATION, "vm", false, false)));
r = condition_test(condition, environ); r = condition_test(condition, environ);
log_info("ConditionVirtualization=vm → %i", r); log_info("ConditionVirtualization=vm → %i", r);
assert_se(r == (detect_vm() && !detect_container())); ASSERT_OK_EQ(r, (detect_vm() && !detect_container()));
condition_free(condition); condition_free(condition);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_VIRTUALIZATION, "private-users", false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_VIRTUALIZATION, "private-users", false, false)));
r = condition_test(condition, environ); r = condition_test(condition, environ);
log_info("ConditionVirtualization=private-users → %i", r); log_info("ConditionVirtualization=private-users → %i", r);
assert_se(r == !!running_in_userns()); ASSERT_OK_EQ(r, !!running_in_userns());
condition_free(condition); condition_free(condition);
NULSTR_FOREACH(virt, NULSTR_FOREACH(virt,
@ -963,13 +973,12 @@ TEST(condition_test_group) {
ASSERT_OK_POSITIVE(r); ASSERT_OK_POSITIVE(r);
condition_free(condition); condition_free(condition);
ngroups_max = sysconf(_SC_NGROUPS_MAX); ngroups_max = ASSERT_OK_ERRNO(sysconf(_SC_NGROUPS_MAX));
assert_se(ngroups_max > 0); ASSERT_GT(ngroups_max, 0);
gids = newa(gid_t, ngroups_max); gids = newa(gid_t, ngroups_max);
ngroups = getgroups(ngroups_max, gids); ngroups = ASSERT_OK_ERRNO(getgroups(ngroups_max, gids));
assert_se(ngroups >= 0);
max_gid = getgid(); max_gid = getgid();
for (i = 0; i < ngroups; i++) { for (i = 0; i < ngroups; i++) {
@ -1019,15 +1028,12 @@ TEST(condition_test_group) {
static void test_condition_test_cpus_one(const char *s, bool result) { static void test_condition_test_cpus_one(const char *s, bool result) {
Condition *condition; Condition *condition;
int r;
log_debug("%s=%s", condition_type_to_string(CONDITION_CPUS), s); log_debug("%s=%s", condition_type_to_string(CONDITION_CPUS), s);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_CPUS, s, false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_CPUS, s, false, false)));
r = condition_test(condition, environ); ASSERT_OK_EQ(condition_test(condition, environ), result);
assert_se(r >= 0);
assert_se(r == result);
condition_free(condition); condition_free(condition);
} }
@ -1035,8 +1041,7 @@ TEST(condition_test_cpus) {
_cleanup_free_ char *t = NULL; _cleanup_free_ char *t = NULL;
int cpus; int cpus;
cpus = cpus_in_affinity_mask(); cpus = ASSERT_OK(cpus_in_affinity_mask());
assert_se(cpus >= 0);
test_condition_test_cpus_one("> 0", true); test_condition_test_cpus_one("> 0", true);
test_condition_test_cpus_one(">= 0", true); test_condition_test_cpus_one(">= 0", true);
@ -1052,42 +1057,39 @@ TEST(condition_test_cpus) {
test_condition_test_cpus_one("!= 100000", true); test_condition_test_cpus_one("!= 100000", true);
test_condition_test_cpus_one("<= 100000", true); test_condition_test_cpus_one("<= 100000", true);
assert_se(asprintf(&t, "= %i", cpus) >= 0); ASSERT_OK(asprintf(&t, "= %i", cpus));
test_condition_test_cpus_one(t, true); test_condition_test_cpus_one(t, true);
t = mfree(t); t = mfree(t);
assert_se(asprintf(&t, "<= %i", cpus) >= 0); ASSERT_OK(asprintf(&t, "<= %i", cpus));
test_condition_test_cpus_one(t, true); test_condition_test_cpus_one(t, true);
t = mfree(t); t = mfree(t);
assert_se(asprintf(&t, ">= %i", cpus) >= 0); ASSERT_OK(asprintf(&t, ">= %i", cpus));
test_condition_test_cpus_one(t, true); test_condition_test_cpus_one(t, true);
t = mfree(t); t = mfree(t);
assert_se(asprintf(&t, "!= %i", cpus) >= 0); ASSERT_OK(asprintf(&t, "!= %i", cpus));
test_condition_test_cpus_one(t, false); test_condition_test_cpus_one(t, false);
t = mfree(t); t = mfree(t);
assert_se(asprintf(&t, "< %i", cpus) >= 0); ASSERT_OK(asprintf(&t, "< %i", cpus));
test_condition_test_cpus_one(t, false); test_condition_test_cpus_one(t, false);
t = mfree(t); t = mfree(t);
assert_se(asprintf(&t, "> %i", cpus) >= 0); ASSERT_OK(asprintf(&t, "> %i", cpus));
test_condition_test_cpus_one(t, false); test_condition_test_cpus_one(t, false);
t = mfree(t); t = mfree(t);
} }
static void test_condition_test_memory_one(const char *s, bool result) { static void test_condition_test_memory_one(const char *s, bool result) {
Condition *condition; Condition *condition;
int r;
log_debug("%s=%s", condition_type_to_string(CONDITION_MEMORY), s); log_debug("%s=%s", condition_type_to_string(CONDITION_MEMORY), s);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_MEMORY, s, false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_MEMORY, s, false, false)));
r = condition_test(condition, environ); ASSERT_OK_EQ(condition_test(condition, environ), result);
assert_se(r >= 0);
assert_se(r == result);
condition_free(condition); condition_free(condition);
} }
@ -1132,42 +1134,39 @@ TEST(condition_test_memory) {
test_condition_test_memory_one("!= 100 T 1 G", true); test_condition_test_memory_one("!= 100 T 1 G", true);
test_condition_test_memory_one("<= 100 T 1 G", true); test_condition_test_memory_one("<= 100 T 1 G", true);
assert_se(asprintf(&t, "= %" PRIu64, memory) >= 0); ASSERT_OK(asprintf(&t, "= %" PRIu64, memory));
test_condition_test_memory_one(t, true); test_condition_test_memory_one(t, true);
t = mfree(t); t = mfree(t);
assert_se(asprintf(&t, "<= %" PRIu64, memory) >= 0); ASSERT_OK(asprintf(&t, "<= %" PRIu64, memory));
test_condition_test_memory_one(t, true); test_condition_test_memory_one(t, true);
t = mfree(t); t = mfree(t);
assert_se(asprintf(&t, ">= %" PRIu64, memory) >= 0); ASSERT_OK(asprintf(&t, ">= %" PRIu64, memory));
test_condition_test_memory_one(t, true); test_condition_test_memory_one(t, true);
t = mfree(t); t = mfree(t);
assert_se(asprintf(&t, "!= %" PRIu64, memory) >= 0); ASSERT_OK(asprintf(&t, "!= %" PRIu64, memory));
test_condition_test_memory_one(t, false); test_condition_test_memory_one(t, false);
t = mfree(t); t = mfree(t);
assert_se(asprintf(&t, "< %" PRIu64, memory) >= 0); ASSERT_OK(asprintf(&t, "< %" PRIu64, memory));
test_condition_test_memory_one(t, false); test_condition_test_memory_one(t, false);
t = mfree(t); t = mfree(t);
assert_se(asprintf(&t, "> %" PRIu64, memory) >= 0); ASSERT_OK(asprintf(&t, "> %" PRIu64, memory));
test_condition_test_memory_one(t, false); test_condition_test_memory_one(t, false);
t = mfree(t); t = mfree(t);
} }
static void test_condition_test_environment_one(const char *s, bool result) { static void test_condition_test_environment_one(const char *s, bool result) {
Condition *condition; Condition *condition;
int r;
log_debug("%s=%s", condition_type_to_string(CONDITION_ENVIRONMENT), s); log_debug("%s=%s", condition_type_to_string(CONDITION_ENVIRONMENT), s);
ASSERT_NOT_NULL((condition = condition_new(CONDITION_ENVIRONMENT, s, false, false))); ASSERT_NOT_NULL((condition = condition_new(CONDITION_ENVIRONMENT, s, false, false)));
r = condition_test(condition, environ); ASSERT_OK_EQ(condition_test(condition, environ), result);
assert_se(r >= 0);
assert_se(r == result);
condition_free(condition); condition_free(condition);
} }
@ -1463,13 +1462,11 @@ TEST(condition_test_kernel_module_loaded) {
Condition *condition; Condition *condition;
int r; int r;
condition = condition_new(CONDITION_KERNEL_MODULE_LOADED, "", /* trigger= */ false, /* negate= */ false); condition = ASSERT_NOT_NULL(condition_new(CONDITION_KERNEL_MODULE_LOADED, "", /* trigger= */ false, /* negate= */ false));
assert_se(condition);
ASSERT_OK_ZERO(condition_test(condition, environ)); ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition); condition_free(condition);
condition = condition_new(CONDITION_KERNEL_MODULE_LOADED, "..", /* trigger= */ false, /* negate= */ false); condition = ASSERT_NOT_NULL(condition_new(CONDITION_KERNEL_MODULE_LOADED, "..", /* trigger= */ false, /* negate= */ false));
assert_se(condition);
ASSERT_OK_ZERO(condition_test(condition, environ)); ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition); condition_free(condition);
@ -1477,8 +1474,7 @@ TEST(condition_test_kernel_module_loaded) {
return (void) log_tests_skipped("/sys/module not available, skipping."); return (void) log_tests_skipped("/sys/module not available, skipping.");
FOREACH_STRING(m, "random", "vfat", "fat", "cec", "binfmt_misc", "binfmt-misc") { FOREACH_STRING(m, "random", "vfat", "fat", "cec", "binfmt_misc", "binfmt-misc") {
condition = condition_new(CONDITION_KERNEL_MODULE_LOADED, m, /* trigger= */ false, /* negate= */ false); condition = ASSERT_NOT_NULL(condition_new(CONDITION_KERNEL_MODULE_LOADED, m, /* trigger= */ false, /* negate= */ false));
assert_se(condition);
r = condition_test(condition, environ); r = condition_test(condition, environ);
ASSERT_OK(r); ASSERT_OK(r);
condition_free(condition); condition_free(condition);
@ -1486,8 +1482,7 @@ TEST(condition_test_kernel_module_loaded) {
log_notice("kmod %s is loaded: %s", m, yes_no(r)); log_notice("kmod %s is loaded: %s", m, yes_no(r));
} }
condition = condition_new(CONDITION_KERNEL_MODULE_LOADED, "idefinitelydontexist", /* trigger= */ false, /* negate= */ false); condition = ASSERT_NOT_NULL(condition_new(CONDITION_KERNEL_MODULE_LOADED, "idefinitelydontexist", /* trigger= */ false, /* negate= */ false));
assert_se(condition);
ASSERT_OK_ZERO(condition_test(condition, environ)); ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition); condition_free(condition);
} }

View File

@ -270,6 +270,20 @@ static int dump_event_json(UdevEvent *event, sd_json_format_flags_t flags, FILE
return r; return r;
} }
tags = strv_free(tags);
FOREACH_DEVICE_CURRENT_TAG(dev, tag) {
r = strv_extend(&tags, tag);
if (r < 0)
return r;
}
if (!strv_isempty(tags)) {
r = sd_json_variant_set_field_strv(&v, "currentTags", strv_sort(tags));
if (r < 0)
return r;
}
char **properties; char **properties;
if (device_get_properties_strv(dev, &properties) >= 0 && !strv_isempty(properties)) { if (device_get_properties_strv(dev, &properties) >= 0 && !strv_isempty(properties)) {
r = sd_json_variant_set_field_strv(&v, "properties", strv_sort(properties)); r = sd_json_variant_set_field_strv(&v, "properties", strv_sort(properties));
@ -417,6 +431,12 @@ int dump_event(UdevEvent *event, sd_json_format_flags_t flags, FILE *f) {
fprintf(f, " %s\n", tag); fprintf(f, " %s\n", tag);
} }
if (sd_device_get_current_tag_first(dev)) {
fprintf(f, "%sCurrent Tags:%s\n", ansi_highlight(), ansi_normal());
FOREACH_DEVICE_CURRENT_TAG(dev, tag)
fprintf(f, " %s\n", tag);
}
char **properties; char **properties;
if (device_get_properties_strv(dev, &properties) >= 0 && !strv_isempty(properties)) { if (device_get_properties_strv(dev, &properties) >= 0 && !strv_isempty(properties)) {
bool space = true; bool space = true;

View File

@ -63,7 +63,7 @@ int device_broadcast_on_error(sd_device *dev, sd_device_monitor *monitor) {
/* delete state from disk */ /* delete state from disk */
(void) device_delete_db(dev); (void) device_delete_db(dev);
(void) device_tag_index(dev, /* device_old= */ NULL, /* add= */ false); (void) device_tag_index(dev, /* add= */ false);
r = device_monitor_send(monitor, /* destination= */ NULL, dev); r = device_monitor_send(monitor, /* destination= */ NULL, dev);
if (r < 0) { if (r < 0) {

View File

@ -310,7 +310,7 @@ static int event_execute_rules_on_remove(UdevEvent *event, UdevRules *rules) {
log_device_debug_errno(dev, r, "Failed to read database under /run/udev/data/: %m"); log_device_debug_errno(dev, r, "Failed to read database under /run/udev/data/: %m");
if (EVENT_MODE_DESTRUCTIVE(event)) { if (EVENT_MODE_DESTRUCTIVE(event)) {
r = device_tag_index(dev, NULL, false); r = device_tag_index(dev, /* add= */ false);
if (r < 0) if (r < 0)
log_device_debug_errno(dev, r, "Failed to remove corresponding tag files under /run/udev/tag/, ignoring: %m"); log_device_debug_errno(dev, r, "Failed to remove corresponding tag files under /run/udev/tag/, ignoring: %m");
@ -329,23 +329,6 @@ static int event_execute_rules_on_remove(UdevEvent *event, UdevRules *rules) {
return r; return r;
} }
static int copy_all_tags(sd_device *d, sd_device *s) {
int r;
assert(d);
if (!s)
return 0;
FOREACH_DEVICE_TAG(s, tag) {
r = device_add_tag(d, tag, false);
if (r < 0)
return r;
}
return 0;
}
static int update_clone(UdevEvent *event) { static int update_clone(UdevEvent *event) {
sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev_db_clone); sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev_db_clone);
int r; int r;
@ -398,7 +381,7 @@ int udev_event_execute_rules(UdevEvent *event, UdevRules *rules) {
if (r < 0) if (r < 0)
return log_device_debug_errno(dev, r, "Failed to clone sd_device object: %m"); return log_device_debug_errno(dev, r, "Failed to clone sd_device object: %m");
r = copy_all_tags(dev, event->dev_db_clone); r = device_copy_all_tags(dev, event->dev_db_clone);
if (r < 0) if (r < 0)
log_device_warning_errno(dev, r, "Failed to copy all tags from old database entry, ignoring: %m"); log_device_warning_errno(dev, r, "Failed to copy all tags from old database entry, ignoring: %m");
@ -433,7 +416,7 @@ int udev_event_execute_rules(UdevEvent *event, UdevRules *rules) {
if (EVENT_MODE_DESTRUCTIVE(event)) { if (EVENT_MODE_DESTRUCTIVE(event)) {
/* (re)write database file */ /* (re)write database file */
r = device_tag_index(dev, event->dev_db_clone, true); r = device_tag_index(dev, /* add= */ true);
if (r < 0) if (r < 0)
return log_device_debug_errno(dev, r, "Failed to update tags under /run/udev/tag/: %m"); return log_device_debug_errno(dev, r, "Failed to update tags under /run/udev/tag/: %m");
} }

View File

@ -2900,8 +2900,11 @@ static int udev_rule_apply_token_to_event(
assert(IN_SET(token->op, OP_ASSIGN, OP_ADD)); assert(IN_SET(token->op, OP_ASSIGN, OP_ADD));
if (token->op == OP_ASSIGN) if (token->op == OP_ASSIGN) {
device_cleanup_tags(dev); r = device_cleanup_tags(dev, event->dev_db_clone);
if (r < 0)
log_event_warning_errno(event, token, r, "Failed to clear previously assigned tags, ignoring: %m");
}
r = device_add_tag(dev, buf, /* both= */ true); r = device_add_tag(dev, buf, /* both= */ true);
if (r == -ENOMEM) if (r == -ENOMEM)

View File

@ -6,4 +6,3 @@
# (at your option) any later version. # (at your option) any later version.
g systemd-journal {{SYSTEMD_JOURNAL_GID}} - g systemd-journal {{SYSTEMD_JOURNAL_GID}} -
u! systemd-journal {{SYSTEMD_JOURNAL_UID}} "systemd Journal"

View File

@ -21,6 +21,8 @@ if [[ ! -x "$SYSUPDATE" ]]; then
exit 77 exit 77
fi fi
have_updatectl=$([[ -x "$SYSUPDATED" ]] && command -v updatectl)
# Loopback devices may not be supported. They are used because sfdisk cannot # Loopback devices may not be supported. They are used because sfdisk cannot
# change the sector size of a file, and we want to test both 512 and 4096 byte # change the sector size of a file, and we want to test both 512 and 4096 byte
# sectors. If loopback devices are not supported, we can only test one sector # sectors. If loopback devices are not supported, we can only test one sector
@ -112,6 +114,16 @@ update_now() {
elif [[ "$update_type" == "split" ]]; then elif [[ "$update_type" == "split" ]]; then
"$SYSUPDATE" --verify=no acquire "$SYSUPDATE" --verify=no acquire
"$SYSUPDATE" --verify=no update "$SYSUPDATE" --verify=no update
elif [[ "$update_type" == "updatectl" ]]; then
if $have_updatectl; then
systemctl start systemd-sysupdated
updatectl update
else
# Gracefully fall back to sysupdate
"$SYSUPDATE" --verify=no update
fi
else
exit 1
fi fi
(! "$SYSUPDATE" --verify=no check-new) (! "$SYSUPDATE" --verify=no check-new)
} }
@ -150,8 +162,14 @@ verify_version_current() {
cmp "$WORKDIR/source/dir-$version/bar.txt" "$WORKDIR/dirs/current/bar.txt" cmp "$WORKDIR/source/dir-$version/bar.txt" "$WORKDIR/dirs/current/bar.txt"
} }
verify_object_fields() {
local updatectl_output="${1:?}"
[[ "${updatectl_output}" != *"Unrecognized object field"* ]] || exit 1
}
for sector_size in "${SECTOR_SIZES[@]}"; do for sector_size in "${SECTOR_SIZES[@]}"; do
for update_type in monolithic split-offline split; do for update_type in monolithic split-offline split updatectl; do
# Disk size of: # Disk size of:
# - 1MB for GPT # - 1MB for GPT
# - 4 partitions of 2048 sectors each # - 4 partitions of 2048 sectors each
@ -352,7 +370,7 @@ EOF
# Create sixth version, update using updatectl and verify it replaced the # Create sixth version, update using updatectl and verify it replaced the
# correct version # correct version
new_version "$sector_size" v6 new_version "$sector_size" v6
if [[ -x "$SYSUPDATED" ]] && command -v updatectl; then if $have_updatectl; then
systemctl start systemd-sysupdated systemctl start systemd-sysupdated
"$SYSUPDATE" --verify=no check-new "$SYSUPDATE" --verify=no check-new
updatectl update updatectl update
@ -370,12 +388,12 @@ EOF
# testing for specific output, but this will at least catch obvious crashes # testing for specific output, but this will at least catch obvious crashes
# and allow updatectl to run under the various sanitizers. We create a # and allow updatectl to run under the various sanitizers. We create a
# component so that updatectl has multiple targets to list. # component so that updatectl has multiple targets to list.
if [[ -x "$SYSUPDATED" ]] && command -v updatectl; then if $have_updatectl; then
mkdir -p /run/sysupdate.test.d/ mkdir -p /run/sysupdate.test.d/
cp "$CONFIGDIR/01-first.transfer" /run/sysupdate.test.d/01-first.transfer cp "$CONFIGDIR/01-first.transfer" /run/sysupdate.test.d/01-first.transfer
updatectl list verify_object_fields "$(updatectl list 2>&1)"
updatectl list host verify_object_fields "$(updatectl list host 2>&1)"
updatectl list host@v6 verify_object_fields "$(updatectl list host@v6 2>&1)"
updatectl check updatectl check
rm -r /run/sysupdate.test.d rm -r /run/sysupdate.test.d
fi fi

View File

@ -13,7 +13,10 @@ Documentation=man:journalctl(1)
DefaultDependencies=no DefaultDependencies=no
Conflicts=shutdown.target Conflicts=shutdown.target
Before=shutdown.target Before=shutdown.target
RequiresMountsFor=/var/log/journal
[Service] [Service]
ExecStart=journalctl ExecStart=journalctl --system
User=systemd-journal DynamicUser=yes
User=systemd-journal-access
SupplementaryGroups=systemd-journal

View File

@ -22,6 +22,3 @@ FileDescriptorName=varlink
SocketMode=0600 SocketMode=0600
Accept=yes Accept=yes
MaxConnectionsPerSource=16 MaxConnectionsPerSource=16
[Install]
WantedBy=sockets.target

View File

@ -17,4 +17,3 @@ Symlinks=%t/varlink/registry/io.systemd.JournalAccess
FileDescriptorName=varlink FileDescriptorName=varlink
SocketMode=0600 SocketMode=0600
Accept=yes Accept=yes
MaxConnectionsPerSource=16

View File

@ -12,4 +12,4 @@ Description=Journal Log Access Service
Documentation=man:journalctl(1) Documentation=man:journalctl(1)
[Service] [Service]
ExecStart=journalctl ExecStart=journalctl --user