summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Hindley <mark@hindley.org.uk>2018-11-12 09:10:28 +0000
committerMark Hindley <mark@hindley.org.uk>2018-11-12 09:21:21 +0000
commit2cc17d30309a1db16cccbf376a59ae40e47b6959 (patch)
treecd51ee95799631af348ebae8630a69219bee99cd
parentae65e91a5439f395e0da0cd8ffda95d6289849e1 (diff)
parentd4a3f291e3955648ea1d29e674b0f8f9b1556257 (diff)
Merge remote-tracking branch 'upstream/v239-stable' into merge_upstream.
-rw-r--r--README2
-rw-r--r--cb/elogind.cbp124
-rw-r--r--man/logind.conf.xml11
-rw-r--r--man/meson.build3
-rw-r--r--man/rules/meson.build3
-rw-r--r--man/sd_bus_error.xml2
-rw-r--r--man/user@.service.xml190
-rw-r--r--meson.build176
-rw-r--r--meson_options.txt4
m---------pwx0
-rw-r--r--src/basic/alloc-util.h15
-rw-r--r--src/basic/env-util.h1
-rw-r--r--src/basic/errno-list.c2
-rw-r--r--src/basic/exec-util.c16
-rw-r--r--src/basic/exec-util.h3
-rw-r--r--src/basic/fd-util.c24
-rw-r--r--src/basic/fd-util.h8
-rw-r--r--src/basic/fileio.c8
-rw-r--r--src/basic/fileio.h2
-rw-r--r--src/basic/format-table.c14
-rw-r--r--src/basic/fs-util.c2
-rw-r--r--src/basic/hashmap.c17
-rw-r--r--src/basic/hexdecoct.c6
-rw-r--r--src/basic/mempool.c18
-rw-r--r--src/basic/mempool.h5
-rw-r--r--src/basic/meson.build3
-rw-r--r--src/basic/missing.h11
-rw-r--r--src/basic/parse-util.c7
-rw-r--r--src/basic/proc-cmdline.c25
-rw-r--r--src/basic/proc-cmdline.h1
-rw-r--r--src/basic/process-util.c40
-rw-r--r--src/basic/process-util.h6
-rw-r--r--src/basic/raw-clone.h2
-rw-r--r--src/basic/selinux-util.c4
-rw-r--r--src/basic/sigbus.c4
-rw-r--r--src/basic/socket-util.c59
-rw-r--r--src/basic/socket-util.h7
-rw-r--r--src/basic/string-util.c28
-rw-r--r--src/basic/string-util.h27
-rw-r--r--src/basic/terminal-util.c16
-rw-r--r--src/basic/time-util.c19
-rw-r--r--src/basic/unit-def.c2
-rw-r--r--src/basic/unit-def.h2
-rw-r--r--src/basic/unit-name.c4
-rw-r--r--src/basic/unit-name.h6
-rw-r--r--src/basic/user-util.h2
-rw-r--r--src/basic/util.c19
-rw-r--r--src/basic/util.h2
-rw-r--r--src/basic/virt.c38
-rw-r--r--src/basic/xattr-util.c1
-rw-r--r--src/basic/xml.c238
-rw-r--r--src/basic/xml.h14
-rw-r--r--src/busctl/busctl-introspect.c763
-rw-r--r--src/busctl/busctl-introspect.h14
-rw-r--r--src/busctl/busctl.c2029
-rw-r--r--src/libelogind/disable-mempool.c5
-rw-r--r--src/libelogind/meson.build12
-rw-r--r--src/libelogind/sd-bus/bus-dump.c595
-rw-r--r--src/libelogind/sd-bus/bus-dump.h22
-rw-r--r--src/libelogind/sd-bus/bus-error.c24
-rw-r--r--src/libelogind/sd-bus/bus-error.h4
-rw-r--r--src/libelogind/sd-bus/bus-internal.h4
-rw-r--r--src/libelogind/sd-bus/bus-introspect.c2
-rw-r--r--src/libelogind/sd-bus/bus-kernel.c2
-rw-r--r--src/libelogind/sd-bus/bus-kernel.h7
-rw-r--r--src/libelogind/sd-bus/bus-message.c262
-rw-r--r--src/libelogind/sd-bus/bus-signature.c6
-rw-r--r--src/libelogind/sd-bus/bus-slot.c5
-rw-r--r--src/libelogind/sd-bus/bus-socket.c5
-rw-r--r--src/libelogind/sd-bus/sd-bus.c18
-rw-r--r--src/libelogind/sd-bus/test-bus-signature.c8
-rw-r--r--src/login/70-uaccess.rules.m4 (renamed from src/login/70-uaccess.rules)4
-rw-r--r--src/login/elogind-dbus.c2
-rw-r--r--src/login/elogind.c46
-rw-r--r--src/login/elogind.h3
-rw-r--r--src/login/logind-core.c216
-rw-r--r--src/login/logind-dbus.c113
-rw-r--r--src/login/logind-device.c4
-rw-r--r--src/login/logind-gperf.gperf1
-rw-r--r--src/login/logind-seat.c58
-rw-r--r--src/login/logind-seat.h6
-rw-r--r--src/login/logind-session-dbus.c23
-rw-r--r--src/login/logind-session.c252
-rw-r--r--src/login/logind-session.h25
-rw-r--r--src/login/logind-user-dbus.c2
-rw-r--r--src/login/logind-user.c395
-rw-r--r--src/login/logind-user.h22
-rw-r--r--src/login/logind-utmp.c6
-rw-r--r--src/login/logind.c79
-rw-r--r--src/login/logind.h20
-rw-r--r--src/login/meson.build13
-rw-r--r--src/login/org.freedesktop.login1.policy2
-rw-r--r--src/login/pam_elogind.c124
-rw-r--r--src/login/user-runtime-dir.c111
-rw-r--r--src/shared/bus-util.c12
-rw-r--r--src/shared/bus-util.h4
-rw-r--r--src/shared/enable-mempool.c5
-rw-r--r--src/shared/meson.build11
-rw-r--r--src/shared/musl_missing.c17
-rw-r--r--src/shared/musl_missing.h3
-rw-r--r--src/shared/nsflags.h4
-rw-r--r--src/shared/qsort_r_missing.c497
-rw-r--r--src/shared/qsort_r_missing.h34
-rw-r--r--src/shared/sleep-config.c225
-rw-r--r--src/shared/sleep-config.h2
-rw-r--r--src/sleep/sleep.c34
-rw-r--r--src/test/meson.build20
-rw-r--r--src/test/test-chown-rec.c160
-rw-r--r--src/test/test-exec-util.c43
-rw-r--r--src/test/test-format-table.c42
-rw-r--r--src/test/test-proc-cmdline.c96
-rw-r--r--src/test/test-process-util.c9
-rw-r--r--src/test/test-set-disable-mempool.c55
-rw-r--r--src/test/test-string-util.c116
-rwxr-xr-xtools/gdb-sd_dump_hashmaps.py18
115 files changed, 6901 insertions, 1063 deletions
diff --git a/README b/README
index ced8cc1..dcaabd0 100644
--- a/README
+++ b/README
@@ -169,7 +169,7 @@ Dependencies
During runtime, you need the following additional dependencies:
util-linux >= v2.27.1 required
- dbus >= 1.4.0 (strictly speaking optional, but recommended)
+ dbus >= 1.9.14 (strictly speaking optional, but recommended)
NOTE: If using dbus < 1.9.18, you should override the default
policy directory (--with-dbuspolicydir=/etc/dbus-1/system.d).
PolicyKit (optional)
diff --git a/cb/elogind.cbp b/cb/elogind.cbp
index 387e057..7f2dd66 100644
--- a/cb/elogind.cbp
+++ b/cb/elogind.cbp
@@ -296,9 +296,6 @@
<Option compilerVar="CC" />
</Unit>
<Unit filename="../src/basic/mount-util.h" />
- <Unit filename="../src/basic/os-util.c">
- <Option compilerVar="CC" />
- </Unit>
<Unit filename="../src/basic/os-util.h" />
<Unit filename="../src/basic/pager.c">
<Option compilerVar="CC" />
@@ -432,6 +429,17 @@
<Option compilerVar="CC" />
</Unit>
<Unit filename="../src/basic/xattr-util.h" />
+ <Unit filename="../src/basic/xml.c">
+ <Option compilerVar="CC" />
+ </Unit>
+ <Unit filename="../src/basic/xml.h" />
+ <Unit filename="../src/busctl/busctl-introspect.c">
+ <Option compilerVar="CC" />
+ </Unit>
+ <Unit filename="../src/busctl/busctl-introspect.h" />
+ <Unit filename="../src/busctl/busctl.c">
+ <Option compilerVar="CC" />
+ </Unit>
<Unit filename="../src/cgroups-agent/cgroups-agent.c">
<Option compilerVar="CC" />
<Option target="all" />
@@ -456,14 +464,14 @@
<Option target="all" />
<Option target="clean" />
</Unit>
- <Unit filename="../src/libelogind/libelogind.sym">
- <Option target="all" />
- <Option target="clean" />
+ <Unit filename="../src/libelogind/disable-mempool.c">
+ <Option compilerVar="CC" />
</Unit>
- <Unit filename="../src/libelogind/meson.build">
+ <Unit filename="../src/libelogind/libelogind.sym">
<Option target="all" />
<Option target="clean" />
</Unit>
+ <Unit filename="../src/libelogind/meson.build" />
<Unit filename="../src/libelogind/sd-bus/bus-common-errors.c">
<Option compilerVar="CC" />
<Option target="all" />
@@ -505,6 +513,10 @@
<Option target="all" />
<Option target="clean" />
</Unit>
+ <Unit filename="../src/libelogind/sd-bus/bus-dump.c">
+ <Option compilerVar="CC" />
+ </Unit>
+ <Unit filename="../src/libelogind/sd-bus/bus-dump.h" />
<Unit filename="../src/libelogind/sd-bus/bus-error.c">
<Option compilerVar="CC" />
<Option target="all" />
@@ -816,101 +828,59 @@
<Unit filename="../src/login/user-runtime-dir.h" />
<Unit filename="../src/shared/acl-util.c">
<Option compilerVar="CC" />
- <Option target="all" />
- <Option target="clean" />
- </Unit>
- <Unit filename="../src/shared/acl-util.h">
- <Option target="all" />
- <Option target="clean" />
</Unit>
+ <Unit filename="../src/shared/acl-util.h" />
<Unit filename="../src/shared/bus-util.c">
<Option compilerVar="CC" />
- <Option target="all" />
- <Option target="clean" />
- </Unit>
- <Unit filename="../src/shared/bus-util.h">
- <Option target="all" />
- <Option target="clean" />
</Unit>
+ <Unit filename="../src/shared/bus-util.h" />
<Unit filename="../src/shared/clean-ipc.c">
<Option compilerVar="CC" />
- <Option target="all" />
- <Option target="clean" />
- </Unit>
- <Unit filename="../src/shared/clean-ipc.h">
- <Option target="all" />
- <Option target="clean" />
</Unit>
+ <Unit filename="../src/shared/clean-ipc.h" />
<Unit filename="../src/shared/conf-parser.c">
<Option compilerVar="CC" />
- <Option target="all" />
- <Option target="clean" />
- </Unit>
- <Unit filename="../src/shared/conf-parser.h">
- <Option target="all" />
- <Option target="clean" />
</Unit>
- <Unit filename="../src/shared/libshared.sym">
- <Option target="all" />
- <Option target="clean" />
+ <Unit filename="../src/shared/conf-parser.h" />
+ <Unit filename="../src/shared/enable-mempool.c">
+ <Option compilerVar="CC" />
</Unit>
- <Unit filename="../src/shared/meson.build">
- <Option target="all" />
- <Option target="clean" />
+ <Unit filename="../src/shared/libshared.sym" />
+ <Unit filename="../src/shared/meson.build" />
+ <Unit filename="../src/shared/module-util.c">
+ <Option compilerVar="CC" />
</Unit>
<Unit filename="../src/shared/musl_missing.c">
<Option compilerVar="CC" />
- <Option target="all" />
- <Option target="clean" />
- </Unit>
- <Unit filename="../src/shared/musl_missing.h">
- <Option target="all" />
- <Option target="clean" />
</Unit>
+ <Unit filename="../src/shared/musl_missing.h" />
<Unit filename="../src/shared/nsflags.c">
<Option compilerVar="CC" />
- <Option target="all" />
- <Option target="clean" />
</Unit>
- <Unit filename="../src/shared/nsflags.h">
- <Option target="all" />
- <Option target="clean" />
+ <Unit filename="../src/shared/nsflags.h" />
+ <Unit filename="../src/shared/output-mode.c">
+ <Option compilerVar="CC" />
</Unit>
- <Unit filename="../src/shared/sleep-config.c">
+ <Unit filename="../src/shared/output-mode.h" />
+ <Unit filename="../src/shared/qsort_r_missing.c">
<Option compilerVar="CC" />
- <Option target="all" />
- <Option target="clean" />
</Unit>
- <Unit filename="../src/shared/sleep-config.h">
- <Option target="all" />
- <Option target="clean" />
+ <Unit filename="../src/shared/qsort_r_missing.h" />
+ <Unit filename="../src/shared/sleep-config.c">
+ <Option compilerVar="CC" />
</Unit>
+ <Unit filename="../src/shared/sleep-config.h" />
<Unit filename="../src/shared/spawn-polkit-agent.c">
<Option compilerVar="CC" />
- <Option target="all" />
- <Option target="clean" />
- </Unit>
- <Unit filename="../src/shared/spawn-polkit-agent.h">
- <Option target="all" />
- <Option target="clean" />
- </Unit>
- <Unit filename="../src/shared/test-tables.h">
- <Option target="all" />
- <Option target="clean" />
- </Unit>
- <Unit filename="../src/shared/udev-util.h">
- <Option target="all" />
- <Option target="clean" />
</Unit>
+ <Unit filename="../src/shared/spawn-polkit-agent.h" />
+ <Unit filename="../src/shared/test-tables.h" />
+ <Unit filename="../src/shared/udev-util.h" />
<Unit filename="../src/shared/utmp-wtmp.c">
<Option compilerVar="CC" />
- <Option target="all" />
- <Option target="clean" />
- </Unit>
- <Unit filename="../src/shared/utmp-wtmp.h">
- <Option target="all" />
- <Option target="clean" />
</Unit>
+ <Unit filename="../src/shared/utmp-wtmp.h" />
+ <Unit filename="../src/shared/wireguard-netlink.h" />
<Unit filename="../src/sleep/sleep.c">
<Option compilerVar="CC" />
<Option target="all" />
@@ -973,6 +943,9 @@
<Unit filename="../src/test/test-cgroup.c">
<Option compilerVar="CC" />
</Unit>
+ <Unit filename="../src/test/test-chown-rec.c">
+ <Option compilerVar="CC" />
+ </Unit>
<Unit filename="../src/test/test-conf-files.c">
<Option compilerVar="CC" />
</Unit>
@@ -1069,6 +1042,9 @@
<Unit filename="../src/test/test-selinux.c">
<Option compilerVar="CC" />
</Unit>
+ <Unit filename="../src/test/test-set-disable-mempool.c">
+ <Option compilerVar="CC" />
+ </Unit>
<Unit filename="../src/test/test-set.c">
<Option compilerVar="CC" />
</Unit>
diff --git a/man/logind.conf.xml b/man/logind.conf.xml
index abc97b3..f5ad348 100644
--- a/man/logind.conf.xml
+++ b/man/logind.conf.xml
@@ -226,6 +226,17 @@
</varlistentry>
<varlistentry>
+ <term><varname>UserStopDelaySec=</varname></term>
+
+ <listitem><para>Specifies how long to keep the user record and per-user service
+ <filename>user@.service</filename> around for a user after they logged out fully. If set to zero, the per-user
+ service is terminated immediately when the last session of the user has ended. If this option is configured to
+ non-zero rapid logout/login cycles are sped up, as the user's service manager is not constantly restarted. If
+ set to <literal>infinity</literal> the per-user service for a user is never terminated again after first login,
+ and continues to run until system shutdown. Defaults to 10s.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><varname>HandlePowerKey=</varname></term>
<term><varname>HandleSuspendKey=</varname></term>
<term><varname>HandleHibernateKey=</varname></term>
diff --git a/man/meson.build b/man/meson.build
index d5895eb..de41193 100644
--- a/man/meson.build
+++ b/man/meson.build
@@ -177,7 +177,8 @@ foreach tuple : want_man or want_html ? [['elogind.directives', '7', elogind_dir
html_pages += [p3]
endforeach
-# cannot use run_target until https://github.com/mesonbuild/meson/issues/1644 is resolved
+# Cannot use run_target because those targets are used in depends
+# Also see https://github.com/mesonbuild/meson/issues/368.
man = custom_target(
'man',
output : 'man',
diff --git a/man/rules/meson.build b/man/rules/meson.build
index 74191d0..4ed84e5 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -344,6 +344,7 @@ manpages = [
'sd_uid_get_sessions',
'sd_uid_is_on_seat'],
'HAVE_PAM'],
- ['sd_watchdog_enabled', '3', [], '']
+ ['sd_watchdog_enabled', '3', [], ''],
+ ['user@.service', '5', ['user-runtime-dir@.service'], '']
]
# Really, do not edit.
diff --git a/man/sd_bus_error.xml b/man/sd_bus_error.xml
index 070032b..1b2f33e 100644
--- a/man/sd_bus_error.xml
+++ b/man/sd_bus_error.xml
@@ -148,7 +148,7 @@
should have both fields initialized to NULL. Set an error
structure to <constant>SD_BUS_ERROR_NULL</constant> in order to
reset both fields to NULL. When no longer necessary, resources
- held by the <structname>sd_bus_error</structname>structure should
+ held by the <structname>sd_bus_error</structname> structure should
be destroyed with <function>sd_bus_error_free()</function>.</para>
<para><function>sd_bus_error_set()</function> sets an error
diff --git a/man/user@.service.xml b/man/user@.service.xml
new file mode 100644
index 0000000..fc9c3e7
--- /dev/null
+++ b/man/user@.service.xml
@@ -0,0 +1,190 @@
+<?xml version="1.0"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1+ -->
+
+<refentry id="user@.service">
+ <refentryinfo>
+ <title>user@.service</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>user@.service</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>user@.service</refname>
+ <refname>user-runtime-dir@.service</refname>
+ <refpurpose>System units to manager user processes</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>user@<replaceable>UID</replaceable>.service</filename></para>
+ <para><filename>user-runtime-dir@<replaceable>UID</replaceable>.service</filename></para>
+ <para><filename>user-<replaceable>UID</replaceable>.slice</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>The
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ system manager (PID 1) starts user manager instances as
+ <filename>user@<replaceable>UID</replaceable>.service</filename>, where the user's numerical UID
+ is used as the instance identifier. Each <command>systemd --user</command> instance manages a
+ hierarchy of its own units. See
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ a discussion of systemd units and
+ <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ for a list of units that form the basis of the unit hierarchies of system and user units.</para>
+
+ <para><filename>user@<replaceable>UID</replaceable>.service</filename> is accompanied by the
+ system unit <filename>user-runtime-dir@<replaceable>UID</replaceable>.service</filename>, which
+ creates the user's runtime directory
+ <filename>/run/user/<replaceable>UID</replaceable></filename>, and then removes it when this
+ unit is stopped.</para>
+
+ <para>User processes may be started by the <filename>user@.service</filename> instance, in which
+ case they will be part of that unit in the system hierarchy. They may also be started elsewhere,
+ for example by
+ <citerefentry><refentrytitle>sshd</refentrytitle><manvolnum>8</manvolnum></citerefentry> or a
+ display manager like <command>gdm</command>, in which case they form a .scope unit (see
+ <citerefentry><refentrytitle>systemd.scope</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
+ Both <filename>user@<replaceable>UID</replaceable>.service</filename> and the scope units are
+ collected under a <filename>user-<replaceable>UID</replaceable>.slice</filename>.</para>
+
+ <para>Individual <filename>user-<replaceable>UID</replaceable>.slice</filename> slices are
+ collected under <filename>user.slice</filename>, see
+ <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Controlling resources for logged-in users</title>
+
+ <para>Options that control resources available to logged-in users can be configured at a few
+ different levels. As described in the previous section, <filename>user.slice</filename> contains
+ processes of all users, so any resource limits on that slice apply to all users together. The
+ usual way to configure them would be through drop-ins, e.g. <filename
+ noindex='true'>/etc/systemd/system/user.slice.d/resources.conf</filename>.
+ </para>
+
+ <para>The processes of a single user are collected under
+ <filename>user-<replaceable>UID</replaceable>.slice</filename>. Resource limits for that user
+ can be configured through drop-ins for that unit, e.g. <filename
+ noindex='true'>/etc/systemd/system/user-1000.slice.d/resources.conf</filename>. If the limits
+ should apply to all users instead, they may be configured through drop-ins for the truncated
+ unit name, <filename>user-.slice</filename>. For example, configuration in <filename
+ noindex='true'>/etc/systemd/system/user-.slice.d/resources.conf</filename> is included in all
+ <filename>user-<replaceable>UID</replaceable>.slice</filename> units, see
+ <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for a discussion of the drop-in mechanism.</para>
+
+ <para>When a user logs in and a .scope unit is created for the session (see previous section),
+ the creation of the scope may be managed through
+ <citerefentry><refentrytitle>pam_systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ This PAM module communicates with
+ <citerefentry><refentrytitle>systemd-logind</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ to create the session scope and provide access to hardware resources. Resource limits for the
+ scope may be configured through the PAM module configuration, see
+ <citerefentry><refentrytitle>pam_systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ Configuring them through the normal unit configuration is also possible, but since
+ the name of the slice unit is generally unpredictable, this is less useful.</para>
+
+ <para>In general any resources that apply to units may be set for
+ <filename>user@<replaceable>UID</replaceable>.service</filename> and the slice
+ units discussed above, see
+ <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for an overview.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+ <example>
+ <title>Hierarchy of control groups with two logged in users</title>
+
+ <programlisting>$ systemd-cgls
+Control group /:
+-.slice
+├─user.slice
+│ ├─user-1000.slice
+│ │ ├─user@1000.service
+│ │ │ ├─pulseaudio.service
+│ │ │ │ └─2386 /usr/bin/pulseaudio --daemonize=no
+│ │ │ └─gnome-terminal-server.service
+│ │ │ └─init.scope
+│ │ │ ├─ 4127 /usr/libexec/gnome-terminal-server
+│ │ │ └─ 4198 zsh
+│ │ …
+│ │ └─session-4.scope
+│ │ ├─ 1264 gdm-session-worker [pam/gdm-password]
+│ │ ├─ 2339 /usr/bin/gnome-shell
+│ │ …
+│ │ ├─session-19.scope
+│ │ ├─6497 sshd: zbyszek [priv]
+│ │ ├─6502 sshd: zbyszek@pts/6
+│ │ ├─6509 -zsh
+│ │ └─6602 systemd-cgls --no-pager
+│ …
+│ └─user-1001.slice
+│ ├─session-20.scope
+│ │ ├─6675 sshd: guest [priv]
+│ │ ├─6708 sshd: guest@pts/6
+│ │ └─6717 -bash
+│ └─user@1001.service
+│ ├─init.scope
+│ │ ├─6680 /usr/lib/systemd/systemd --user
+│ │ └─6688 (sd-pam)
+│ └─sleep.service
+│ └─6706 /usr/bin/sleep 30
+…</programlisting>
+ <para>User with UID 1000 is logged in using <command>gdm</command> (<filename
+ noindex='true'>session-4.scope</filename>) and
+ <citerefentry><refentrytitle>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ (<filename noindex='true'>session-19.scope</filename>), and also has a user manager instance
+ running (<filename noindex='true'>user@1000.service</filename>). User with UID 1001 is logged
+ in using <command>ssh</command> (<filename noindex='true'>session-20.scope</filename>) and
+ also has a user manager instance running (<filename
+ noindex='true'>user@1001.service</filename>). Those are all (leaf) system units, and form
+ part of the slice hierarchy, with <filename noindex='true'>user-1000.slice</filename> and
+ <filename noindex='true'>user-1001.slice</filename> below <filename
+ noindex='true'>user.slice</filename>. User units are visible below the
+ <filename>user@.service</filename> instances (<filename
+ noindex='true'>pulseaudio.service</filename>, <filename
+ noindex='true'>gnome-terminal-server.service</filename>, <filename
+ noindex='true'>init.scope</filename>, <filename noindex='true'>sleep.service</filename>).
+ </para>
+ </example>
+
+ <example>
+ <title>Default user resource limits</title>
+
+ <programlisting>$ systemctl cat user-1000.slice
+# /usr/lib/systemd/system/user-.slice.d/10-defaults.conf
+# …
+[Unit]
+Description=User Slice of UID %j
+After=systemd-user-sessions.service
+
+[Slice]
+TasksMax=33%</programlisting>
+ <para>The <filename>user-<replaceable>UID</replaceable>.slice</filename> units by default don't
+ have a unit file. The resource limits are set through a drop-in, which can be easily replaced
+ or extended following standard drop-in mechanisms discussed in the first section.</para>
+ </example>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.slice</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ <citerefentry project='man-pages'><refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+</refentry>
diff --git a/meson.build b/meson.build
index dea5084..fad164e 100644
--- a/meson.build
+++ b/meson.build
@@ -209,7 +209,6 @@ varrunissymllink = run_command('test', '-L', '/var/run').returncode() == 0
message('Setting VARRUN_IS_SYMLINK to: @0@'.format(varrunissymllink))
conf.set10('VARRUN_IS_SYMLINK', varrunissymllink)
#endif // 1
-
pamlibdir = get_option('pamlibdir')
if pamlibdir == ''
pamlibdir = join_paths(rootlibdir, 'security')
@@ -446,6 +445,10 @@ foreach arg : ['unused-parameter',
'unused-result',
'format-signedness',
'error=nonnull', # work-around for gcc 7.1 turning this on on its own
+
+ # Disable -Wmaybe-uninitialized, since it's noisy on gcc 8 with
+ # optimizations enabled, producing essentially false positives.
+ 'maybe-uninitialized',
]
if cc.has_argument('-W' + arg)
add_project_arguments('-Wno-' + arg, language : 'c')
@@ -494,10 +497,14 @@ conf.set('SIZEOF_TIME_T', cc.sizeof('time_t', prefix : '#include <sys/time.h>'))
conf.set('SIZEOF_RLIM_T', cc.sizeof('rlim_t', prefix : '#include <sys/resource.h>'))
decl_headers = '''
+#if 1 /// elogind supports system where _GNU_SOURCE is not set in ENV
+# ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+# endif // _GNU_SOURCE
+#endif // 1
#include <uchar.h>
#include <linux/ethtool.h>
#include <linux/fib_rules.h>
-#include <linux/stat.h>
#include <sys/stat.h>
'''
# FIXME: key_serial_t is only defined in keyutils.h, this is bound to fail
@@ -511,10 +518,24 @@ foreach decl : ['char16_t',
]
# We get -1 if the size cannot be determined
- have = cc.sizeof(decl, prefix : decl_headers) > 0
+ have = cc.sizeof(decl, prefix : decl_headers, args : '-D_GNU_SOURCE') > 0
+
+ if decl == 'struct statx'
+ if have
+ want_linux_stat_h = false
+ else
+ have = cc.sizeof(decl,
+ prefix : decl_headers + '#include <linux/stat.h>',
+ args : '-D_GNU_SOURCE') > 0
+ want_linux_stat_h = have
+ endif
+ endif
+
conf.set10('HAVE_' + decl.underscorify().to_upper(), have)
endforeach
+conf.set10('WANT_LINUX_STAT_H', want_linux_stat_h)
+
foreach decl : [['IFLA_INET6_ADDR_GEN_MODE', 'linux/if_link.h'],
['IN6_ADDR_GEN_MODE_STABLE_PRIVACY', 'linux/if_link.h'],
['IFLA_VRF_TABLE', 'linux/if_link.h'],
@@ -556,12 +577,15 @@ foreach ident : [
#include <unistd.h>'''],
['pivot_root', '''#include <stdlib.h>
#include <unistd.h>'''], # no known header declares pivot_root
+#if 1 /// elogind supports musl, but upstream refuses to add qsort_r
+ ['qsort_r', '''#include <stdlib.h>'''],
+#endif // 1
['name_to_handle_at', '''#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>'''],
['setns', '''#include <sched.h>'''],
['renameat2', '''#include <stdio.h>
-// #include <fcntl.h>'''],
+ #include <fcntl.h>'''],
['kcmp', '''#include <linux/kcmp.h>'''],
['keyctl', '''#include <sys/types.h>
#include <keyutils.h>'''],
@@ -571,7 +595,7 @@ foreach ident : [
#include <unistd.h>'''],
['statx', '''#include <sys/types.h>
#include <sys/stat.h>
-// #include <unistd.h>'''],
+ #include <unistd.h>'''],
['explicit_bzero' , '''#include <string.h>'''],
['reallocarray', '''#include <malloc.h>'''],
]
@@ -815,41 +839,43 @@ message('maximum system GID is @0@'.format(system_gid_max))
nobody_user = get_option('nobody-user')
nobody_group = get_option('nobody-group')
-getent_result = run_command('getent', 'passwd', '65534')
-if getent_result.returncode() == 0
- name = getent_result.stdout().split(':')[0]
- if name != nobody_user
- warning('\n' +
- 'The local user with the UID 65534 does not match the configured user name "@0@" of the nobody user (its name is @1@).\n'.format(nobody_user, name) +
- 'Your build will result in an user table setup that is incompatible with the local system.')
+if not meson.is_cross_build()
+ getent_result = run_command('getent', 'passwd', '65534')
+ if getent_result.returncode() == 0
+ name = getent_result.stdout().split(':')[0]
+ if name != nobody_user
+ warning('\n' +
+ 'The local user with the UID 65534 does not match the configured user name "@0@" of the nobody user (its name is @1@).\n'.format(nobody_user, name) +
+ 'Your build will result in an user table setup that is incompatible with the local system.')
+ endif
endif
-endif
-id_result = run_command('id', '-u', nobody_user)
-if id_result.returncode() == 0
- id = id_result.stdout().to_int()
- if id != 65534
- warning('\n' +
- 'The local user with the configured user name "@0@" of the nobody user does not have UID 65534 (it has @1@).\n'.format(nobody_user, id) +
- 'Your build will result in an user table setup that is incompatible with the local system.')
+ id_result = run_command('id', '-u', nobody_user)
+ if id_result.returncode() == 0
+ id = id_result.stdout().to_int()
+ if id != 65534
+ warning('\n' +
+ 'The local user with the configured user name "@0@" of the nobody user does not have UID 65534 (it has @1@).\n'.format(nobody_user, id) +
+ 'Your build will result in an user table setup that is incompatible with the local system.')
+ endif
endif
-endif
-getent_result = run_command('getent', 'group', '65534')
-if getent_result.returncode() == 0
- name = getent_result.stdout().split(':')[0]
- if name != nobody_group
- warning('\n' +
- 'The local group with the GID 65534 does not match the configured group name "@0@" of the nobody group (its name is @1@).\n'.format(nobody_group, name) +
- 'Your build will result in an group table setup that is incompatible with the local system.')
+ getent_result = run_command('getent', 'group', '65534')
+ if getent_result.returncode() == 0
+ name = getent_result.stdout().split(':')[0]
+ if name != nobody_group
+ warning('\n' +
+ 'The local group with the GID 65534 does not match the configured group name "@0@" of the nobody group (its name is @1@).\n'.format(nobody_group, name) +
+ 'Your build will result in an group table setup that is incompatible with the local system.')
+ endif
endif
-endif
-id_result = run_command('id', '-g', nobody_group)
-if id_result.returncode() == 0
- id = id_result.stdout().to_int()
- if id != 65534
- warning('\n' +
- 'The local group with the configured group name "@0@" of the nobody group does not have UID 65534 (it has @1@).\n'.format(nobody_group, id) +
- 'Your build will result in an group table setup that is incompatible with the local system.')
+ id_result = run_command('id', '-g', nobody_group)
+ if id_result.returncode() == 0
+ id = id_result.stdout().to_int()
+ if id != 65534
+ warning('\n' +
+ 'The local group with the configured group name "@0@" of the nobody group does not have UID 65534 (it has @1@).\n'.format(nobody_group, id) +
+ 'Your build will result in an group table setup that is incompatible with the local system.')
+ endif
endif
endif
if nobody_user != nobody_group and not (nobody_user == 'nobody' and nobody_group == 'nogroup')
@@ -879,7 +905,9 @@ substs.set('TTY_GID', tty_gid)
# conf.set10('ENABLE_ADM_GROUP', get_option('adm-group'))
# conf.set10('ENABLE_WHEEL_GROUP', get_option('wheel-group'))
#
-# substs.set('DEV_KVM_MODE', get_option('dev-kvm-mode'))
+# dev_kvm_mode = get_option('dev-kvm-mode')
+# substs.set('DEV_KVM_MODE', dev_kvm_mode)
+# conf.set10('DEV_KVM_UACCESS', dev_kvm_mode != '0666')
# substs.set('GROUP_RENDER_MODE', get_option('group-render-mode'))
#endif // 0
@@ -1322,7 +1350,7 @@ conf.set10('HAVE_DBUS', have)
#
# dns_over_tls = get_option('dns-over-tls')
# if dns_over_tls != 'false'
-# have = conf.get('HAVE_GNUTLS') == 1
+# have = libgnutls != [] and libgnutls.version().version_compare('>=3.5.3')
# if dns_over_tls == 'true' and not have
# error('DNS-over-TLS support was requested, but dependencies are not available')
# endif
@@ -1347,7 +1375,6 @@ conf.set10('HAVE_DBUS', have)
# if want_importd != 'false'
# have = (conf.get('HAVE_LIBCURL') == 1 and
# conf.get('HAVE_ZLIB') == 1 and
-# conf.get('HAVE_BZIP2') == 1 and
# conf.get('HAVE_XZ') == 1 and
# conf.get('HAVE_GCRYPT') == 1)
# if want_importd == 'true' and not have
@@ -1476,7 +1503,6 @@ conf.set_quoted('USER_GENERATOR_PATH', '/dev/null')
#####################################################################
#endif // 1
-
config_h = configure_file(
output : 'config.h',
configuration : conf)
@@ -1548,7 +1574,7 @@ subdir('src/login')
libelogind_sym_path = '@0@/@1@'.format(meson.current_source_dir(), libelogind_sym)
libelogind = shared_library(
'elogind',
- 'src/systemd/sd-id128.h', # pick a header file at random to work around old meson bug
+ disable_mempool_c,
version : libelogind_version,
include_directories : includes,
link_args : ['-shared',
@@ -1585,6 +1611,7 @@ install_libelogind_static = static_library(
#if 0 /// No gcrypt with elogind
# basic_gcrypt_sources,
#endif // 0
+ disable_mempool_c,
include_directories : includes,
build_by_default : static_libelogind != 'false',
install : static_libelogind != 'false',
@@ -1656,6 +1683,7 @@ subdir('src/test')
test_dlopen = executable(
'test-dlopen',
test_dlopen_c,
+ disable_mempool_c,
include_directories : includes,
link_with : [libbasic],
dependencies : [libdl])
@@ -1676,6 +1704,7 @@ test_dlopen = executable(
# nss = shared_library(
# 'nss_' + module,
# 'src/nss-@0@/nss-@0@.c'.format(module),
+# disable_mempool_c,
# version : '2',
# include_directories : includes,
# # Note that we link NSS modules with '-z nodelete' so that mempools never get orphaned
@@ -1946,16 +1975,14 @@ test_dlopen = executable(
# test_dlopen,
# args : [pam_systemd.full_path()]) # path to dlopen must include a slash
# endif
-# endif
-#
-# executable('systemd-user-runtime-dir',
-# user_runtime_dir_sources,
-# include_directories : includes,
-# link_with : [libshared, liblogind_core],
-# install_rpath : rootlibexecdir,
-# install : true,
-# install_dir : rootlibexecdir)
-#
+#
+# executable('systemd-user-runtime-dir',
+# user_runtime_dir_sources,
+# include_directories : includes,
+# link_with : [libshared],
+# install_rpath : rootlibexecdir,
+# install : true,
+# install_dir : rootlibexecdir)
#else
executable('elogind',
@@ -2666,17 +2693,19 @@ executable('elogind-cgroups-agent',
# install_rpath : rootlibexecdir,
# install : true)
# public_programs += [exe]
-#
-# exe = executable('busctl',
-# 'src/busctl/busctl.c',
-# 'src/busctl/busctl-introspect.c',
-# 'src/busctl/busctl-introspect.h',
-# include_directories : includes,
-# link_with : [libshared],
-# install_rpath : rootlibexecdir,
-# install : true)
-# public_programs += [exe]
-#
+#endif // 0
+
+exe = executable('busctl',
+ 'src/busctl/busctl.c',
+ 'src/busctl/busctl-introspect.c',
+ 'src/busctl/busctl-introspect.h',
+ include_directories : includes,
+ link_with : [libshared],
+ install_rpath : rootlibexecdir,
+ install : true)
+public_programs += [exe]
+
+#if 0 /// UNNEEDED by elogind
# if conf.get('ENABLE_SYSUSERS') == 1
# exe = executable('systemd-sysusers',
# 'src/sysusers/sysusers.c',
@@ -3040,8 +3069,6 @@ subdir('shell-completion/zsh')
# subdir('doc/var-log')
#endif // 0
-# FIXME: figure out if the warning is true:
-# https://github.com/mesonbuild/meson/wiki/Reference-manual#install_subdir
install_subdir('factory/etc',
install_dir : factorydir)
@@ -3215,13 +3242,13 @@ status = [
'rootexeclib dir: @0@'.format(rootlibexecdir),
#endif // 1
#if 0 /// UNNEEDED by elogind
-# 'SysV init scripts: @0@'.format(sysvinit_path),
-# 'SysV rc?.d directories: @0@'.format(sysvrcnd_path),
+# 'SysV init scripts: @0@'.format(sysvinit_path),
+# 'SysV rc?.d directories: @0@'.format(sysvrcnd_path),
#endif // 0
'PAM modules directory: @0@'.format(pamlibdir),
'PAM configuration directory: @0@'.format(pamconfdir),
#if 0 /// UNNEEDED by elogind
-# 'RPM macros directory: @0@'.format(rpmmacrosdir),
+# 'RPM macros directory: @0@'.format(rpmmacrosdir),
#endif // 0
'modprobe.d directory: @0@'.format(modprobedir),
'D-Bus policy directory: @0@'.format(dbuspolicydir),
@@ -3276,6 +3303,13 @@ status = [
# 'time epoch: @0@ (@1@)'.format(time_epoch, alt_time_epoch)]
#endif // 0
+status += [
+ 'static libelogind: @0@'.format(get_option('static-libelogind')),
+#if 0 /// elogind does not ship libudeb. Obviously.
+# 'static libudev: @0@'.format(get_option('static-libudev'))]
+#else
+ ]
+#endif // 0
# TODO:
# CFLAGS: ${OUR_CFLAGS} ${CFLAGS}
# CPPFLAGS: ${OUR_CPPFLAGS} ${CPPFLAGS}
@@ -3388,13 +3422,17 @@ foreach tuple : [
# ['wheel group', get_option('wheel-group')],
# ['gshadow'],
#endif // 0
- ['valgrind', conf.get('VALGRIND') == 1],
#if 1 /// Extra debugging for elogind
['debug elogind'],
#endif // 1
['debug hashmap'],
['debug mmap cache'],
-]
+ ['valgrind', conf.get('VALGRIND') == 1],
+#if 0 /// elogind neither ships libudev nor systemctl.
+# ['link-udev-shared', get_option('link-udev-shared')],
+# ['link-systemctl-shared', get_option('link-systemctl-shared')],
+#endif // 0
+ ]
if tuple.length() >= 2
cond = tuple[1]
diff --git a/meson_options.txt b/meson_options.txt
index 05f80ba..2e5fff6 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -343,9 +343,9 @@ option('dbus', type : 'combo', choices : ['auto', 'true', 'false'],
#if 0 /// UNNEEDED BY elogind
# option('gnu-efi', type : 'combo', choices : ['auto', 'true', 'false'],
# description : 'gnu-efi support for sd-boot')
-# option('efi-cc', type : 'string', value : 'gcc',
+# option('efi-cc', type : 'string',
# description : 'the compiler to use for EFI modules')
-# option('efi-ld', type : 'string', value : 'ld',
+# option('efi-ld', type : 'string',
# description : 'the linker to use for EFI modules')
# option('efi-libdir', type : 'string',
# description : 'path to the EFI lib directory')
diff --git a/pwx b/pwx
deleted file mode 160000
-Subproject a7a0ef894d5b221eeb675cea4f69a7d66922168
diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h
index ebe4288..2a6deb1 100644
--- a/src/basic/alloc-util.h
+++ b/src/basic/alloc-util.h
@@ -46,6 +46,21 @@ static inline void *mfree(void *memory) {
void* memdup(const void *p, size_t l) _alloc_(2);
void* memdup_suffix0(const void *p, size_t l) _alloc_(2);
+#define memdupa(p, l) \
+ ({ \
+ void *_q_; \
+ _q_ = alloca(l); \
+ memcpy(_q_, p, l); \
+ })
+
+#define memdupa_suffix0(p, l) \
+ ({ \
+ void *_q_; \
+ _q_ = alloca(l + 1); \
+ ((uint8_t*) _q_)[l] = 0; \
+ memcpy(_q_, p, l); \
+ })
+
static inline void freep(void *p) {
free(*(void**) p);
}
diff --git a/src/basic/env-util.h b/src/basic/env-util.h
index da76e60..486056a 100644
--- a/src/basic/env-util.h
+++ b/src/basic/env-util.h
@@ -4,6 +4,7 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
+//#include <string.h>
#include "macro.h"
diff --git a/src/basic/errno-list.c b/src/basic/errno-list.c
index 0549fce..1b72bbf 100644
--- a/src/basic/errno-list.c
+++ b/src/basic/errno-list.c
@@ -35,5 +35,3 @@ int errno_from_name(const char *name) {
assert(sc->id > 0);
return sc->id;
}
-#if 0 /// UNNEEDED by elogind
-#endif // 0
diff --git a/src/basic/exec-util.c b/src/basic/exec-util.c
index 7896f93..1d45924 100644
--- a/src/basic/exec-util.c
+++ b/src/basic/exec-util.c
@@ -71,11 +71,12 @@ static int do_execute(
gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
void* const callback_args[_STDOUT_CONSUME_MAX],
int output_fd,
- char *argv[]) {
+ char *argv[],
+ char *envp[]) {
_cleanup_hashmap_free_free_ Hashmap *pids = NULL;
_cleanup_strv_free_ char **paths = NULL;
- char **path;
+ char **path, **e;
int r;
/* We fork this all off from a child process so that we can somewhat cleanly make
@@ -86,7 +87,7 @@ static int do_execute(
r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE|CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char* const*) directories);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to enumerate executables: %m");
if (!callbacks) {
pids = hashmap_new(NULL);
@@ -100,6 +101,10 @@ static int do_execute(
if (timeout != USEC_INFINITY)
alarm(DIV_ROUND_UP(timeout, USEC_PER_SEC));
+ STRV_FOREACH(e, envp)
+ if (putenv(*e) != 0)
+ return log_error_errno(errno, "Failed to set environment variable: %m");
+
STRV_FOREACH(path, paths) {
_cleanup_free_ char *t = NULL;
_cleanup_close_ int fd = -1;
@@ -170,7 +175,8 @@ int execute_directories(
usec_t timeout,
gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
void* const callback_args[_STDOUT_CONSUME_MAX],
- char *argv[]) {
+ char *argv[],
+ char *envp[]) {
char **dirs = (char**) directories;
_cleanup_close_ int fd = -1;
@@ -201,7 +207,7 @@ int execute_directories(
if (r < 0)
return r;
if (r == 0) {
- r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv);
+ r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv, envp);
_exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
}
diff --git a/src/basic/exec-util.h b/src/basic/exec-util.h
index f81019f..2c745d5 100644
--- a/src/basic/exec-util.h
+++ b/src/basic/exec-util.h
@@ -18,7 +18,8 @@ int execute_directories(
usec_t timeout,
gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
void* const callback_args[_STDOUT_CONSUME_MAX],
- char *argv[]);
+ char *argv[],
+ char *envp[]);
#if 0 /// UNNEEDED by elogind
extern const gather_stdout_callback_t gather_environment[_STDOUT_CONSUME_MAX];
diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c
index 122b59a..229b4a8 100644
--- a/src/basic/fd-util.c
+++ b/src/basic/fd-util.c
@@ -357,22 +357,22 @@ bool fdname_is_valid(const char *s) {
#endif // 0
int fd_get_path(int fd, char **ret) {
- _cleanup_close_ int dir = -1;
- char fdname[DECIMAL_STR_MAX(int)];
+ char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
int r;
- dir = open("/proc/self/fd/", O_CLOEXEC | O_DIRECTORY | O_PATH);
- if (dir < 0)
- /* /proc is not available or not set up properly, we're most likely
- * in some chroot environment. */
- return errno == ENOENT ? -EOPNOTSUPP : -errno;
+ xsprintf(procfs_path, "/proc/self/fd/%i", fd);
+ r = readlink_malloc(procfs_path, ret);
+ if (r == -ENOENT) {
+ /* ENOENT can mean two things: that the fd does not exist or that /proc is not mounted. Let's make
+ * things debuggable and distuingish the two. */
- xsprintf(fdname, "%i", fd);
+ if (access("/proc/self/fd/", F_OK) < 0)
+ /* /proc is not available or not set up properly, we're most likely in some chroot
+ * environment. */
+ return errno == ENOENT ? -EOPNOTSUPP : -errno;
- r = readlinkat_malloc(dir, fdname, ret);
- if (r == -ENOENT)
- /* If the file doesn't exist the fd is invalid */
- return -EBADF;
+ return -EBADF; /* The directory exists, hence it's the fd that doesn't. */
+ }
return r;
}
diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h
index 2e5616c..b2d5b11 100644
--- a/src/basic/fd-util.h
+++ b/src/basic/fd-util.h
@@ -84,8 +84,12 @@ int fd_duplicate_data_fd(int fd);
#endif // 0
/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */
-#define ERRNO_IS_DISCONNECT(r) \
- IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH)
+/* The kernel sends e.g., EHOSTUNREACH or ENONET to userspace in some ICMP error cases.
+ * See the icmp_err_convert[] in net/ipv4/icmp.c in the kernel sources */
+#define ERRNO_IS_DISCONNECT(r) \
+ IN_SET(r, \
+ ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, \
+ ENETUNREACH, EHOSTUNREACH, ENOPROTOOPT, EHOSTDOWN, ENONET)
/* Resource exhaustion, could be our fault or general system trouble */
#define ERRNO_IS_RESOURCE(r) \
diff --git a/src/basic/fileio.c b/src/basic/fileio.c
index 7b1014b..41c3819 100644
--- a/src/basic/fileio.c
+++ b/src/basic/fileio.c
@@ -780,7 +780,6 @@ int load_env_file(FILE *f, const char *fname, const char *newline, char ***rl) {
*rl = m;
return 0;
}
-#endif // 0
static int load_env_file_push_pairs(
const char *filename, unsigned line,
@@ -830,7 +829,6 @@ int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char **
*rl = m;
return 0;
}
-#if 0 /// UNNEEDED by elogind
static int merge_env_file_push(
const char *filename, unsigned line,
@@ -1499,6 +1497,7 @@ int open_serialization_fd(const char *ident) {
#if 0 /// UNNEEDED by elogind
int link_tmpfile(int fd, const char *path, const char *target) {
+ int r;
assert(fd >= 0);
assert(target);
@@ -1511,8 +1510,9 @@ int link_tmpfile(int fd, const char *path, const char *target) {
* operation currently (renameat2() does), and there is no nice way to emulate this. */
if (path) {
- if (rename_noreplace(AT_FDCWD, path, AT_FDCWD, target) < 0)
- return -errno;
+ r = rename_noreplace(AT_FDCWD, path, AT_FDCWD, target);
+ if (r < 0)
+ return r;
} else {
char proc_fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
diff --git a/src/basic/fileio.h b/src/basic/fileio.h
index eac22ff..117e8c4 100644
--- a/src/basic/fileio.h
+++ b/src/basic/fileio.h
@@ -45,8 +45,8 @@ int parse_env_filev(FILE *f, const char *fname, const char *separator, va_list a
int parse_env_file(FILE *f, const char *fname, const char *separator, ...) _sentinel_;
#if 0 /// UNNEEDED by elogind
int load_env_file(FILE *f, const char *fname, const char *separator, char ***l);
-#endif // 0
int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l);
+#endif // 0
#if 0 /// UNNEEDED by elogind
int merge_env_file(char ***env, FILE *f, const char *fname);
diff --git a/src/basic/format-table.c b/src/basic/format-table.c
index 00473a2..d030520 100644
--- a/src/basic/format-table.c
+++ b/src/basic/format-table.c
@@ -36,7 +36,7 @@
that. The first row is always the header row. If header display is turned off we simply skip outputting the first
row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
- - Note because there's no row and no column object some properties that might be approproate as row/column properties
+ - Note because there's no row and no column object some properties that might be appropriate as row/column properties
are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
@@ -73,11 +73,11 @@ typedef struct TableData {
} TableData;
static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
- unsigned i;
+ size_t i;
assert(cell);
- i = PTR_TO_UINT(cell);
+ i = PTR_TO_SIZE(cell);
assert(i > 0);
return i-1;
@@ -85,7 +85,7 @@ static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
assert(index != (size_t) -1);
- return UINT_TO_PTR((unsigned) (index + 1));
+ return SIZE_TO_PTR(index + 1);
}
struct Table {
@@ -1140,14 +1140,12 @@ int table_print(Table *t, FILE *f) {
assert(weight_sum >= column_weight[j]);
weight_sum -= column_weight[j];
- if (restart)
+ if (restart && !finalize)
break;
}
- if (finalize) {
- assert(!restart);
+ if (finalize)
break;
- }
if (!restart)
finalize = true;
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index 562fe82..0518c20 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -134,7 +134,7 @@ int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char
#endif // 0
int readlinkat_malloc(int fd, const char *p, char **ret) {
- size_t l = 100;
+ size_t l = FILENAME_MAX+1;
int r;
assert(p);
diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c
index 15c8c88..40ae2c3 100644
--- a/src/basic/hashmap.c
+++ b/src/basic/hashmap.c
@@ -6,8 +6,8 @@
#include <string.h>
#include "alloc-util.h"
-#include "hashmap.h"
#include "fileio.h"
+#include "hashmap.h"
#include "macro.h"
#include "mempool.h"
#include "process-util.h"
@@ -769,17 +769,16 @@ static void reset_direct_storage(HashmapBase *h) {
static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) {
HashmapBase *h;
const struct hashmap_type_info *hi = &hashmap_type_info[type];
- bool use_pool;
-
- use_pool = is_main_thread();
+ bool up;
- h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size);
+ up = mempool_enabled();
+ h = up ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size);
if (!h)
return NULL;
h->type = type;
- h->from_pool = use_pool;
+ h->from_pool = up;
h->hash_ops = hash_ops ? hash_ops : &trivial_hash_ops;
if (type == HASHMAP_TYPE_ORDERED) {
@@ -857,9 +856,11 @@ static void hashmap_free_no_clear(HashmapBase *h) {
assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0);
#endif
- if (h->from_pool)
+ if (h->from_pool) {
+ /* Ensure that the object didn't get migrated between threads. */
+ assert_se(is_main_thread());
mempool_free_tile(hashmap_type_info[h->type].mempool, h);
- else
+ } else
free(h);
}
diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c
index 7748e83..e402ba8 100644
--- a/src/basic/hexdecoct.c
+++ b/src/basic/hexdecoct.c
@@ -592,8 +592,7 @@ static int base64_append_width(
_cleanup_free_ char *x = NULL;
char *t, *s;
- ssize_t slen, len, avail;
- int line, lines;
+ ssize_t len, slen, avail, line, lines;
len = base64mem(p, l, &x);
if (len <= 0)
@@ -602,6 +601,9 @@ static int base64_append_width(
lines = DIV_ROUND_UP(len, width);
slen = strlen_ptr(sep);
+ if (lines > (SSIZE_MAX - plen - 1 - slen) / (indent + width + 1))
+ return -ENOMEM;
+
t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines);
if (!t)
return -ENOMEM;
diff --git a/src/basic/mempool.c b/src/basic/mempool.c
index a5ec8a1..159c963 100644
--- a/src/basic/mempool.c
+++ b/src/basic/mempool.c
@@ -3,8 +3,10 @@
#include <stdint.h>
#include <stdlib.h>
+#include "env-util.h"
#include "macro.h"
#include "mempool.h"
+#include "process-util.h"
#include "util.h"
struct pool {
@@ -70,8 +72,21 @@ void mempool_free_tile(struct mempool *mp, void *p) {
mp->freelist = p;
}
-#if VALGRIND
+bool mempool_enabled(void) {
+ static int b = -1;
+
+ if (!is_main_thread())
+ return false;
+ if (!mempool_use_allowed)
+ b = false;
+ if (b < 0)
+ b = getenv_bool("SYSTEMD_MEMPOOL") != 0;
+
+ return b;
+}
+
+#if VALGRIND
void mempool_drop(struct mempool *mp) {
struct pool *p = mp->first_pool;
while (p) {
@@ -81,5 +96,4 @@ void mempool_drop(struct mempool *mp) {
p = n;
}
}
-
#endif
diff --git a/src/basic/mempool.h b/src/basic/mempool.h
index 2a41cb3..0eecca0 100644
--- a/src/basic/mempool.h
+++ b/src/basic/mempool.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
-
+#include <stdbool.h>
#include <stddef.h>
struct pool;
@@ -23,6 +23,9 @@ static struct mempool pool_name = { \
.at_least = alloc_at_least, \
}
+extern const bool mempool_use_allowed;
+bool mempool_enabled(void);
+
#if VALGRIND
void mempool_drop(struct mempool *mp);
#endif
diff --git a/src/basic/meson.build b/src/basic/meson.build
index c199e78..9b36a1e 100644
--- a/src/basic/meson.build
+++ b/src/basic/meson.build
@@ -122,7 +122,6 @@
# mkdir-label.c
# mkdir.c
# mkdir.h
-# module-util.h
# mount-util.c
# mount-util.h
# nss-util.h
@@ -368,6 +367,8 @@ basic_sources = files('''
virt.h
xattr-util.c
xattr-util.h
+ xml.c
+ xml.h
'''.split())
#endif // 0
diff --git a/src/basic/missing.h b/src/basic/missing.h
index fb14785..37d1c2a 100644
--- a/src/basic/missing.h
+++ b/src/basic/missing.h
@@ -15,18 +15,18 @@
#include <linux/neighbour.h>
#include <linux/oom.h>
#include <linux/rtnetlink.h>
-//#include <linux/stat.h>
#include <net/ethernet.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/socket.h>
-//#include <sys/stat.h>
+#include <sys/stat.h>
#include <sys/syscall.h>
#include <uchar.h>
#include <unistd.h>
-/// Additional includes needed by elogind
-#include "musl_missing.h"
+#if WANT_LINUX_STAT_H
+#include <linux/stat.h>
+#endif
#if HAVE_AUDIT
#include <libaudit.h>
@@ -57,6 +57,9 @@ struct sockaddr_vm {
};
#endif /* !HAVE_LINUX_VM_SOCKETS_H */
+/// Additional includes needed by elogind
+#include "musl_missing.h"
+
#ifndef RLIMIT_RTTIME
#define RLIMIT_RTTIME 15
#endif
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
index 3935d8a..e25e821 100644
--- a/src/basic/parse-util.c
+++ b/src/basic/parse-util.c
@@ -642,6 +642,8 @@ int parse_permille_unbounded(const char *p) {
r = safe_atoi(n, &v);
if (r < 0)
return r;
+ if (v < 0)
+ return -ERANGE;
} else {
pc = endswith(p, "%");
if (!pc)
@@ -662,15 +664,14 @@ int parse_permille_unbounded(const char *p) {
r = safe_atoi(n, &v);
if (r < 0)
return r;
+ if (v < 0)
+ return -ERANGE;
if (v > (INT_MAX - q) / 10)
return -ERANGE;
v = v * 10 + q;
}
- if (v < 0)
- return -ERANGE;
-
return v;
}
diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c
index ae33233..67da8d3 100644
--- a/src/basic/proc-cmdline.c
+++ b/src/basic/proc-cmdline.c
@@ -39,18 +39,12 @@ int proc_cmdline(char **ret) {
return read_one_line_file("/proc/cmdline", ret);
}
-int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, unsigned flags) {
-
- _cleanup_free_ char *line = NULL;
+int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, unsigned flags) {
const char *p;
int r;
assert(parse_item);
- r = proc_cmdline(&line);
- if (r < 0)
- return r;
-
p = line;
for (;;) {
_cleanup_free_ char *word = NULL;
@@ -67,11 +61,15 @@ int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, unsigned fla
/* Filter out arguments that are intended only for the initrd */
q = startswith(word, "rd.");
if (q) {
+#if 0 /// elogind is never in initrd. Doesn't make any sense.
if (!in_initrd())
continue;
if (flags & PROC_CMDLINE_STRIP_RD_PREFIX)
key = q;
+#else
+ continue;
+#endif // 0
}
value = strchr(key, '=');
@@ -86,6 +84,19 @@ int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, unsigned fla
return 0;
}
+int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, unsigned flags) {
+ _cleanup_free_ char *line = NULL;
+ int r;
+
+ assert(parse_item);
+
+ r = proc_cmdline(&line);
+ if (r < 0)
+ return r;
+
+ return proc_cmdline_parse_given(line, parse_item, data, flags);
+}
+
static bool relaxed_equal_char(char a, char b) {
return a == b ||
diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h
index 5cb323f..de4978f 100644
--- a/src/basic/proc-cmdline.h
+++ b/src/basic/proc-cmdline.h
@@ -14,6 +14,7 @@ typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *da
int proc_cmdline(char **ret);
+int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, unsigned flags);
int proc_cmdline_parse(const proc_cmdline_parse_t parse, void *userdata, unsigned flags);
#if 0 /// UNNEEDED by elogind
diff --git a/src/basic/process-util.c b/src/basic/process-util.c
index 77d7695..3930aa6 100644
--- a/src/basic/process-util.c
+++ b/src/basic/process-util.c
@@ -308,6 +308,12 @@ int rename_process(const char name[]) {
strncpy(program_invocation_name, name, k);
if (l > k)
truncated = true;
+
+#if 1 /// elogind takes care of situations where the short name points into the long.
+ if ( (program_invocation_short_name >= program_invocation_name)
+ && (program_invocation_short_name < program_invocation_name + k) )
+ program_invocation_short_name = program_invocation_name;
+#endif // 1
}
/* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
@@ -336,15 +342,33 @@ int rename_process(const char name[]) {
/* Now, let's tell the kernel about this new memory */
if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
- log_debug_errno(errno, "PR_SET_MM_ARG_START failed, proceeding without: %m");
- (void) munmap(nn, nn_size);
- goto use_saved_argv;
- }
+ /* HACK: prctl() API is kind of dumb on this point. The existing end address may already be
+ * below the desired start address, in which case the kernel may have kicked this back due
+ * to a range-check failure (see linux/kernel/sys.c:validate_prctl_map() to see this in
+ * action). The proper solution would be to have a prctl() API that could set both start+end
+ * simultaneously, or at least let us query the existing address to anticipate this condition
+ * and respond accordingly. For now, we can only guess at the cause of this failure and try
+ * a workaround--which will briefly expand the arg space to something potentially huge before
+ * resizing it to what we want. */
+ log_debug_errno(errno, "PR_SET_MM_ARG_START failed, attempting PR_SET_MM_ARG_END hack: %m");
+
+ if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) {
+ log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m");
+ (void) munmap(nn, nn_size);
+ goto use_saved_argv;
+ }
- /* And update the end pointer to the new end, too. If this fails, we don't really know what to do, it's
- * pretty unlikely that we can rollback, hence we'll just accept the failure, and continue. */
- if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
- log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
+ if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
+ log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m");
+ goto use_saved_argv;
+ }
+ } else {
+ /* And update the end pointer to the new end, too. If this fails, we don't really know what
+ * to do, it's pretty unlikely that we can rollback, hence we'll just accept the failure,
+ * and continue. */
+ if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
+ log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
+ }
if (mm)
(void) munmap(mm, mm_size);
diff --git a/src/basic/process-util.h b/src/basic/process-util.h
index f730552..e7e1975 100644
--- a/src/basic/process-util.h
+++ b/src/basic/process-util.h
@@ -150,12 +150,6 @@ static inline bool pid_is_valid(pid_t p) {
return p > 0;
}
-static inline int sched_policy_to_string_alloc_with_check(int n, char **s) {
- if (!sched_policy_is_valid(n))
- return -EINVAL;
-
- return sched_policy_to_string_alloc(n, s);
-}
#if 0 /// UNNEEDED by elogind
int ioprio_parse_priority(const char *s, int *ret);
diff --git a/src/basic/raw-clone.h b/src/basic/raw-clone.h
index fbd2d8c..caca34c 100644
--- a/src/basic/raw-clone.h
+++ b/src/basic/raw-clone.h
@@ -60,7 +60,7 @@ static inline pid_t raw_clone(unsigned long flags) {
"mov %%o0, %1" :
"=r"(in_child), "=r"(child_pid), "=r"(error) :
"i"(__NR_clone), "r"(flags) :
- "%o1", "%o0", "%g1" "cc" );
+ "%o1", "%o0", "%g1", "cc" );
if (error) {
errno = child_pid;
diff --git a/src/basic/selinux-util.c b/src/basic/selinux-util.c
index 15e62c4..674b2fb 100644
--- a/src/basic/selinux-util.c
+++ b/src/basic/selinux-util.c
@@ -16,12 +16,12 @@
#endif
#include "alloc-util.h"
-//#include "fd-util.h"
+#include "fd-util.h"
#include "log.h"
#include "macro.h"
#include "path-util.h"
#include "selinux-util.h"
-//#include "stdio-util.h"
+#include "stdio-util.h"
#include "time-util.h"
#include "util.h"
diff --git a/src/basic/sigbus.c b/src/basic/sigbus.c
index 70afba6..d5254ea 100644
--- a/src/basic/sigbus.c
+++ b/src/basic/sigbus.c
@@ -113,6 +113,10 @@ void sigbus_install(void) {
.sa_flags = SA_SIGINFO,
};
+ /* make sure that sysconf() is not called from a signal handler because
+ * it is not guaranteed to be async-signal-safe since POSIX.1-2008 */
+ (void) page_size();
+
n_installed++;
if (n_installed == 1)
diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c
index 7958e81..dcc3a54 100644
--- a/src/basic/socket-util.c
+++ b/src/basic/socket-util.c
@@ -53,6 +53,8 @@ DEFINE_STRING_TABLE_LOOKUP(socket_address_type, int);
int socket_address_parse(SocketAddress *a, const char *s) {
char *e, *n;
unsigned u;
+ _cleanup_free_ char *n = NULL;
+ char *e;
int r;
assert(a);
@@ -71,6 +73,9 @@ int socket_address_parse(SocketAddress *a, const char *s) {
return -EINVAL;
n = strndupa(s+1, e-s-1);
+ n = strndup(s+1, e-s-1);
+ if (!n)
+ return -ENOMEM;
errno = 0;
if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0)
@@ -134,6 +139,10 @@ int socket_address_parse(SocketAddress *a, const char *s) {
return r;
n = strndupa(cid_start, e - cid_start);
+ n = strndup(cid_start, e - cid_start);
+ if (!n)
+ return -ENOMEM;
+
if (!isempty(n)) {
r = safe_atou(n, &a->sockaddr.vm.svm_cid);
if (r < 0)
@@ -160,6 +169,9 @@ int socket_address_parse(SocketAddress *a, const char *s) {
return -EINVAL;
n = strndupa(s, e-s);
+ n = strndup(s, e-s);
+ if (!n)
+ return -ENOMEM;
/* IPv4 in w.x.y.z:p notation? */
r = inet_pton(AF_INET, n, &a->sockaddr.in.sin_addr);
@@ -762,21 +774,6 @@ int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret)
return 0;
}
-int socket_address_unlink(SocketAddress *a) {
- assert(a);
-
- if (socket_address_family(a) != AF_UNIX)
- return 0;
-
- if (a->sockaddr.un.sun_path[0] == 0)
- return 0;
-
- if (unlink(a->sockaddr.un.sun_path) < 0)
- return -errno;
-
- return 1;
-}
-
static const char* const netlink_family_table[] = {
[NETLINK_ROUTE] = "route",
[NETLINK_FIREWALL] = "firewall",
@@ -851,8 +848,8 @@ int fd_inc_sndbuf(int fd, size_t n) {
/* If we have the privileges we will ignore the kernel limit. */
value = (int) n;
- if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0)
- if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0)
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0)
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0)
return -errno;
return 1;
@@ -869,8 +866,8 @@ int fd_inc_rcvbuf(int fd, size_t n) {
/* If we have the privileges we will ignore the kernel limit. */
value = (int) n;
- if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0)
- if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0)
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0)
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0)
return -errno;
return 1;
}
@@ -1208,4 +1205,28 @@ int socket_ioctl_fd(void) {
return fd;
}
+
+int sockaddr_un_unlink(const struct sockaddr_un *sa) {
+ const char *p, * nul;
+
+ assert(sa);
+
+ if (sa->sun_family != AF_UNIX)
+ return -EPROTOTYPE;
+
+ if (sa->sun_path[0] == 0) /* Nothing to do for abstract sockets */
+ return 0;
+
+ /* The path in .sun_path is not necessarily NUL terminated. Let's fix that. */
+ nul = memchr(sa->sun_path, 0, sizeof(sa->sun_path));
+ if (nul)
+ p = sa->sun_path;
+ else
+ p = memdupa_suffix0(sa->sun_path, sizeof(sa->sun_path));
+
+ if (unlink(p) < 0)
+ return -errno;
+
+ return 1;
+}
#endif // 0
diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h
index 3519161..84c024a 100644
--- a/src/basic/socket-util.h
+++ b/src/basic/socket-util.h
@@ -74,7 +74,12 @@ int socket_address_parse_and_warn(SocketAddress *a, const char *s);
int socket_address_parse_netlink(SocketAddress *a, const char *s);
int socket_address_print(const SocketAddress *a, char **p);
int socket_address_verify(const SocketAddress *a) _pure_;
-int socket_address_unlink(SocketAddress *a);
+
+int sockaddr_un_unlink(const struct sockaddr_un *sa);
+
+static inline int socket_address_unlink(const SocketAddress *a) {
+ return socket_address_family(a) == AF_UNIX ? sockaddr_un_unlink(&a->sockaddr.un) : 0;
+}
bool socket_address_can_accept(const SocketAddress *a) _pure_;
diff --git a/src/basic/string-util.c b/src/basic/string-util.c
index d7ba435..38adaaf 100644
--- a/src/basic/string-util.c
+++ b/src/basic/string-util.c
@@ -1006,7 +1006,7 @@ int free_and_strdup(char **p, const char *s) {
assert(p);
- /* Replaces a string pointer with an strdup()ed new string,
+ /* Replaces a string pointer with a strdup()ed new string,
* possibly freeing the old one. */
if (streq_ptr(*p, s))
@@ -1025,6 +1025,32 @@ int free_and_strdup(char **p, const char *s) {
return 1;
}
+int free_and_strndup(char **p, const char *s, size_t l) {
+ char *t;
+
+ assert(p);
+ assert(s || l == 0);
+
+ /* Replaces a string pointer with a strndup()ed new string,
+ * freeing the old one. */
+
+ if (!*p && !s)
+ return 0;
+
+ if (*p && s && strneq(*p, s, l) && (l > strlen(*p) || (*p)[l] == '\0'))
+ return 0;
+
+ if (s) {
+ t = strndup(s, l);
+ if (!t)
+ return -ENOMEM;
+ } else
+ t = NULL;
+
+ free_and_replace(*p, t);
+ return 1;
+}
+
#if !HAVE_EXPLICIT_BZERO
/*
* Pointer to memset is volatile so that compiler must de-reference
diff --git a/src/basic/string-util.h b/src/basic/string-util.h
index d3a01a6..f0f73dd 100644
--- a/src/basic/string-util.h
+++ b/src/basic/string-util.h
@@ -52,11 +52,9 @@ static inline const char *empty_to_null(const char *p) {
return isempty(p) ? NULL : p;
}
-#if 0 /// UNNEEDED by elogind
static inline const char *empty_to_dash(const char *str) {
return isempty(str) ? "-" : str;
}
-#endif // 0
static inline char *startswith(const char *s, const char *prefix) {
size_t l;
@@ -182,6 +180,7 @@ char *strrep(const char *s, unsigned n);
int split_pair(const char *s, const char *sep, char **l, char **r);
int free_and_strdup(char **p, const char *s);
+int free_and_strndup(char **p, const char *s, size_t l);
/* Normal memmem() requires haystack to be nonnull, which is annoying for zero-length buffers */
static inline void *memmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) {
@@ -234,3 +233,27 @@ static inline void *memory_startswith(const void *p, size_t sz, const char *toke
return (uint8_t*) p + n;
}
+
+#if 0 /// Not needed by elogind, only test-string-util uses this.
+/* Like startswith_no_case(), but operates on arbitrary memory blocks.
+ * It works only for ASCII strings.
+ */
+static inline void *memory_startswith_no_case(const void *p, size_t sz, const char *token) {
+ size_t n, i;
+
+ assert(token);
+
+ n = strlen(token);
+ if (sz < n)
+ return NULL;
+
+ assert(p);
+
+ for (i = 0; i < n; i++) {
+ if (ascii_tolower(((char *)p)[i]) != ascii_tolower(token[i]))
+ return NULL;
+ }
+
+ return (uint8_t*) p + n;
+}
+#endif // 0
diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c
index f8623f9..d8e5d0d 100644
--- a/src/basic/terminal-util.c
+++ b/src/basic/terminal-util.c
@@ -830,11 +830,11 @@ unsigned columns(void) {
if (e)
(void) safe_atoi(e, &c);
- if (c <= 0)
+ if (c <= 0 || c > USHRT_MAX) {
c = fd_columns(STDOUT_FILENO);
-
- if (c <= 0)
- c = 80;
+ if (c <= 0)
+ c = 80;
+ }
cached_columns = c;
return cached_columns;
@@ -864,11 +864,11 @@ unsigned lines(void) {
if (e)
(void) safe_atoi(e, &l);
- if (l <= 0)
+ if (l <= 0 || l > USHRT_MAX) {
l = fd_lines(STDOUT_FILENO);
-
- if (l <= 0)
- l = 24;
+ if (l <= 0)
+ l = 24;
+ }
cached_lines = l;
return cached_lines;
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index 6bd86bf..fce250a 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -1073,18 +1073,19 @@ int parse_sec(const char *t, usec_t *usec) {
}
#if 0 /// UNNEEDED by elogind
-int parse_sec_fix_0(const char *t, usec_t *usec) {
- assert(t);
- assert(usec);
+int parse_sec_fix_0(const char *t, usec_t *ret) {
+ usec_t k;
+ int r;
- t += strspn(t, WHITESPACE);
+ assert(t);
+ assert(ret);
- if (streq(t, "0")) {
- *usec = USEC_INFINITY;
- return 0;
- }
+ r = parse_sec(t, &k);
+ if (r < 0)
+ return r;
- return parse_sec(t, usec);
+ *ret = k == 0 ? USEC_INFINITY : k;
+ return r;
}
int parse_nsec(const char *t, nsec_t *nsec) {
diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c
index ac6a9b3..2987a17 100644
--- a/src/basic/unit-def.c
+++ b/src/basic/unit-def.c
@@ -34,6 +34,7 @@ int unit_name_from_dbus_path(const char *path, char **name) {
return 0;
}
+#if 0 /// elogind does not support systemd units
const char* unit_dbus_interface_from_type(UnitType t) {
static const char *const table[_UNIT_TYPE_MAX] = {
@@ -67,6 +68,7 @@ const char *unit_dbus_interface_from_name(const char *name) {
return unit_dbus_interface_from_type(t);
}
+#endif // 0
static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
[UNIT_SERVICE] = "service",
diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h
index d7e2d74..415f06e 100644
--- a/src/basic/unit-def.h
+++ b/src/basic/unit-def.h
@@ -232,8 +232,10 @@ typedef enum NotifyAccess {
char *unit_dbus_path_from_name(const char *name);
int unit_name_from_dbus_path(const char *path, char **name);
+#if 0 /// elogind does not support systemd units
const char* unit_dbus_interface_from_type(UnitType t);
const char *unit_dbus_interface_from_name(const char *name);
+#endif // 0
const char *unit_type_to_string(UnitType i) _const_;
UnitType unit_type_from_string(const char *s) _pure_;
diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c
index bd0e3f4..13c86e3 100644
--- a/src/basic/unit-name.c
+++ b/src/basic/unit-name.c
@@ -102,6 +102,7 @@ bool unit_instance_is_valid(const char *i) {
return in_charset(i, "@" VALID_CHARS);
}
+#if 0 /// UNNEEDED by elogind
bool unit_suffix_is_valid(const char *s) {
if (isempty(s))
@@ -116,7 +117,6 @@ bool unit_suffix_is_valid(const char *s) {
return true;
}
-#if 0 /// UNNEEDED by elogind
int unit_name_to_prefix(const char *n, char **ret) {
const char *p;
char *s;
@@ -193,7 +193,6 @@ int unit_name_to_prefix_and_instance(const char *n, char **ret) {
*ret = s;
return 0;
}
-#endif // 0
UnitType unit_name_to_type(const char *n) {
const char *e;
@@ -208,7 +207,6 @@ UnitType unit_name_to_type(const char *n) {
return unit_type_from_string(e + 1);
}
-#if 0 /// UNNEEDED by elogind
int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
char *e, *s;
size_t a, b;
diff --git a/src/basic/unit-name.h b/src/basic/unit-name.h
index a9a2923..d625df0 100644
--- a/src/basic/unit-name.h
+++ b/src/basic/unit-name.h
@@ -8,8 +8,6 @@
#define UNIT_NAME_MAX 256
-#if 0 /// UNNEEDED by elogind
-#endif // 0
typedef enum UnitNameFlags {
UNIT_NAME_PLAIN = 1, /* Allow foo.service */
UNIT_NAME_INSTANCE = 2, /* Allow foo@bar.service */
@@ -20,9 +18,9 @@ typedef enum UnitNameFlags {
bool unit_name_is_valid(const char *n, UnitNameFlags flags) _pure_;
bool unit_prefix_is_valid(const char *p) _pure_;
bool unit_instance_is_valid(const char *i) _pure_;
+#if 0 /// UNNEEDED by elogind
bool unit_suffix_is_valid(const char *s) _pure_;
-#if 0 /// UNNEEDED by elogind
static inline int unit_prefix_and_instance_is_valid(const char *p) {
/* For prefix+instance and instance the same rules apply */
return unit_instance_is_valid(p);
@@ -31,11 +29,9 @@ static inline int unit_prefix_and_instance_is_valid(const char *p) {
int unit_name_to_prefix(const char *n, char **prefix);
int unit_name_to_instance(const char *n, char **instance);
int unit_name_to_prefix_and_instance(const char *n, char **ret);
-#endif // 0
UnitType unit_name_to_type(const char *n) _pure_;
-#if 0 /// UNNEEDED by elogind
int unit_name_change_suffix(const char *n, const char *suffix, char **ret);
#endif // 0
diff --git a/src/basic/user-util.h b/src/basic/user-util.h
index 14a4d18..484395b 100644
--- a/src/basic/user-util.h
+++ b/src/basic/user-util.h
@@ -113,7 +113,7 @@ int fgetgrent_sane(FILE *stream, struct group **gr);
int putpwent_sane(const struct passwd *pw, FILE *stream);
int putspent_sane(const struct spwd *sp, FILE *stream);
int putgrent_sane(const struct group *gr, FILE *stream);
-#ifdef ENABLE_GSHADOW
+#if ENABLE_GSHADOW
int fgetsgent_sane(FILE *stream, struct sgrp **sg);
int putsgent_sane(const struct sgrp *sg, FILE *stream);
#endif
diff --git a/src/basic/util.c b/src/basic/util.c
index d2ebb45..f00bf30 100644
--- a/src/basic/util.c
+++ b/src/basic/util.c
@@ -23,6 +23,7 @@
//#include "def.h"
//#include "device-nodes.h"
#include "dirent-util.h"
+//#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
//#include "format-util.h"
@@ -48,7 +49,9 @@
int saved_argc = 0;
char **saved_argv = NULL;
+#if 0 /// UNNEEDED by elogind
static int saved_in_initrd = -1;
+#endif // 0
size_t page_size(void) {
static thread_local size_t pgsz = 0;
@@ -131,10 +134,10 @@ int prot_from_flags(int flags) {
return -EINVAL;
}
}
-#endif // 0
bool in_initrd(void) {
struct statfs s;
+ int r;
if (saved_in_initrd >= 0)
return saved_in_initrd;
@@ -149,14 +152,20 @@ bool in_initrd(void) {
* emptying when transititioning to the main systemd.
*/
- saved_in_initrd = access("/etc/initrd-release", F_OK) >= 0 &&
- statfs("/", &s) >= 0 &&
- is_temporary_fs(&s);
+ r = getenv_bool_secure("SYSTEMD_IN_INITRD");
+ if (r < 0 && r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_IN_INITRD, ignoring: %m");
+
+ if (r >= 0)
+ saved_in_initrd = r > 0;
+ else
+ saved_in_initrd = access("/etc/initrd-release", F_OK) >= 0 &&
+ statfs("/", &s) >= 0 &&
+ is_temporary_fs(&s);
return saved_in_initrd;
}
-#if 0 /// UNNEEDED by elogind
void in_initrd_force(bool value) {
saved_in_initrd = value;
}
diff --git a/src/basic/util.h b/src/basic/util.h
index ff21a99..8e8ef68 100644
--- a/src/basic/util.h
+++ b/src/basic/util.h
@@ -69,10 +69,8 @@ extern char **saved_argv;
bool kexec_loaded(void);
int prot_from_flags(int flags) _const_;
-#endif // 0
bool in_initrd(void);
-#if 0 /// UNNEEDED by elogind
void in_initrd_force(bool value);
void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
diff --git a/src/basic/virt.c b/src/basic/virt.c
index ac42947..94c791c 100644
--- a/src/basic/virt.c
+++ b/src/basic/virt.c
@@ -11,6 +11,7 @@
#include "alloc-util.h"
#include "dirent-util.h"
+//#include "def.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
@@ -260,21 +261,38 @@ static int detect_vm_hypervisor(void) {
}
static int detect_vm_uml(void) {
- _cleanup_free_ char *cpuinfo_contents = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
int r;
/* Detect User-Mode Linux by reading /proc/cpuinfo */
- r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
- if (r == -ENOENT) {
- log_debug("/proc/cpuinfo not found, assuming no UML virtualization.");
- return VIRTUALIZATION_NONE;
+ f = fopen("/proc/cpuinfo", "re");
+ if (!f) {
+ if (errno == ENOENT) {
+ log_debug("/proc/cpuinfo not found, assuming no UML virtualization.");
+ return VIRTUALIZATION_NONE;
+ }
+ return -errno;
}
- if (r < 0)
- return r;
- if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) {
- log_debug("UML virtualization found in /proc/cpuinfo");
- return VIRTUALIZATION_UML;
+ for (;;) {
+ _cleanup_free_ char *line = NULL;
+ const char *t;
+
+ r = read_line(f, LONG_LINE_MAX, &line);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ t = startswith(line, "vendor_id\t: ");
+ if (t) {
+ if (startswith(t, "User Mode Linux")) {
+ log_debug("UML virtualization found in /proc/cpuinfo");
+ return VIRTUALIZATION_UML;
+ }
+
+ break;
+ }
}
log_debug("UML virtualization not found in /proc/cpuinfo.");
diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c
index a9ec8e1..d3d0194 100644
--- a/src/basic/xattr-util.c
+++ b/src/basic/xattr-util.c
@@ -2,7 +2,6 @@
#include <errno.h>
#include <fcntl.h>
-//#include <linux/stat.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
diff --git a/src/basic/xml.c b/src/basic/xml.c
new file mode 100644
index 0000000..cb34d87
--- /dev/null
+++ b/src/basic/xml.c
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "macro.h"
+#include "string-util.h"
+#include "xml.h"
+
+enum {
+ STATE_NULL,
+ STATE_TEXT,
+ STATE_TAG,
+ STATE_ATTRIBUTE,
+};
+
+static void inc_lines(unsigned *line, const char *s, size_t n) {
+ const char *p = s;
+
+ if (!line)
+ return;
+
+ for (;;) {
+ const char *f;
+
+ f = memchr(p, '\n', n);
+ if (!f)
+ return;
+
+ n -= (f - p) + 1;
+ p = f + 1;
+ (*line)++;
+ }
+}
+
+/* We don't actually do real XML here. We only read a simplistic
+ * subset, that is a bit less strict that XML and lacks all the more
+ * complex features, like entities, or namespaces. However, we do
+ * support some HTML5-like simplifications */
+
+int xml_tokenize(const char **p, char **name, void **state, unsigned *line) {
+ const char *c, *e, *b;
+ char *ret;
+ int t;
+
+ assert(p);
+ assert(*p);
+ assert(name);
+ assert(state);
+
+ t = PTR_TO_INT(*state);
+ c = *p;
+
+ if (t == STATE_NULL) {
+ if (line)
+ *line = 1;
+ t = STATE_TEXT;
+ }
+
+ for (;;) {
+ if (*c == 0)
+ return XML_END;
+
+ switch (t) {
+
+ case STATE_TEXT: {
+ int x;
+
+ e = strchrnul(c, '<');
+ if (e > c) {
+ /* More text... */
+ ret = strndup(c, e - c);
+ if (!ret)
+ return -ENOMEM;
+
+ inc_lines(line, c, e - c);
+
+ *name = ret;
+ *p = e;
+ *state = INT_TO_PTR(STATE_TEXT);
+
+ return XML_TEXT;
+ }
+
+ assert(*e == '<');
+ b = c + 1;
+
+ if (startswith(b, "!--")) {
+ /* A comment */
+ e = strstr(b + 3, "-->");
+ if (!e)
+ return -EINVAL;
+
+ inc_lines(line, b, e + 3 - b);
+
+ c = e + 3;
+ continue;
+ }
+
+ if (*b == '?') {
+ /* Processing instruction */
+
+ e = strstr(b + 1, "?>");
+ if (!e)
+ return -EINVAL;
+
+ inc_lines(line, b, e + 2 - b);
+
+ c = e + 2;
+ continue;
+ }
+
+ if (*b == '!') {
+ /* DTD */
+
+ e = strchr(b + 1, '>');
+ if (!e)
+ return -EINVAL;
+
+ inc_lines(line, b, e + 1 - b);
+
+ c = e + 1;
+ continue;
+ }
+
+ if (*b == '/') {
+ /* A closing tag */
+ x = XML_TAG_CLOSE;
+ b++;
+ } else
+ x = XML_TAG_OPEN;
+
+ e = strpbrk(b, WHITESPACE "/>");
+ if (!e)
+ return -EINVAL;
+
+ ret = strndup(b, e - b);
+ if (!ret)
+ return -ENOMEM;
+
+ *name = ret;
+ *p = e;
+ *state = INT_TO_PTR(STATE_TAG);
+
+ return x;
+ }
+
+ case STATE_TAG:
+
+ b = c + strspn(c, WHITESPACE);
+ if (*b == 0)
+ return -EINVAL;
+
+ inc_lines(line, c, b - c);
+
+ e = b + strcspn(b, WHITESPACE "=/>");
+ if (e > b) {
+ /* An attribute */
+
+ ret = strndup(b, e - b);
+ if (!ret)
+ return -ENOMEM;
+
+ *name = ret;
+ *p = e;
+ *state = INT_TO_PTR(STATE_ATTRIBUTE);
+
+ return XML_ATTRIBUTE_NAME;
+ }
+
+ if (startswith(b, "/>")) {
+ /* An empty tag */
+
+ *name = NULL; /* For empty tags we return a NULL name, the caller must be prepared for that */
+ *p = b + 2;
+ *state = INT_TO_PTR(STATE_TEXT);
+
+ return XML_TAG_CLOSE_EMPTY;
+ }
+
+ if (*b != '>')
+ return -EINVAL;
+
+ c = b + 1;
+ t = STATE_TEXT;
+ continue;
+
+ case STATE_ATTRIBUTE:
+
+ if (*c == '=') {
+ c++;
+
+ if (IN_SET(*c, '\'', '\"')) {
+ /* Tag with a quoted value */
+
+ e = strchr(c+1, *c);
+ if (!e)
+ return -EINVAL;
+
+ inc_lines(line, c, e - c);
+
+ ret = strndup(c+1, e - c - 1);
+ if (!ret)
+ return -ENOMEM;
+
+ *name = ret;
+ *p = e + 1;
+ *state = INT_TO_PTR(STATE_TAG);
+
+ return XML_ATTRIBUTE_VALUE;
+
+ }
+
+ /* Tag with a value without quotes */
+
+ b = strpbrk(c, WHITESPACE ">");
+ if (!b)
+ b = c;
+
+ ret = strndup(c, b - c);
+ if (!ret)
+ return -ENOMEM;
+
+ *name = ret;
+ *p = b;
+ *state = INT_TO_PTR(STATE_TAG);
+ return XML_ATTRIBUTE_VALUE;
+ }
+
+ t = STATE_TAG;
+ continue;
+ }
+
+ }
+
+ assert_not_reached("Bad state");
+}
diff --git a/src/basic/xml.h b/src/basic/xml.h
new file mode 100644
index 0000000..8da2ff5
--- /dev/null
+++ b/src/basic/xml.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+enum {
+ XML_END,
+ XML_TEXT,
+ XML_TAG_OPEN,
+ XML_TAG_CLOSE,
+ XML_TAG_CLOSE_EMPTY,
+ XML_ATTRIBUTE_NAME,
+ XML_ATTRIBUTE_VALUE,
+};
+
+int xml_tokenize(const char **p, char **name, void **state, unsigned *line);
diff --git a/src/busctl/busctl-introspect.c b/src/busctl/busctl-introspect.c
new file mode 100644
index 0000000..4af3481
--- /dev/null
+++ b/src/busctl/busctl-introspect.c
@@ -0,0 +1,763 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "busctl-introspect.h"
+#include "string-util.h"
+#include "util.h"
+#include "xml.h"
+
+#define NODE_DEPTH_MAX 16
+
+typedef struct Context {
+ const XMLIntrospectOps *ops;
+ void *userdata;
+
+ char *interface_name;
+ uint64_t interface_flags;
+
+ char *member_name;
+ char *member_signature;
+ char *member_result;
+ uint64_t member_flags;
+ bool member_writable;
+
+ const char *current;
+ void *xml_state;
+} Context;
+
+static void context_reset_member(Context *c) {
+ free(c->member_name);
+ free(c->member_signature);
+ free(c->member_result);
+
+ c->member_name = c->member_signature = c->member_result = NULL;
+ c->member_flags = 0;
+ c->member_writable = false;
+}
+
+static void context_reset_interface(Context *c) {
+ c->interface_name = mfree(c->interface_name);
+ c->interface_flags = 0;
+
+ context_reset_member(c);
+}
+
+static int parse_xml_annotation(Context *context, uint64_t *flags) {
+
+ enum {
+ STATE_ANNOTATION,
+ STATE_NAME,
+ STATE_VALUE
+ } state = STATE_ANNOTATION;
+
+ _cleanup_free_ char *field = NULL, *value = NULL;
+
+ assert(context);
+
+ for (;;) {
+ _cleanup_free_ char *name = NULL;
+
+ int t;
+
+ t = xml_tokenize(&context->current, &name, &context->xml_state, NULL);
+ if (t < 0) {
+ log_error("XML parse error.");
+ return t;
+ }
+
+ if (t == XML_END) {
+ log_error("Premature end of XML data.");
+ return -EBADMSG;
+ }
+
+ switch (state) {
+
+ case STATE_ANNOTATION:
+
+ if (t == XML_ATTRIBUTE_NAME) {
+
+ if (streq_ptr(name, "name"))
+ state = STATE_NAME;
+
+ else if (streq_ptr(name, "value"))
+ state = STATE_VALUE;
+
+ else {
+ log_error("Unexpected <annotation> attribute %s.", name);
+ return -EBADMSG;
+ }
+
+ } else if (t == XML_TAG_CLOSE_EMPTY ||
+ (t == XML_TAG_CLOSE && streq_ptr(name, "annotation"))) {
+
+ if (flags) {
+ if (streq_ptr(field, "org.freedesktop.DBus.Deprecated")) {
+
+ if (streq_ptr(value, "true"))
+ *flags |= SD_BUS_VTABLE_DEPRECATED;
+
+ } else if (streq_ptr(field, "org.freedesktop.DBus.Method.NoReply")) {
+
+ if (streq_ptr(value, "true"))
+ *flags |= SD_BUS_VTABLE_METHOD_NO_REPLY;
+
+ } else if (streq_ptr(field, "org.freedesktop.DBus.Property.EmitsChangedSignal")) {
+
+ if (streq_ptr(value, "const"))
+ *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) | SD_BUS_VTABLE_PROPERTY_CONST;
+ else if (streq_ptr(value, "invalidates"))
+ *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST)) | SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION;
+ else if (streq_ptr(value, "false"))
+ *flags = *flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION);
+ }
+ }
+
+ return 0;
+
+ } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
+ log_error("Unexpected token in <annotation>. (1)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(field, name);
+
+ state = STATE_ANNOTATION;
+ } else {
+ log_error("Unexpected token in <annotation>. (2)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_VALUE:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(value, name);
+
+ state = STATE_ANNOTATION;
+ } else {
+ log_error("Unexpected token in <annotation>. (3)");
+ return -EINVAL;
+ }
+
+ break;
+
+ default:
+ assert_not_reached("Bad state");
+ }
+ }
+}
+
+static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth) {
+
+ enum {
+ STATE_NODE,
+ STATE_NODE_NAME,
+ STATE_INTERFACE,
+ STATE_INTERFACE_NAME,
+ STATE_METHOD,
+ STATE_METHOD_NAME,
+ STATE_METHOD_ARG,
+ STATE_METHOD_ARG_NAME,
+ STATE_METHOD_ARG_TYPE,
+ STATE_METHOD_ARG_DIRECTION,
+ STATE_SIGNAL,
+ STATE_SIGNAL_NAME,
+ STATE_SIGNAL_ARG,
+ STATE_SIGNAL_ARG_NAME,
+ STATE_SIGNAL_ARG_TYPE,
+ STATE_SIGNAL_ARG_DIRECTION,
+ STATE_PROPERTY,
+ STATE_PROPERTY_NAME,
+ STATE_PROPERTY_TYPE,
+ STATE_PROPERTY_ACCESS,
+ } state = STATE_NODE;
+
+ _cleanup_free_ char *node_path = NULL, *argument_type = NULL, *argument_direction = NULL;
+ const char *np = prefix;
+ int r;
+
+ assert(context);
+ assert(prefix);
+
+ if (n_depth > NODE_DEPTH_MAX) {
+ log_error("<node> depth too high.");
+ return -EINVAL;
+ }
+
+ for (;;) {
+ _cleanup_free_ char *name = NULL;
+ int t;
+
+ t = xml_tokenize(&context->current, &name, &context->xml_state, NULL);
+ if (t < 0) {
+ log_error("XML parse error.");
+ return t;
+ }
+
+ if (t == XML_END) {
+ log_error("Premature end of XML data.");
+ return -EBADMSG;
+ }
+
+ switch (state) {
+
+ case STATE_NODE:
+ if (t == XML_ATTRIBUTE_NAME) {
+
+ if (streq_ptr(name, "name"))
+ state = STATE_NODE_NAME;
+ else {
+ log_error("Unexpected <node> attribute %s.", name);
+ return -EBADMSG;
+ }
+
+ } else if (t == XML_TAG_OPEN) {
+
+ if (streq_ptr(name, "interface"))
+ state = STATE_INTERFACE;
+ else if (streq_ptr(name, "node")) {
+
+ r = parse_xml_node(context, np, n_depth+1);
+ if (r < 0)
+ return r;
+ } else {
+ log_error("Unexpected <node> tag %s.", name);
+ return -EBADMSG;
+ }
+
+ } else if (t == XML_TAG_CLOSE_EMPTY ||
+ (t == XML_TAG_CLOSE && streq_ptr(name, "node"))) {
+
+ if (context->ops->on_path) {
+ r = context->ops->on_path(node_path ? node_path : np, context->userdata);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+
+ } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
+ log_error("Unexpected token in <node>. (1)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_NODE_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+
+ free(node_path);
+
+ if (name[0] == '/')
+ node_path = TAKE_PTR(name);
+ else {
+
+ if (endswith(prefix, "/"))
+ node_path = strappend(prefix, name);
+ else
+ node_path = strjoin(prefix, "/", name);
+ if (!node_path)
+ return log_oom();
+ }
+
+ np = node_path;
+ state = STATE_NODE;
+ } else {
+ log_error("Unexpected token in <node>. (2)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_INTERFACE:
+
+ if (t == XML_ATTRIBUTE_NAME) {
+ if (streq_ptr(name, "name"))
+ state = STATE_INTERFACE_NAME;
+ else {
+ log_error("Unexpected <interface> attribute %s.", name);
+ return -EBADMSG;
+ }
+
+ } else if (t == XML_TAG_OPEN) {
+ if (streq_ptr(name, "method"))
+ state = STATE_METHOD;
+ else if (streq_ptr(name, "signal"))
+ state = STATE_SIGNAL;
+ else if (streq_ptr(name, "property")) {
+ context->member_flags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE;
+ state = STATE_PROPERTY;
+ } else if (streq_ptr(name, "annotation")) {
+ r = parse_xml_annotation(context, &context->interface_flags);
+ if (r < 0)
+ return r;
+ } else {
+ log_error("Unexpected <interface> tag %s.", name);
+ return -EINVAL;
+ }
+ } else if (t == XML_TAG_CLOSE_EMPTY ||
+ (t == XML_TAG_CLOSE && streq_ptr(name, "interface"))) {
+
+ if (n_depth == 0) {
+ if (context->ops->on_interface) {
+ r = context->ops->on_interface(context->interface_name, context->interface_flags, context->userdata);
+ if (r < 0)
+ return r;
+ }
+
+ context_reset_interface(context);
+ }
+
+ state = STATE_NODE;
+
+ } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
+ log_error("Unexpected token in <interface>. (1)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_INTERFACE_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ if (n_depth == 0)
+ free_and_replace(context->interface_name, name);
+
+ state = STATE_INTERFACE;
+ } else {
+ log_error("Unexpected token in <interface>. (2)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_METHOD:
+
+ if (t == XML_ATTRIBUTE_NAME) {
+ if (streq_ptr(name, "name"))
+ state = STATE_METHOD_NAME;
+ else {
+ log_error("Unexpected <method> attribute %s", name);
+ return -EBADMSG;
+ }
+ } else if (t == XML_TAG_OPEN) {
+ if (streq_ptr(name, "arg"))
+ state = STATE_METHOD_ARG;
+ else if (streq_ptr(name, "annotation")) {
+ r = parse_xml_annotation(context, &context->member_flags);
+ if (r < 0)
+ return r;
+ } else {
+ log_error("Unexpected <method> tag %s.", name);
+ return -EINVAL;
+ }
+ } else if (t == XML_TAG_CLOSE_EMPTY ||
+ (t == XML_TAG_CLOSE && streq_ptr(name, "method"))) {
+
+ if (n_depth == 0) {
+ if (context->ops->on_method) {
+ r = context->ops->on_method(context->interface_name, context->member_name, context->member_signature, context->member_result, context->member_flags, context->userdata);
+ if (r < 0)
+ return r;
+ }
+
+ context_reset_member(context);
+ }
+
+ state = STATE_INTERFACE;
+
+ } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
+ log_error("Unexpected token in <method> (1).");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_METHOD_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ if (n_depth == 0)
+ free_and_replace(context->member_name, name);
+
+ state = STATE_METHOD;
+ } else {
+ log_error("Unexpected token in <method> (2).");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_METHOD_ARG:
+
+ if (t == XML_ATTRIBUTE_NAME) {
+ if (streq_ptr(name, "name"))
+ state = STATE_METHOD_ARG_NAME;
+ else if (streq_ptr(name, "type"))
+ state = STATE_METHOD_ARG_TYPE;
+ else if (streq_ptr(name, "direction"))
+ state = STATE_METHOD_ARG_DIRECTION;
+ else {
+ log_error("Unexpected method <arg> attribute %s.", name);
+ return -EBADMSG;
+ }
+ } else if (t == XML_TAG_OPEN) {
+ if (streq_ptr(name, "annotation")) {
+ r = parse_xml_annotation(context, NULL);
+ if (r < 0)
+ return r;
+ } else {
+ log_error("Unexpected method <arg> tag %s.", name);
+ return -EINVAL;
+ }
+ } else if (t == XML_TAG_CLOSE_EMPTY ||
+ (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
+
+ if (n_depth == 0) {
+
+ if (argument_type) {
+ if (!argument_direction || streq(argument_direction, "in")) {
+ if (!strextend(&context->member_signature, argument_type, NULL))
+ return log_oom();
+ } else if (streq(argument_direction, "out")) {
+ if (!strextend(&context->member_result, argument_type, NULL))
+ return log_oom();
+ } else
+ log_error("Unexpected method <arg> direction value '%s'.", argument_direction);
+ }
+
+ argument_type = mfree(argument_type);
+ argument_direction = mfree(argument_direction);
+ }
+
+ state = STATE_METHOD;
+ } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
+ log_error("Unexpected token in method <arg>. (1)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_METHOD_ARG_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE)
+ state = STATE_METHOD_ARG;
+ else {
+ log_error("Unexpected token in method <arg>. (2)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_METHOD_ARG_TYPE:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(argument_type, name);
+
+ state = STATE_METHOD_ARG;
+ } else {
+ log_error("Unexpected token in method <arg>. (3)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_METHOD_ARG_DIRECTION:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(argument_direction, name);
+
+ state = STATE_METHOD_ARG;
+ } else {
+ log_error("Unexpected token in method <arg>. (4)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_SIGNAL:
+
+ if (t == XML_ATTRIBUTE_NAME) {
+ if (streq_ptr(name, "name"))
+ state = STATE_SIGNAL_NAME;
+ else {
+ log_error("Unexpected <signal> attribute %s.", name);
+ return -EBADMSG;
+ }
+ } else if (t == XML_TAG_OPEN) {
+ if (streq_ptr(name, "arg"))
+ state = STATE_SIGNAL_ARG;
+ else if (streq_ptr(name, "annotation")) {
+ r = parse_xml_annotation(context, &context->member_flags);
+ if (r < 0)
+ return r;
+ } else {
+ log_error("Unexpected <signal> tag %s.", name);
+ return -EINVAL;
+ }
+ } else if (t == XML_TAG_CLOSE_EMPTY ||
+ (t == XML_TAG_CLOSE && streq_ptr(name, "signal"))) {
+
+ if (n_depth == 0) {
+ if (context->ops->on_signal) {
+ r = context->ops->on_signal(context->interface_name, context->member_name, context->member_signature, context->member_flags, context->userdata);
+ if (r < 0)
+ return r;
+ }
+
+ context_reset_member(context);
+ }
+
+ state = STATE_INTERFACE;
+
+ } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
+ log_error("Unexpected token in <signal>. (1)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_SIGNAL_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ if (n_depth == 0)
+ free_and_replace(context->member_name, name);
+
+ state = STATE_SIGNAL;
+ } else {
+ log_error("Unexpected token in <signal>. (2)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_SIGNAL_ARG:
+
+ if (t == XML_ATTRIBUTE_NAME) {
+ if (streq_ptr(name, "name"))
+ state = STATE_SIGNAL_ARG_NAME;
+ else if (streq_ptr(name, "type"))
+ state = STATE_SIGNAL_ARG_TYPE;
+ else if (streq_ptr(name, "direction"))
+ state = STATE_SIGNAL_ARG_DIRECTION;
+ else {
+ log_error("Unexpected signal <arg> attribute %s.", name);
+ return -EBADMSG;
+ }
+ } else if (t == XML_TAG_OPEN) {
+ if (streq_ptr(name, "annotation")) {
+ r = parse_xml_annotation(context, NULL);
+ if (r < 0)
+ return r;
+ } else {
+ log_error("Unexpected signal <arg> tag %s.", name);
+ return -EINVAL;
+ }
+ } else if (t == XML_TAG_CLOSE_EMPTY ||
+ (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
+
+ if (argument_type) {
+ if (!argument_direction || streq(argument_direction, "out")) {
+ if (!strextend(&context->member_signature, argument_type, NULL))
+ return log_oom();
+ } else
+ log_error("Unexpected signal <arg> direction value '%s'.", argument_direction);
+
+ argument_type = mfree(argument_type);
+ }
+
+ state = STATE_SIGNAL;
+ } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
+ log_error("Unexpected token in signal <arg> (1).");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_SIGNAL_ARG_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE)
+ state = STATE_SIGNAL_ARG;
+ else {
+ log_error("Unexpected token in signal <arg> (2).");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_SIGNAL_ARG_TYPE:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(argument_type, name);
+
+ state = STATE_SIGNAL_ARG;
+ } else {
+ log_error("Unexpected token in signal <arg> (3).");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_SIGNAL_ARG_DIRECTION:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(argument_direction, name);
+
+ state = STATE_SIGNAL_ARG;
+ } else {
+ log_error("Unexpected token in signal <arg>. (4)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_PROPERTY:
+
+ if (t == XML_ATTRIBUTE_NAME) {
+ if (streq_ptr(name, "name"))
+ state = STATE_PROPERTY_NAME;
+ else if (streq_ptr(name, "type"))
+ state = STATE_PROPERTY_TYPE;
+ else if (streq_ptr(name, "access"))
+ state = STATE_PROPERTY_ACCESS;
+ else {
+ log_error("Unexpected <property> attribute %s.", name);
+ return -EBADMSG;
+ }
+ } else if (t == XML_TAG_OPEN) {
+
+ if (streq_ptr(name, "annotation")) {
+ r = parse_xml_annotation(context, &context->member_flags);
+ if (r < 0)
+ return r;
+ } else {
+ log_error("Unexpected <property> tag %s.", name);
+ return -EINVAL;
+ }
+
+ } else if (t == XML_TAG_CLOSE_EMPTY ||
+ (t == XML_TAG_CLOSE && streq_ptr(name, "property"))) {
+
+ if (n_depth == 0) {
+ if (context->ops->on_property) {
+ r = context->ops->on_property(context->interface_name, context->member_name, context->member_signature, context->member_writable, context->member_flags, context->userdata);
+ if (r < 0)
+ return r;
+ }
+
+ context_reset_member(context);
+ }
+
+ state = STATE_INTERFACE;
+
+ } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
+ log_error("Unexpected token in <property>. (1)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_PROPERTY_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ if (n_depth == 0)
+ free_and_replace(context->member_name, name);
+
+ state = STATE_PROPERTY;
+ } else {
+ log_error("Unexpected token in <property>. (2)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_PROPERTY_TYPE:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ if (n_depth == 0)
+ free_and_replace(context->member_signature, name);
+
+ state = STATE_PROPERTY;
+ } else {
+ log_error("Unexpected token in <property>. (3)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_PROPERTY_ACCESS:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+
+ if (streq(name, "readwrite") || streq(name, "write"))
+ context->member_writable = true;
+
+ state = STATE_PROPERTY;
+ } else {
+ log_error("Unexpected token in <property>. (4)");
+ return -EINVAL;
+ }
+
+ break;
+ }
+ }
+}
+
+int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) {
+ Context context = {
+ .ops = ops,
+ .userdata = userdata,
+ .current = xml,
+ };
+
+ int r;
+
+ assert(prefix);
+ assert(xml);
+ assert(ops);
+
+ for (;;) {
+ _cleanup_free_ char *name = NULL;
+
+ r = xml_tokenize(&context.current, &name, &context.xml_state, NULL);
+ if (r < 0) {
+ log_error("XML parse error");
+ goto finish;
+ }
+
+ if (r == XML_END) {
+ r = 0;
+ break;
+ }
+
+ if (r == XML_TAG_OPEN) {
+
+ if (streq(name, "node")) {
+ r = parse_xml_node(&context, prefix, 0);
+ if (r < 0)
+ goto finish;
+ } else {
+ log_error("Unexpected tag '%s' in introspection data.", name);
+ r = -EBADMSG;
+ goto finish;
+ }
+ } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) {
+ log_error("Unexpected token.");
+ r = -EBADMSG;
+ goto finish;
+ }
+ }
+
+finish:
+ context_reset_interface(&context);
+
+ return r;
+}
diff --git a/src/busctl/busctl-introspect.h b/src/busctl/busctl-introspect.h
new file mode 100644
index 0000000..b17800e
--- /dev/null
+++ b/src/busctl/busctl-introspect.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+
+typedef struct XMLIntrospectOps {
+ int (*on_path)(const char *path, void *userdata);
+ int (*on_interface)(const char *name, uint64_t flags, void *userdata);
+ int (*on_method)(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata);
+ int (*on_signal)(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata);
+ int (*on_property)(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata);
+} XMLIntrospectOps;
+
+int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata);
diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c
new file mode 100644
index 0000000..efb68c8
--- /dev/null
+++ b/src/busctl/busctl.c
@@ -0,0 +1,2029 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <getopt.h>
+#include <stdio_ext.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-dump.h"
+#include "bus-internal.h"
+#include "bus-signature.h"
+#include "bus-type.h"
+#include "bus-util.h"
+#include "busctl-introspect.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "locale-util.h"
+#include "log.h"
+#include "pager.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "set.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "user-util.h"
+#include "util.h"
+#include "verbs.h"
+
+static bool arg_no_pager = false;
+static bool arg_legend = true;
+static char *arg_address = NULL;
+static bool arg_unique = false;
+static bool arg_acquired = false;
+static bool arg_activatable = false;
+static bool arg_show_machine = false;
+static char **arg_matches = NULL;
+static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
+static const char *arg_host = NULL;
+static bool arg_user = false;
+static size_t arg_snaplen = 4096;
+static bool arg_list = false;
+static bool arg_quiet = false;
+static bool arg_verbose = false;
+static bool arg_expect_reply = true;
+static bool arg_auto_start = true;
+static bool arg_allow_interactive_authorization = true;
+static bool arg_augment_creds = true;
+static bool arg_watch_bind = false;
+static usec_t arg_timeout = 0;
+
+#define NAME_IS_ACQUIRED INT_TO_PTR(1)
+#define NAME_IS_ACTIVATABLE INT_TO_PTR(2)
+
+static int acquire_bus(bool set_monitor, sd_bus **ret) {
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ r = sd_bus_new(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate bus: %m");
+
+ if (set_monitor) {
+ r = sd_bus_set_monitor(bus, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set monitor mode: %m");
+
+ r = sd_bus_negotiate_creds(bus, true, _SD_BUS_CREDS_ALL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable credentials: %m");
+
+ r = sd_bus_negotiate_timestamp(bus, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable timestamps: %m");
+
+ r = sd_bus_negotiate_fds(bus, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable fds: %m");
+ }
+
+ r = sd_bus_set_bus_client(bus, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set bus client: %m");
+
+ r = sd_bus_set_watch_bind(bus, arg_watch_bind);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set watch-bind setting to '%s': %m", yes_no(arg_watch_bind));
+
+ if (arg_address)
+ r = sd_bus_set_address(bus, arg_address);
+ else {
+ switch (arg_transport) {
+
+ case BUS_TRANSPORT_LOCAL:
+ if (arg_user) {
+ bus->is_user = true;
+ r = bus_set_address_user(bus);
+ } else {
+ bus->is_system = true;
+ r = bus_set_address_system(bus);
+ }
+ break;
+
+ case BUS_TRANSPORT_REMOTE:
+ r = bus_set_address_system_remote(bus, arg_host);
+ break;
+
+ case BUS_TRANSPORT_MACHINE:
+ r = bus_set_address_system_machine(bus, arg_host);
+ break;
+
+ default:
+ assert_not_reached("Hmm, unknown transport type.");
+ }
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to set address: %m");
+
+ r = sd_bus_start(bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to bus: %m");
+
+ *ret = TAKE_PTR(bus);
+
+ return 0;
+}
+
+static int list_bus_names(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL;
+ _cleanup_free_ char **merged = NULL;
+ _cleanup_hashmap_free_ Hashmap *names = NULL;
+ char **i;
+ int r;
+ size_t max_i = 0;
+ unsigned n = 0;
+ void *v;
+ char *k;
+ Iterator iterator;
+
+ if (!arg_unique && !arg_acquired && !arg_activatable)
+ arg_unique = arg_acquired = arg_activatable = true;
+
+ r = acquire_bus(false, &bus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_list_names(bus, (arg_acquired || arg_unique) ? &acquired : NULL, arg_activatable ? &activatable : NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to list names: %m");
+
+ (void) pager_open(arg_no_pager, false);
+
+ names = hashmap_new(&string_hash_ops);
+ if (!names)
+ return log_oom();
+
+ STRV_FOREACH(i, acquired) {
+ max_i = MAX(max_i, strlen(*i));
+
+ r = hashmap_put(names, *i, NAME_IS_ACQUIRED);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add to hashmap: %m");
+ }
+
+ STRV_FOREACH(i, activatable) {
+ max_i = MAX(max_i, strlen(*i));
+
+ r = hashmap_put(names, *i, NAME_IS_ACTIVATABLE);
+ if (r < 0 && r != -EEXIST)
+ return log_error_errno(r, "Failed to add to hashmap: %m");
+ }
+
+ merged = new(char*, hashmap_size(names) + 1);
+ if (!merged)
+ return log_oom();
+
+ HASHMAP_FOREACH_KEY(v, k, names, iterator)
+ merged[n++] = k;
+
+ merged[n] = NULL;
+ strv_sort(merged);
+
+ if (arg_legend) {
+ printf("%-*s %*s %-*s %-*s %-*s %-*s %-*s %-*s",
+ (int) max_i, "NAME", 10, "PID", 15, "PROCESS", 16, "USER", 13, "CONNECTION", 25, "UNIT", 10, "SESSION", 19, "DESCRIPTION");
+
+ if (arg_show_machine)
+ puts(" MACHINE");
+ else
+ putchar('\n');
+ }
+
+ STRV_FOREACH(i, merged) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ sd_id128_t mid;
+
+ if (hashmap_get(names, *i) == NAME_IS_ACTIVATABLE) {
+ /* Activatable */
+
+ printf("%-*s", (int) max_i, *i);
+ printf(" - - - (activatable) - - ");
+ if (arg_show_machine)
+ puts(" -");
+ else
+ putchar('\n');
+ continue;
+
+ }
+
+ if (!arg_unique && (*i)[0] == ':')
+ continue;
+
+ if (!arg_acquired && (*i)[0] != ':')
+ continue;
+
+ printf("%-*s", (int) max_i, *i);
+
+ r = sd_bus_get_name_creds(
+ bus, *i,
+ (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) |
+ SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM|
+ SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_SESSION|
+ SD_BUS_CREDS_DESCRIPTION, &creds);
+ if (r >= 0) {
+#if 0 /// elogind does not support systemd units
+ const char *unique, *session, *unit, *cn;
+#else
+ const char *unique, *session, *cn;
+#endif // 0
+ pid_t pid;
+ uid_t uid;
+
+ r = sd_bus_creds_get_pid(creds, &pid);
+ if (r >= 0) {
+ const char *comm = NULL;
+
+ sd_bus_creds_get_comm(creds, &comm);
+
+ printf(" %10lu %-15s", (unsigned long) pid, strna(comm));
+ } else
+ fputs(" - - ", stdout);
+
+ r = sd_bus_creds_get_euid(creds, &uid);
+ if (r >= 0) {
+ _cleanup_free_ char *u = NULL;
+
+ u = uid_to_name(uid);
+ if (!u)
+ return log_oom();
+
+ if (strlen(u) > 16)
+ u[16] = 0;
+
+ printf(" %-16s", u);
+ } else
+ fputs(" - ", stdout);
+
+ r = sd_bus_creds_get_unique_name(creds, &unique);
+ if (r >= 0)
+ printf(" %-13s", unique);
+ else
+ fputs(" - ", stdout);
+
+#if 0 /// elogind does not support systemd units
+ r = sd_bus_creds_get_unit(creds, &unit);
+ if (r >= 0) {
+ _cleanup_free_ char *e;
+
+ e = ellipsize(unit, 25, 100);
+ if (!e)
+ return log_oom();
+
+ printf(" %-25s", e);
+ } else
+ fputs(" - ", stdout);
+#endif // 0
+
+ r = sd_bus_creds_get_session(creds, &session);
+ if (r >= 0)
+ printf(" %-10s", session);
+ else
+ fputs(" - ", stdout);
+
+ r = sd_bus_creds_get_description(creds, &cn);
+ if (r >= 0)
+ printf(" %-19s", cn);
+ else
+ fputs(" - ", stdout);
+
+ } else
+ printf(" - - - - - - - ");
+
+ if (arg_show_machine) {
+ r = sd_bus_get_name_machine_id(bus, *i, &mid);
+ if (r >= 0) {
+ char m[SD_ID128_STRING_MAX];
+ printf(" %s\n", sd_id128_to_string(mid, m));
+ } else
+ puts(" -");
+ } else
+ putchar('\n');
+ }
+
+ return 0;
+}
+
+static void print_subtree(const char *prefix, const char *path, char **l) {
+ const char *vertical, *space;
+ char **n;
+
+ /* We assume the list is sorted. Let's first skip over the
+ * entry we are looking at. */
+ for (;;) {
+ if (!*l)
+ return;
+
+ if (!streq(*l, path))
+ break;
+
+ l++;
+ }
+
+ vertical = strjoina(prefix, special_glyph(TREE_VERTICAL));
+ space = strjoina(prefix, special_glyph(TREE_SPACE));
+
+ for (;;) {
+ bool has_more = false;
+
+ if (!*l || !path_startswith(*l, path))
+ break;
+
+ n = l + 1;
+ for (;;) {
+ if (!*n || !path_startswith(*n, path))
+ break;
+
+ if (!path_startswith(*n, *l)) {
+ has_more = true;
+ break;
+ }
+
+ n++;
+ }
+
+ printf("%s%s%s\n", prefix, special_glyph(has_more ? TREE_BRANCH : TREE_RIGHT), *l);
+
+ print_subtree(has_more ? vertical : space, *l, l);
+ l = n;
+ }
+}
+
+static void print_tree(const char *prefix, char **l) {
+
+ prefix = strempty(prefix);
+
+ if (arg_list) {
+ char **i;
+
+ STRV_FOREACH(i, l)
+ printf("%s%s\n", prefix, *i);
+ return;
+ }
+
+ if (strv_isempty(l)) {
+ printf("No objects discovered.\n");
+ return;
+ }
+
+ if (streq(l[0], "/") && !l[1]) {
+ printf("Only root object discovered.\n");
+ return;
+ }
+
+ print_subtree(prefix, "/", l);
+}
+
+static int on_path(const char *path, void *userdata) {
+ Set *paths = userdata;
+ int r;
+
+ assert(paths);
+
+ r = set_put_strdup(paths, path);
+ if (r < 0)
+ return log_oom();
+
+ return 0;
+}
+
+static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *paths, bool many) {
+ static const XMLIntrospectOps ops = {
+ .on_path = on_path,
+ };
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ const char *xml;
+ int r;
+
+ r = sd_bus_call_method(bus, service, path, "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, "");
+ if (r < 0) {
+ if (many)
+ printf("Failed to introspect object %s of service %s: %s\n", path, service, bus_error_message(&error, r));
+ else
+ log_error_errno(r, "Failed to introspect object %s of service %s: %s", path, service, bus_error_message(&error, r));
+ return r;
+ }
+
+ r = sd_bus_message_read(reply, "s", &xml);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ return parse_xml_introspect(path, xml, &ops, paths);
+}
+
+static int tree_one(sd_bus *bus, const char *service, const char *prefix, bool many) {
+ _cleanup_set_free_free_ Set *paths = NULL, *done = NULL, *failed = NULL;
+ _cleanup_free_ char **l = NULL;
+ char *m;
+ int r;
+
+ paths = set_new(&string_hash_ops);
+ if (!paths)
+ return log_oom();
+
+ done = set_new(&string_hash_ops);
+ if (!done)
+ return log_oom();
+
+ failed = set_new(&string_hash_ops);
+ if (!failed)
+ return log_oom();
+
+ m = strdup("/");
+ if (!m)
+ return log_oom();
+
+ r = set_put(paths, m);
+ if (r < 0) {
+ free(m);
+ return log_oom();
+ }
+
+ for (;;) {
+ _cleanup_free_ char *p = NULL;
+ int q;
+
+ p = set_steal_first(paths);
+ if (!p)
+ break;
+
+ if (set_contains(done, p) ||
+ set_contains(failed, p))
+ continue;
+
+ q = find_nodes(bus, service, p, paths, many);
+ if (q < 0) {
+ if (r >= 0)
+ r = q;
+
+ q = set_put(failed, p);
+ } else
+ q = set_put(done, p);
+
+ if (q < 0)
+ return log_oom();
+
+ assert(q != 0);
+ p = NULL;
+ }
+
+ (void) pager_open(arg_no_pager, false);
+
+ l = set_get_strv(done);
+ if (!l)
+ return log_oom();
+
+ strv_sort(l);
+ print_tree(prefix, l);
+
+ fflush(stdout);
+
+ return r;
+}
+
+static int tree(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ char **i;
+ int r = 0;
+
+ if (!arg_unique && !arg_acquired)
+ arg_acquired = true;
+
+ r = acquire_bus(false, &bus);
+ if (r < 0)
+ return r;
+
+ if (argc <= 1) {
+ _cleanup_strv_free_ char **names = NULL;
+ bool not_first = false;
+
+ r = sd_bus_list_names(bus, &names, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get name list: %m");
+
+ (void) pager_open(arg_no_pager, false);
+
+ STRV_FOREACH(i, names) {
+ int q;
+
+ if (!arg_unique && (*i)[0] == ':')
+ continue;
+
+ if (!arg_acquired && (*i)[0] == ':')
+ continue;
+
+ if (not_first)
+ printf("\n");
+
+ printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal());
+
+ q = tree_one(bus, *i, NULL, true);
+ if (q < 0 && r >= 0)
+ r = q;
+
+ not_first = true;
+ }
+ } else {
+ STRV_FOREACH(i, argv+1) {
+ int q;
+
+ if (i > argv+1)
+ printf("\n");
+
+ if (argv[2]) {
+ (void) pager_open(arg_no_pager, false);
+ printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal());
+ }
+
+ q = tree_one(bus, *i, NULL, !!argv[2]);
+ if (q < 0 && r >= 0)
+ r = q;
+ }
+ }
+
+ return r;
+}
+
+static int format_cmdline(sd_bus_message *m, FILE *f, bool needs_space) {
+ int r;
+
+ for (;;) {
+ const char *contents = NULL;
+ char type;
+ union {
+ uint8_t u8;
+ uint16_t u16;
+ int16_t s16;
+ uint32_t u32;
+ int32_t s32;
+ uint64_t u64;
+ int64_t s64;
+ double d64;
+ const char *string;
+ int i;
+ } basic;
+
+ r = sd_bus_message_peek_type(m, &type, &contents);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return needs_space;
+
+ if (bus_type_is_container(type) > 0) {
+
+ r = sd_bus_message_enter_container(m, type, contents);
+ if (r < 0)
+ return r;
+
+ if (type == SD_BUS_TYPE_ARRAY) {
+ unsigned n = 0;
+
+ /* count array entries */
+ for (;;) {
+
+ r = sd_bus_message_skip(m, contents);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ n++;
+ }
+
+ r = sd_bus_message_rewind(m, false);
+ if (r < 0)
+ return r;
+
+ if (needs_space)
+ fputc(' ', f);
+
+ fprintf(f, "%u", n);
+ needs_space = true;
+
+ } else if (type == SD_BUS_TYPE_VARIANT) {
+
+ if (needs_space)
+ fputc(' ', f);
+
+ fprintf(f, "%s", contents);
+ needs_space = true;
+ }
+
+ r = format_cmdline(m, f, needs_space);
+ if (r < 0)
+ return r;
+
+ needs_space = r > 0;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ continue;
+ }
+
+ r = sd_bus_message_read_basic(m, type, &basic);
+ if (r < 0)
+ return r;
+
+ if (needs_space)
+ fputc(' ', f);
+
+ switch (type) {
+ case SD_BUS_TYPE_BYTE:
+ fprintf(f, "%u", basic.u8);
+ break;
+
+ case SD_BUS_TYPE_BOOLEAN:
+ fputs(true_false(basic.i), f);
+ break;
+
+ case SD_BUS_TYPE_INT16:
+ fprintf(f, "%i", basic.s16);
+ break;
+
+ case SD_BUS_TYPE_UINT16:
+ fprintf(f, "%u", basic.u16);
+ break;
+
+ case SD_BUS_TYPE_INT32:
+ fprintf(f, "%i", basic.s32);
+ break;
+
+ case SD_BUS_TYPE_UINT32:
+ fprintf(f, "%u", basic.u32);
+ break;
+
+ case SD_BUS_TYPE_INT64:
+ fprintf(f, "%" PRIi64, basic.s64);
+ break;
+
+ case SD_BUS_TYPE_UINT64:
+ fprintf(f, "%" PRIu64, basic.u64);
+ break;
+
+ case SD_BUS_TYPE_DOUBLE:
+ fprintf(f, "%g", basic.d64);
+ break;
+
+ case SD_BUS_TYPE_STRING:
+ case SD_BUS_TYPE_OBJECT_PATH:
+ case SD_BUS_TYPE_SIGNATURE: {
+ _cleanup_free_ char *b = NULL;
+
+ b = cescape(basic.string);
+ if (!b)
+ return -ENOMEM;
+
+ fprintf(f, "\"%s\"", b);
+ break;
+ }
+
+ case SD_BUS_TYPE_UNIX_FD:
+ fprintf(f, "%i", basic.i);
+ break;
+
+ default:
+ assert_not_reached("Unknown basic type.");
+ }
+
+ needs_space = true;
+ }
+}
+
+typedef struct Member {
+ const char *type;
+ char *interface;
+ char *name;
+ char *signature;
+ char *result;
+ char *value;
+ bool writable;
+ uint64_t flags;
+} Member;
+
+static void member_hash_func(const void *p, struct siphash *state) {
+ const Member *m = p;
+ uint64_t arity = 1;
+
+ assert(m);
+ assert(m->type);
+
+ string_hash_func(m->type, state);
+
+ arity += !!m->name + !!m->interface;
+
+ uint64_hash_func(&arity, state);
+
+ if (m->name)
+ string_hash_func(m->name, state);
+
+ if (m->interface)
+ string_hash_func(m->interface, state);
+}
+
+static int member_compare_func(const void *a, const void *b) {
+ const Member *x = a, *y = b;
+ int d;
+
+ assert(x);
+ assert(y);
+ assert(x->type);
+ assert(y->type);
+
+ d = strcmp_ptr(x->interface, y->interface);
+ if (d != 0)
+ return d;
+
+ d = strcmp(x->type, y->type);
+ if (d != 0)
+ return d;
+
+ return strcmp_ptr(x->name, y->name);
+}
+
+static int member_compare_funcp(const void *a, const void *b) {
+ const Member *const * x = (const Member *const *) a, * const *y = (const Member *const *) b;
+
+ return member_compare_func(*x, *y);
+}
+
+static void member_free(Member *m) {
+ if (!m)
+ return;
+
+ free(m->interface);
+ free(m->name);
+ free(m->signature);
+ free(m->result);
+ free(m->value);
+ free(m);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Member*, member_free);
+
+static void member_set_free(Set *s) {
+ set_free_with_destructor(s, member_free);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, member_set_free);
+
+static int on_interface(const char *interface, uint64_t flags, void *userdata) {
+ _cleanup_(member_freep) Member *m;
+ Set *members = userdata;
+ int r;
+
+ assert(interface);
+ assert(members);
+
+ m = new0(Member, 1);
+ if (!m)
+ return log_oom();
+
+ m->type = "interface";
+ m->flags = flags;
+
+ r = free_and_strdup(&m->interface, interface);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(members, m);
+ if (r <= 0) {
+ log_error("Duplicate interface");
+ return -EINVAL;
+ }
+
+ m = NULL;
+ return 0;
+}
+
+static int on_method(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata) {
+ _cleanup_(member_freep) Member *m;
+ Set *members = userdata;
+ int r;
+
+ assert(interface);
+ assert(name);
+
+ m = new0(Member, 1);
+ if (!m)
+ return log_oom();
+
+ m->type = "method";
+ m->flags = flags;
+
+ r = free_and_strdup(&m->interface, interface);
+ if (r < 0)
+ return log_oom();
+
+ r = free_and_strdup(&m->name, name);
+ if (r < 0)
+ return log_oom();
+
+ r = free_and_strdup(&m->signature, signature);
+ if (r < 0)
+ return log_oom();
+
+ r = free_and_strdup(&m->result, result);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(members, m);
+ if (r <= 0) {
+ log_error("Duplicate method");
+ return -EINVAL;
+ }
+
+ m = NULL;
+ return 0;
+}
+
+static int on_signal(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata) {
+ _cleanup_(member_freep) Member *m;
+ Set *members = userdata;
+ int r;
+
+ assert(interface);
+ assert(name);
+
+ m = new0(Member, 1);
+ if (!m)
+ return log_oom();
+
+ m->type = "signal";
+ m->flags = flags;
+
+ r = free_and_strdup(&m->interface, interface);
+ if (r < 0)
+ return log_oom();
+
+ r = free_and_strdup(&m->name, name);
+ if (r < 0)
+ return log_oom();
+
+ r = free_and_strdup(&m->signature, signature);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(members, m);
+ if (r <= 0) {
+ log_error("Duplicate signal");
+ return -EINVAL;
+ }
+
+ m = NULL;
+ return 0;
+}
+
+static int on_property(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata) {
+ _cleanup_(member_freep) Member *m;
+ Set *members = userdata;
+ int r;
+
+ assert(interface);
+ assert(name);
+
+ m = new0(Member, 1);
+ if (!m)
+ return log_oom();
+
+ m->type = "property";
+ m->flags = flags;
+ m->writable = writable;
+
+ r = free_and_strdup(&m->interface, interface);
+ if (r < 0)
+ return log_oom();
+
+ r = free_and_strdup(&m->name, name);
+ if (r < 0)
+ return log_oom();
+
+ r = free_and_strdup(&m->signature, signature);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(members, m);
+ if (r <= 0) {
+ log_error("Duplicate property");
+ return -EINVAL;
+ }
+
+ m = NULL;
+ return 0;
+}
+
+static int introspect(int argc, char **argv, void *userdata) {
+ static const struct hash_ops member_hash_ops = {
+ .hash = member_hash_func,
+ .compare = member_compare_func,
+ };
+
+ static const XMLIntrospectOps ops = {
+ .on_interface = on_interface,
+ .on_method = on_method,
+ .on_signal = on_signal,
+ .on_property = on_property,
+ };
+
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_xml = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(member_set_freep) Set *members = NULL;
+ unsigned name_width, type_width, signature_width, result_width, j, k = 0;
+ Member *m, **sorted = NULL;
+ Iterator i;
+ const char *xml;
+ int r;
+
+ r = acquire_bus(false, &bus);
+ if (r < 0)
+ return r;
+
+ members = set_new(&member_hash_ops);
+ if (!members)
+ return log_oom();
+
+ r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply_xml, "");
+ if (r < 0)
+ return log_error_errno(r, "Failed to introspect object %s of service %s: %s", argv[2], argv[1], bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply_xml, "s", &xml);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ /* First, get list of all properties */
+ r = parse_xml_introspect(argv[2], xml, &ops, members);
+ if (r < 0)
+ return r;
+
+ /* Second, find the current values for them */
+ SET_FOREACH(m, members, i) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+
+ if (!streq(m->type, "property"))
+ continue;
+
+ if (m->value)
+ continue;
+
+ if (argv[3] && !streq(argv[3], m->interface))
+ continue;
+
+ r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", m->interface);
+ if (r < 0)
+ return log_error_errno(r, "%s", bus_error_message(&error, r));
+
+ r = sd_bus_message_enter_container(reply, 'a', "{sv}");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ for (;;) {
+ Member *z;
+ _cleanup_free_ char *buf = NULL;
+ _cleanup_fclose_ FILE *mf = NULL;
+ size_t sz = 0;
+ const char *name;
+
+ r = sd_bus_message_enter_container(reply, 'e', "sv");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ if (r == 0)
+ break;
+
+ r = sd_bus_message_read(reply, "s", &name);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_enter_container(reply, 'v', NULL);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ mf = open_memstream(&buf, &sz);
+ if (!mf)
+ return log_oom();
+
+ (void) __fsetlocking(mf, FSETLOCKING_BYCALLER);
+
+ r = format_cmdline(reply, mf, false);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ mf = safe_fclose(mf);
+
+ z = set_get(members, &((Member) {
+ .type = "property",
+ .interface = m->interface,
+ .name = (char*) name }));
+ if (z)
+ free_and_replace(z->value, buf);
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ }
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ }
+
+ (void) pager_open(arg_no_pager, false);
+
+ name_width = STRLEN("NAME");
+ type_width = STRLEN("TYPE");
+ signature_width = STRLEN("SIGNATURE");
+ result_width = STRLEN("RESULT/VALUE");
+
+ sorted = newa(Member*, set_size(members));
+
+ SET_FOREACH(m, members, i) {
+
+ if (argv[3] && !streq(argv[3], m->interface))
+ continue;
+
+ if (m->interface)
+ name_width = MAX(name_width, strlen(m->interface));
+ if (m->name)
+ name_width = MAX(name_width, strlen(m->name) + 1);
+ if (m->type)
+ type_width = MAX(type_width, strlen(m->type));
+ if (m->signature)
+ signature_width = MAX(signature_width, strlen(m->signature));
+ if (m->result)
+ result_width = MAX(result_width, strlen(m->result));
+ if (m->value)
+ result_width = MAX(result_width, strlen(m->value));
+
+ sorted[k++] = m;
+ }
+
+ if (result_width > 40)
+ result_width = 40;
+
+ qsort(sorted, k, sizeof(Member*), member_compare_funcp);
+
+ if (arg_legend) {
+ printf("%-*s %-*s %-*s %-*s %s\n",
+ (int) name_width, "NAME",
+ (int) type_width, "TYPE",
+ (int) signature_width, "SIGNATURE",
+ (int) result_width, "RESULT/VALUE",
+ "FLAGS");
+ }
+
+ for (j = 0; j < k; j++) {
+ _cleanup_free_ char *ellipsized = NULL;
+ const char *rv;
+ bool is_interface;
+
+ m = sorted[j];
+
+ if (argv[3] && !streq(argv[3], m->interface))
+ continue;
+
+ is_interface = streq(m->type, "interface");
+
+ if (argv[3] && is_interface)
+ continue;
+
+ if (m->value) {
+ ellipsized = ellipsize(m->value, result_width, 100);
+ if (!ellipsized)
+ return log_oom();
+
+ rv = ellipsized;
+ } else
+ rv = empty_to_dash(m->result);
+
+ printf("%s%s%-*s%s %-*s %-*s %-*s%s%s%s%s%s%s\n",
+ is_interface ? ansi_highlight() : "",
+ is_interface ? "" : ".",
+ - !is_interface + (int) name_width, empty_to_dash(streq_ptr(m->type, "interface") ? m->interface : m->name),
+ is_interface ? ansi_normal() : "",
+ (int) type_width, empty_to_dash(m->type),
+ (int) signature_width, empty_to_dash(m->signature),
+ (int) result_width, rv,
+ (m->flags & SD_BUS_VTABLE_DEPRECATED) ? " deprecated" : (m->flags || m->writable ? "" : " -"),
+ (m->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) ? " no-reply" : "",
+ (m->flags & SD_BUS_VTABLE_PROPERTY_CONST) ? " const" : "",
+ (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) ? " emits-change" : "",
+ (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) ? " emits-invalidation" : "",
+ m->writable ? " writable" : "");
+ }
+
+ return 0;
+}
+
+static int message_dump(sd_bus_message *m, FILE *f) {
+ return bus_message_dump(m, f, BUS_MESSAGE_DUMP_WITH_HEADER);
+}
+
+static int message_pcap(sd_bus_message *m, FILE *f) {
+ return bus_message_pcap_frame(m, arg_snaplen, f);
+}
+
+static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f)) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ char **i;
+ uint32_t flags = 0;
+ const char *unique_name;
+ bool is_monitor = false;
+ int r;
+
+ r = acquire_bus(true, &bus);
+ if (r < 0)
+ return r;
+
+ /* upgrade connection; it's not used for anything else after this call */
+ r = sd_bus_message_new_method_call(bus, &message, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus.Monitoring", "BecomeMonitor");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(message, 'a', "s");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ STRV_FOREACH(i, argv+1) {
+ _cleanup_free_ char *m = NULL;
+
+ if (!service_name_is_valid(*i)) {
+ log_error("Invalid service name '%s'", *i);
+ return -EINVAL;
+ }
+
+ m = strjoin("sender='", *i, "'");
+ if (!m)
+ return log_oom();
+
+ r = sd_bus_message_append_basic(message, 's', m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ free(m);
+ m = strjoin("destination='", *i, "'");
+ if (!m)
+ return log_oom();
+
+ r = sd_bus_message_append_basic(message, 's', m);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ STRV_FOREACH(i, arg_matches) {
+ r = sd_bus_message_append_basic(message, 's', *i);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ r = sd_bus_message_close_container(message);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_basic(message, 'u', &flags);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_call(bus, message, arg_timeout, &error, NULL);
+ if (r < 0)
+ return log_error_errno(r, "%s", bus_error_message(&error, r));
+
+ r = sd_bus_get_unique_name(bus, &unique_name);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get unique name: %m");
+
+ log_info("Monitoring bus message stream.");
+
+ for (;;) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+ r = sd_bus_process(bus, &m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to process bus: %m");
+
+ if (!is_monitor) {
+ const char *name;
+
+ /* wait until we lose our unique name */
+ if (sd_bus_message_is_signal(m, "org.freedesktop.DBus", "NameLost") <= 0)
+ continue;
+
+ r = sd_bus_message_read(m, "s", &name);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read lost name: %m");
+
+ if (streq(name, unique_name))
+ is_monitor = true;
+
+ continue;
+ }
+
+ if (m) {
+ dump(m, stdout);
+ fflush(stdout);
+
+ if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected") > 0) {
+ log_info("Connection terminated, exiting.");
+ return 0;
+ }
+
+ continue;
+ }
+
+ if (r > 0)
+ continue;
+
+ r = sd_bus_wait(bus, (uint64_t) -1);
+ if (r < 0)
+ return log_error_errno(r, "Failed to wait for bus: %m");
+ }
+}
+
+static int verb_monitor(int argc, char **argv, void *userdata) {
+ return monitor(argc, argv, message_dump);
+}
+
+static int verb_capture(int argc, char **argv, void *userdata) {
+ int r;
+
+ if (isatty(fileno(stdout)) > 0) {
+ log_error("Refusing to write message data to console, please redirect output to a file.");
+ return -EINVAL;
+ }
+
+ bus_pcap_header(arg_snaplen, stdout);
+
+ r = monitor(argc, argv, message_pcap);
+ if (r < 0)
+ return r;
+
+ r = fflush_and_check(stdout);
+ if (r < 0)
+ return log_error_errno(r, "Couldn't write capture file: %m");
+
+ return r;
+}
+
+static int status(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ pid_t pid;
+ int r;
+
+ r = acquire_bus(false, &bus);
+ if (r < 0)
+ return r;
+
+ if (!isempty(argv[1])) {
+ r = parse_pid(argv[1], &pid);
+ if (r < 0)
+ r = sd_bus_get_name_creds(
+ bus,
+ argv[1],
+ (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL,
+ &creds);
+ else
+ r = sd_bus_creds_new_from_pid(
+ &creds,
+ pid,
+ _SD_BUS_CREDS_ALL);
+ } else {
+ const char *scope, *address;
+ sd_id128_t bus_id;
+
+ r = sd_bus_get_address(bus, &address);
+ if (r >= 0)
+ printf("BusAddress=%s%s%s\n", ansi_highlight(), address, ansi_normal());
+
+ r = sd_bus_get_scope(bus, &scope);
+ if (r >= 0)
+ printf("BusScope=%s%s%s\n", ansi_highlight(), scope, ansi_normal());
+
+ r = sd_bus_get_bus_id(bus, &bus_id);
+ if (r >= 0)
+ printf("BusID=%s" SD_ID128_FORMAT_STR "%s\n", ansi_highlight(), SD_ID128_FORMAT_VAL(bus_id), ansi_normal());
+
+ r = sd_bus_get_owner_creds(
+ bus,
+ (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL,
+ &creds);
+ }
+
+ if (r < 0)
+ return log_error_errno(r, "Failed to get credentials: %m");
+
+ bus_creds_dump(creds, NULL, false);
+ return 0;
+}
+
+static int message_append_cmdline(sd_bus_message *m, const char *signature, char ***x) {
+ char **p;
+ int r;
+
+ assert(m);
+ assert(signature);
+ assert(x);
+
+ p = *x;
+
+ for (;;) {
+ const char *v;
+ char t;
+
+ t = *signature;
+ v = *p;
+
+ if (t == 0)
+ break;
+ if (!v) {
+ log_error("Too few parameters for signature.");
+ return -EINVAL;
+ }
+
+ signature++;
+ p++;
+
+ switch (t) {
+
+ case SD_BUS_TYPE_BOOLEAN:
+
+ r = parse_boolean(v);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse as boolean: %s", v);
+
+ r = sd_bus_message_append_basic(m, t, &r);
+ break;
+
+ case SD_BUS_TYPE_BYTE: {
+ uint8_t z;
+
+ r = safe_atou8(v, &z);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse as byte (unsigned 8bit integer): %s", v);
+
+ r = sd_bus_message_append_basic(m, t, &z);
+ break;
+ }
+
+ case SD_BUS_TYPE_INT16: {
+ int16_t z;
+
+ r = safe_atoi16(v, &z);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse as signed 16bit integer: %s", v);
+
+ r = sd_bus_message_append_basic(m, t, &z);
+ break;
+ }
+
+ case SD_BUS_TYPE_UINT16: {
+ uint16_t z;
+
+ r = safe_atou16(v, &z);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse as unsigned 16bit integer: %s", v);
+
+ r = sd_bus_message_append_basic(m, t, &z);
+ break;
+ }
+
+ case SD_BUS_TYPE_INT32: {
+ int32_t z;
+
+ r = safe_atoi32(v, &z);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse as signed 32bit integer: %s", v);
+
+ r = sd_bus_message_append_basic(m, t, &z);
+ break;
+ }
+
+ case SD_BUS_TYPE_UINT32: {
+ uint32_t z;
+
+ r = safe_atou32(v, &z);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse as unsigned 32bit integer: %s", v);
+
+ r = sd_bus_message_append_basic(m, t, &z);
+ break;
+ }
+
+ case SD_BUS_TYPE_INT64: {
+ int64_t z;
+
+ r = safe_atoi64(v, &z);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse as signed 64bit integer: %s", v);
+
+ r = sd_bus_message_append_basic(m, t, &z);
+ break;
+ }
+
+ case SD_BUS_TYPE_UINT64: {
+ uint64_t z;
+
+ r = safe_atou64(v, &z);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse as unsigned 64bit integer: %s", v);
+
+ r = sd_bus_message_append_basic(m, t, &z);
+ break;
+ }
+
+ case SD_BUS_TYPE_DOUBLE: {
+ double z;
+
+ r = safe_atod(v, &z);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse as double precision floating point: %s", v);
+
+ r = sd_bus_message_append_basic(m, t, &z);
+ break;
+ }
+
+ case SD_BUS_TYPE_STRING:
+ case SD_BUS_TYPE_OBJECT_PATH:
+ case SD_BUS_TYPE_SIGNATURE:
+
+ r = sd_bus_message_append_basic(m, t, v);
+ break;
+
+ case SD_BUS_TYPE_ARRAY: {
+ uint32_t n;
+ size_t k;
+
+ r = safe_atou32(v, &n);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse number of array entries: %s", v);
+
+ r = signature_element_length(signature, &k);
+ if (r < 0)
+ return log_error_errno(r, "Invalid array signature.");
+
+ {
+ unsigned i;
+ char s[k + 1];
+ memcpy(s, signature, k);
+ s[k] = 0;
+
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ for (i = 0; i < n; i++) {
+ r = message_append_cmdline(m, s, &p);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ signature += k;
+
+ r = sd_bus_message_close_container(m);
+ break;
+ }
+
+ case SD_BUS_TYPE_VARIANT:
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, v);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = message_append_cmdline(m, v, &p);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(m);
+ break;
+
+ case SD_BUS_TYPE_STRUCT_BEGIN:
+ case SD_BUS_TYPE_DICT_ENTRY_BEGIN: {
+ size_t k;
+
+ signature--;
+ p--;
+
+ r = signature_element_length(signature, &k);
+ if (r < 0)
+ return log_error_errno(r, "Invalid struct/dict entry signature.");
+
+ {
+ char s[k-1];
+ memcpy(s, signature + 1, k - 2);
+ s[k - 2] = 0;
+
+ r = sd_bus_message_open_container(m, t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = message_append_cmdline(m, s, &p);
+ if (r < 0)
+ return r;
+ }
+
+ signature += k;
+
+ r = sd_bus_message_close_container(m);
+ break;
+ }
+
+ case SD_BUS_TYPE_UNIX_FD:
+ log_error("UNIX file descriptor not supported as type.");
+ return -EINVAL;
+
+ default:
+ log_error("Unknown signature type %c.", t);
+ return -EINVAL;
+ }
+
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ *x = p;
+ return 0;
+}
+
+static int call(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ int r;
+
+ r = acquire_bus(false, &bus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], argv[3], argv[4]);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_set_expect_reply(m, arg_expect_reply);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_set_auto_start(m, arg_auto_start);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_set_allow_interactive_authorization(m, arg_allow_interactive_authorization);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ if (!isempty(argv[5])) {
+ char **p;
+
+ p = argv+6;
+
+ r = message_append_cmdline(m, argv[5], &p);
+ if (r < 0)
+ return r;
+
+ if (*p) {
+ log_error("Too many parameters for signature.");
+ return -EINVAL;
+ }
+ }
+
+ if (!arg_expect_reply) {
+ r = sd_bus_send(bus, m, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to send message: %m");
+
+ return 0;
+ }
+
+ r = sd_bus_call(bus, m, arg_timeout, &error, &reply);
+ if (r < 0)
+ return log_error_errno(r, "%s", bus_error_message(&error, r));
+
+ r = sd_bus_message_is_empty(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ if (r == 0 && !arg_quiet) {
+
+ if (arg_verbose) {
+ (void) pager_open(arg_no_pager, false);
+
+ r = bus_message_dump(reply, stdout, 0);
+ if (r < 0)
+ return r;
+ } else {
+
+ fputs(sd_bus_message_get_signature(reply, true), stdout);
+ fputc(' ', stdout);
+
+ r = format_cmdline(reply, stdout, false);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ fputc('\n', stdout);
+ }
+ }
+
+ return 0;
+}
+
+static int get_property(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ char **i;
+ int r;
+
+ r = acquire_bus(false, &bus);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, argv + 4) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ const char *contents = NULL;
+ char type;
+
+ r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "Get", &error, &reply, "ss", argv[3], *i);
+ if (r < 0)
+ return log_error_errno(r, "%s", bus_error_message(&error, r));
+
+ r = sd_bus_message_peek_type(reply, &type, &contents);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_enter_container(reply, 'v', contents);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ if (arg_verbose) {
+ (void) pager_open(arg_no_pager, false);
+
+ r = bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_SUBTREE_ONLY);
+ if (r < 0)
+ return r;
+ } else {
+ fputs(contents, stdout);
+ fputc(' ', stdout);
+
+ r = format_cmdline(reply, stdout, false);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ fputc('\n', stdout);
+ }
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ }
+
+ return 0;
+}
+
+static int set_property(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ char **p;
+ int r;
+
+ r = acquire_bus(false, &bus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], "org.freedesktop.DBus.Properties", "Set");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "ss", argv[3], argv[4]);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'v', argv[5]);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ p = argv + 6;
+ r = message_append_cmdline(m, argv[5], &p);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ if (*p) {
+ log_error("Too many parameters for signature.");
+ return -EINVAL;
+ }
+
+ r = sd_bus_call(bus, m, arg_timeout, &error, NULL);
+ if (r < 0)
+ return log_error_errno(r, "%s", bus_error_message(&error, r));
+
+ return 0;
+}
+
+static int help(void) {
+ printf("%s [OPTIONS...] {COMMAND} ...\n\n"
+ "Introspect the bus.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " --no-legend Do not show the headers and footers\n"
+ " --system Connect to system bus\n"
+ " --user Connect to user bus\n"
+ " -H --host=[USER@]HOST Operate on remote host\n"
+ " -M --machine=CONTAINER Operate on local container\n"
+ " --address=ADDRESS Connect to bus specified by address\n"
+ " --show-machine Show machine ID column in list\n"
+ " --unique Only show unique names\n"
+ " --acquired Only show acquired names\n"
+ " --activatable Only show activatable names\n"
+ " --match=MATCH Only show matching messages\n"
+ " --size=SIZE Maximum length of captured packet\n"
+ " --list Don't show tree, but simple object path list\n"
+ " -q --quiet Don't show method call reply\n"
+ " --verbose Show result values in long format\n"
+ " --expect-reply=BOOL Expect a method call reply\n"
+ " --auto-start=BOOL Auto-start destination service\n"
+ " --allow-interactive-authorization=BOOL\n"
+ " Allow interactive authorization for operation\n"
+ " --timeout=SECS Maximum time to wait for method call completion\n"
+ " --augment-creds=BOOL Extend credential data with data read from /proc/$PID\n"
+ " --watch-bind=BOOL Wait for bus AF_UNIX socket to be bound in the file\n"
+ " system\n\n"
+ "Commands:\n"
+ " list List bus names\n"
+ " status [SERVICE] Show bus service, process or bus owner credentials\n"
+ " monitor [SERVICE...] Show bus traffic\n"
+ " capture [SERVICE...] Capture bus traffic as pcap\n"
+ " tree [SERVICE...] Show object tree of service\n"
+ " introspect SERVICE OBJECT [INTERFACE]\n"
+ " call SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT...]]\n"
+ " Call a method\n"
+ " get-property SERVICE OBJECT INTERFACE PROPERTY...\n"
+ " Get property value\n"
+ " set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n"
+ " Set property value\n"
+ " help Show this help\n"
+ , program_invocation_short_name);
+
+ return 0;
+}
+
+static int verb_help(int argc, char **argv, void *userdata) {
+ return help();
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_NO_PAGER,
+ ARG_NO_LEGEND,
+ ARG_SYSTEM,
+ ARG_USER,
+ ARG_ADDRESS,
+ ARG_MATCH,
+ ARG_SHOW_MACHINE,
+ ARG_UNIQUE,
+ ARG_ACQUIRED,
+ ARG_ACTIVATABLE,
+ ARG_SIZE,
+ ARG_LIST,
+ ARG_VERBOSE,
+ ARG_EXPECT_REPLY,
+ ARG_AUTO_START,
+ ARG_ALLOW_INTERACTIVE_AUTHORIZATION,
+ ARG_TIMEOUT,
+ ARG_AUGMENT_CREDS,
+ ARG_WATCH_BIND,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "system", no_argument, NULL, ARG_SYSTEM },
+ { "user", no_argument, NULL, ARG_USER },
+ { "address", required_argument, NULL, ARG_ADDRESS },
+ { "show-machine", no_argument, NULL, ARG_SHOW_MACHINE },
+ { "unique", no_argument, NULL, ARG_UNIQUE },
+ { "acquired", no_argument, NULL, ARG_ACQUIRED },
+ { "activatable", no_argument, NULL, ARG_ACTIVATABLE },
+ { "match", required_argument, NULL, ARG_MATCH },
+ { "host", required_argument, NULL, 'H' },
+ { "machine", required_argument, NULL, 'M' },
+ { "size", required_argument, NULL, ARG_SIZE },
+ { "list", no_argument, NULL, ARG_LIST },
+ { "quiet", no_argument, NULL, 'q' },
+ { "verbose", no_argument, NULL, ARG_VERBOSE },
+ { "expect-reply", required_argument, NULL, ARG_EXPECT_REPLY },
+ { "auto-start", required_argument, NULL, ARG_AUTO_START },
+ { "allow-interactive-authorization", required_argument, NULL, ARG_ALLOW_INTERACTIVE_AUTHORIZATION },
+ { "timeout", required_argument, NULL, ARG_TIMEOUT },
+ { "augment-creds",required_argument, NULL, ARG_AUGMENT_CREDS},
+ { "watch-bind", required_argument, NULL, ARG_WATCH_BIND },
+ {},
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hH:M:q", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_NO_PAGER:
+ arg_no_pager = true;
+ break;
+
+ case ARG_NO_LEGEND:
+ arg_legend = false;
+ break;
+
+ case ARG_USER:
+ arg_user = true;
+ break;
+
+ case ARG_SYSTEM:
+ arg_user = false;
+ break;
+
+ case ARG_ADDRESS:
+ arg_address = optarg;
+ break;
+
+ case ARG_SHOW_MACHINE:
+ arg_show_machine = true;
+ break;
+
+ case ARG_UNIQUE:
+ arg_unique = true;
+ break;
+
+ case ARG_ACQUIRED:
+ arg_acquired = true;
+ break;
+
+ case ARG_ACTIVATABLE:
+ arg_activatable = true;
+ break;
+
+ case ARG_MATCH:
+ if (strv_extend(&arg_matches, optarg) < 0)
+ return log_oom();
+ break;
+
+ case ARG_SIZE: {
+ uint64_t sz;
+
+ r = parse_size(optarg, 1024, &sz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse size: %s", optarg);
+
+ if ((uint64_t) (size_t) sz != sz) {
+ log_error("Size out of range.");
+ return -E2BIG;
+ }
+
+ arg_snaplen = (size_t) sz;
+ break;
+ }
+
+ case ARG_LIST:
+ arg_list = true;
+ break;
+
+ case 'H':
+ arg_transport = BUS_TRANSPORT_REMOTE;
+ arg_host = optarg;
+ break;
+
+ case 'M':
+ arg_transport = BUS_TRANSPORT_MACHINE;
+ arg_host = optarg;
+ break;
+
+ case 'q':
+ arg_quiet = true;
+ break;
+
+ case ARG_VERBOSE:
+ arg_verbose = true;
+ break;
+
+ case ARG_EXPECT_REPLY:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --expect-reply= parameter: %s", optarg);
+
+ arg_expect_reply = r;
+ break;
+
+ case ARG_AUTO_START:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --auto-start= parameter: %s", optarg);
+
+ arg_auto_start = r;
+ break;
+
+ case ARG_ALLOW_INTERACTIVE_AUTHORIZATION:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --allow-interactive-authorization= parameter: %s", optarg);
+
+ arg_allow_interactive_authorization = r;
+ break;
+
+ case ARG_TIMEOUT:
+ r = parse_sec(optarg, &arg_timeout);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --timeout= parameter: %s", optarg);
+
+ break;
+
+ case ARG_AUGMENT_CREDS:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --augment-creds= parameter: %s", optarg);
+
+ arg_augment_creds = r;
+ break;
+
+ case ARG_WATCH_BIND:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --watch-bind= parameter: %s", optarg);
+
+ arg_watch_bind = r;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ return 1;
+}
+
+static int busctl_main(int argc, char *argv[]) {
+
+ static const Verb verbs[] = {
+ { "list", VERB_ANY, 1, VERB_DEFAULT, list_bus_names },
+ { "status", VERB_ANY, 2, 0, status },
+ { "monitor", VERB_ANY, VERB_ANY, 0, verb_monitor },
+ { "capture", VERB_ANY, VERB_ANY, 0, verb_capture },
+ { "tree", VERB_ANY, VERB_ANY, 0, tree },
+ { "introspect", 3, 4, 0, introspect },
+ { "call", 5, VERB_ANY, 0, call },
+ { "get-property", 5, VERB_ANY, 0, get_property },
+ { "set-property", 6, VERB_ANY, 0, set_property },
+ { "help", VERB_ANY, VERB_ANY, 0, verb_help },
+ {}
+ };
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+int main(int argc, char *argv[]) {
+ int r;
+
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ r = busctl_main(argc, argv);
+
+finish:
+ /* make sure we terminate the bus connection first, and then close the
+ * pager, see issue #3543 for the details. */
+ pager_close();
+
+ arg_matches = strv_free(arg_matches);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/libelogind/disable-mempool.c b/src/libelogind/disable-mempool.c
new file mode 100644
index 0000000..034bd24
--- /dev/null
+++ b/src/libelogind/disable-mempool.c
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "mempool.h"
+
+const bool mempool_use_allowed = false;
diff --git a/src/libelogind/meson.build b/src/libelogind/meson.build
index dfecea3..8a3f8a7 100644
--- a/src/libelogind/meson.build
+++ b/src/libelogind/meson.build
@@ -80,6 +80,7 @@ sd_login_c = files('sd-login/sd-login.c')
# sd-resolve/sd-resolve.c
# sd-utf8/sd-utf8.c
# '''.split()) + id128_sources + sd_daemon_c + sd_event_c + sd_login_c
+#
#else
libelogind_sources = files('''
sd-bus/bus-common-errors.c
@@ -91,6 +92,8 @@ libelogind_sources = files('''
sd-bus/bus-convenience.c
sd-bus/bus-creds.c
sd-bus/bus-creds.h
+ sd-bus/bus-dump.c
+ sd-bus/bus-dump.h
sd-bus/bus-error.c
sd-bus/bus-error.h
sd-bus/bus-gvariant.c
@@ -119,14 +122,11 @@ libelogind_sources = files('''
sd-bus/bus-type.c
sd-bus/bus-type.h
sd-bus/sd-bus.c
- sd-daemon/sd-daemon.c
- sd-event/sd-event.c
- sd-id128/id128-util.c
- sd-id128/id128-util.h
- sd-id128/sd-id128.c
-'''.split()) + sd_login_c
+'''.split()) + id128_sources + sd_daemon_c + sd_event_c + sd_login_c
#endif // 0
+disable_mempool_c = files('disable-mempool.c')
+
libelogind_c_args = ['-fvisibility=default']
libelogind_static = static_library(
diff --git a/src/libelogind/sd-bus/bus-dump.c b/src/libelogind/sd-bus/bus-dump.c
new file mode 100644
index 0000000..bd018db
--- /dev/null
+++ b/src/libelogind/sd-bus/bus-dump.c
@@ -0,0 +1,595 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/***
+***/
+
+#include "alloc-util.h"
+#include "bus-dump.h"
+#include "bus-internal.h"
+#include "bus-message.h"
+#include "bus-type.h"
+#include "cap-list.h"
+#include "capability-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "locale-util.h"
+#include "macro.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "util.h"
+
+static char *indent(unsigned level, unsigned flags) {
+ char *p;
+ unsigned n, i = 0;
+
+ n = 0;
+
+ if (flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY && level > 0)
+ level -= 1;
+
+ if (flags & BUS_MESSAGE_DUMP_WITH_HEADER)
+ n += 2;
+
+ p = new(char, n + level*8 + 1);
+ if (!p)
+ return NULL;
+
+ if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) {
+ p[i++] = ' ';
+ p[i++] = ' ';
+ }
+
+ memset(p + i, ' ', level*8);
+ p[i + level*8] = 0;
+
+ return p;
+}
+
+int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags) {
+ unsigned level = 1;
+ int r;
+
+ assert(m);
+
+ if (!f)
+ f = stdout;
+
+ if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) {
+ fprintf(f,
+ "%s%s%s Type=%s%s%s Endian=%c Flags=%u Version=%u Priority=%"PRIi64,
+ m->header->type == SD_BUS_MESSAGE_METHOD_ERROR ? ansi_highlight_red() :
+ m->header->type == SD_BUS_MESSAGE_METHOD_RETURN ? ansi_highlight_green() :
+ m->header->type != SD_BUS_MESSAGE_SIGNAL ? ansi_highlight() : "", special_glyph(TRIANGULAR_BULLET), ansi_normal(),
+ ansi_highlight(), bus_message_type_to_string(m->header->type), ansi_normal(),
+ m->header->endian,
+ m->header->flags,
+ m->header->version,
+ m->priority);
+
+ /* Display synthetic message serial number in a more readable
+ * format than (uint32_t) -1 */
+ if (BUS_MESSAGE_COOKIE(m) == 0xFFFFFFFFULL)
+ fprintf(f, " Cookie=-1");
+ else
+ fprintf(f, " Cookie=%" PRIu64, BUS_MESSAGE_COOKIE(m));
+
+ if (m->reply_cookie != 0)
+ fprintf(f, " ReplyCookie=%" PRIu64, m->reply_cookie);
+
+ fputs("\n", f);
+
+ if (m->sender)
+ fprintf(f, " Sender=%s%s%s", ansi_highlight(), m->sender, ansi_normal());
+ if (m->destination)
+ fprintf(f, " Destination=%s%s%s", ansi_highlight(), m->destination, ansi_normal());
+ if (m->path)
+ fprintf(f, " Path=%s%s%s", ansi_highlight(), m->path, ansi_normal());
+ if (m->interface)
+ fprintf(f, " Interface=%s%s%s", ansi_highlight(), m->interface, ansi_normal());
+ if (m->member)
+ fprintf(f, " Member=%s%s%s", ansi_highlight(), m->member, ansi_normal());
+
+ if (m->sender || m->destination || m->path || m->interface || m->member)
+ fputs("\n", f);
+
+ if (sd_bus_error_is_set(&m->error))
+ fprintf(f,
+ " ErrorName=%s%s%s"
+ " ErrorMessage=%s\"%s\"%s\n",
+ ansi_highlight_red(), strna(m->error.name), ansi_normal(),
+ ansi_highlight_red(), strna(m->error.message), ansi_normal());
+
+ if (m->monotonic != 0)
+ fprintf(f, " Monotonic="USEC_FMT, m->monotonic);
+ if (m->realtime != 0)
+ fprintf(f, " Realtime="USEC_FMT, m->realtime);
+ if (m->seqnum != 0)
+ fprintf(f, " SequenceNumber=%"PRIu64, m->seqnum);
+
+ if (m->monotonic != 0 || m->realtime != 0 || m->seqnum != 0)
+ fputs("\n", f);
+
+ bus_creds_dump(&m->creds, f, true);
+ }
+
+ r = sd_bus_message_rewind(m, !(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY));
+ if (r < 0)
+ return log_error_errno(r, "Failed to rewind: %m");
+
+ if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) {
+ _cleanup_free_ char *prefix = NULL;
+
+ prefix = indent(0, flags);
+ if (!prefix)
+ return log_oom();
+
+ fprintf(f, "%sMESSAGE \"%s\" {\n", prefix, strempty(m->root_container.signature));
+ }
+
+ for (;;) {
+ _cleanup_free_ char *prefix = NULL;
+ const char *contents = NULL;
+ char type;
+ union {
+ uint8_t u8;
+ uint16_t u16;
+ int16_t s16;
+ uint32_t u32;
+ int32_t s32;
+ uint64_t u64;
+ int64_t s64;
+ double d64;
+ const char *string;
+ int i;
+ } basic;
+
+ r = sd_bus_message_peek_type(m, &type, &contents);
+ if (r < 0)
+ return log_error_errno(r, "Failed to peek type: %m");
+
+ if (r == 0) {
+ if (level <= 1)
+ break;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to exit container: %m");
+
+ level--;
+
+ prefix = indent(level, flags);
+ if (!prefix)
+ return log_oom();
+
+ fprintf(f, "%s};\n", prefix);
+ continue;
+ }
+
+ prefix = indent(level, flags);
+ if (!prefix)
+ return log_oom();
+
+ if (bus_type_is_container(type) > 0) {
+ r = sd_bus_message_enter_container(m, type, contents);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enter container: %m");
+
+ if (type == SD_BUS_TYPE_ARRAY)
+ fprintf(f, "%sARRAY \"%s\" {\n", prefix, contents);
+ else if (type == SD_BUS_TYPE_VARIANT)
+ fprintf(f, "%sVARIANT \"%s\" {\n", prefix, contents);
+ else if (type == SD_BUS_TYPE_STRUCT)
+ fprintf(f, "%sSTRUCT \"%s\" {\n", prefix, contents);
+ else if (type == SD_BUS_TYPE_DICT_ENTRY)
+ fprintf(f, "%sDICT_ENTRY \"%s\" {\n", prefix, contents);
+
+ level++;
+
+ continue;
+ }
+
+ r = sd_bus_message_read_basic(m, type, &basic);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get basic: %m");
+
+ assert(r > 0);
+
+ switch (type) {
+
+ case SD_BUS_TYPE_BYTE:
+ fprintf(f, "%sBYTE %s%u%s;\n", prefix, ansi_highlight(), basic.u8, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_BOOLEAN:
+ fprintf(f, "%sBOOLEAN %s%s%s;\n", prefix, ansi_highlight(), true_false(basic.i), ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_INT16:
+ fprintf(f, "%sINT16 %s%i%s;\n", prefix, ansi_highlight(), basic.s16, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_UINT16:
+ fprintf(f, "%sUINT16 %s%u%s;\n", prefix, ansi_highlight(), basic.u16, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_INT32:
+ fprintf(f, "%sINT32 %s%i%s;\n", prefix, ansi_highlight(), basic.s32, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_UINT32:
+ fprintf(f, "%sUINT32 %s%u%s;\n", prefix, ansi_highlight(), basic.u32, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_INT64:
+ fprintf(f, "%sINT64 %s%"PRIi64"%s;\n", prefix, ansi_highlight(), basic.s64, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_UINT64:
+ fprintf(f, "%sUINT64 %s%"PRIu64"%s;\n", prefix, ansi_highlight(), basic.u64, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_DOUBLE:
+ fprintf(f, "%sDOUBLE %s%g%s;\n", prefix, ansi_highlight(), basic.d64, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_STRING:
+ fprintf(f, "%sSTRING \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_OBJECT_PATH:
+ fprintf(f, "%sOBJECT_PATH \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_SIGNATURE:
+ fprintf(f, "%sSIGNATURE \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_UNIX_FD:
+ fprintf(f, "%sUNIX_FD %s%i%s;\n", prefix, ansi_highlight(), basic.i, ansi_normal());
+ break;
+
+ default:
+ assert_not_reached("Unknown basic type.");
+ }
+ }
+
+ if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) {
+ _cleanup_free_ char *prefix = NULL;
+
+ prefix = indent(0, flags);
+ if (!prefix)
+ return log_oom();
+
+ fprintf(f, "%s};\n\n", prefix);
+ }
+
+ return 0;
+}
+
+static void dump_capabilities(
+ sd_bus_creds *c,
+ FILE *f,
+ const char *name,
+ bool terse,
+ int (*has)(sd_bus_creds *c, int capability)) {
+
+ unsigned long i, last_cap;
+ unsigned n = 0;
+ int r;
+
+ assert(c);
+ assert(f);
+ assert(name);
+ assert(has);
+
+ i = 0;
+ r = has(c, i);
+ if (r < 0)
+ return;
+
+ fprintf(f, "%s%s=%s", terse ? " " : "", name, terse ? "" : ansi_highlight());
+ last_cap = cap_last_cap();
+
+ for (;;) {
+ if (r > 0) {
+
+ if (n > 0)
+ fputc(' ', f);
+ if (n % 4 == 3)
+ fprintf(f, terse ? "\n " : "\n ");
+
+ fprintf(f, "%s", strna(capability_to_name(i)));
+ n++;
+ }
+
+ i++;
+
+ if (i > last_cap)
+ break;
+
+ r = has(c, i);
+ }
+
+ fputs("\n", f);
+
+ if (!terse)
+ fputs(ansi_normal(), f);
+}
+
+int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse) {
+ uid_t owner, audit_loginuid;
+ uint32_t audit_sessionid;
+ char **cmdline = NULL, **well_known = NULL;
+ const char *prefix, *color, *suffix, *s;
+ int r, q, v, w, z;
+
+ assert(c);
+
+ if (!f)
+ f = stdout;
+
+ if (terse) {
+ prefix = " ";
+ suffix = "";
+ color = "";
+ } else {
+ const char *off;
+
+ prefix = "";
+ color = ansi_highlight();
+
+ off = ansi_normal();
+ suffix = strjoina(off, "\n");
+ }
+
+ if (c->mask & SD_BUS_CREDS_PID)
+ fprintf(f, "%sPID=%s"PID_FMT"%s", prefix, color, c->pid, suffix);
+ if (c->mask & SD_BUS_CREDS_TID)
+ fprintf(f, "%sTID=%s"PID_FMT"%s", prefix, color, c->tid, suffix);
+ if (c->mask & SD_BUS_CREDS_PPID) {
+ if (c->ppid == 0)
+ fprintf(f, "%sPPID=%sn/a%s", prefix, color, suffix);
+ else
+ fprintf(f, "%sPPID=%s"PID_FMT"%s", prefix, color, c->ppid, suffix);
+ }
+ if (c->mask & SD_BUS_CREDS_TTY)
+ fprintf(f, "%sTTY=%s%s%s", prefix, color, strna(c->tty), suffix);
+
+ if (terse && ((c->mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_PPID|SD_BUS_CREDS_TTY))))
+ fputs("\n", f);
+
+ if (c->mask & SD_BUS_CREDS_UID)
+ fprintf(f, "%sUID=%s"UID_FMT"%s", prefix, color, c->uid, suffix);
+ if (c->mask & SD_BUS_CREDS_EUID)
+ fprintf(f, "%sEUID=%s"UID_FMT"%s", prefix, color, c->euid, suffix);
+ if (c->mask & SD_BUS_CREDS_SUID)
+ fprintf(f, "%sSUID=%s"UID_FMT"%s", prefix, color, c->suid, suffix);
+ if (c->mask & SD_BUS_CREDS_FSUID)
+ fprintf(f, "%sFSUID=%s"UID_FMT"%s", prefix, color, c->fsuid, suffix);
+ r = sd_bus_creds_get_owner_uid(c, &owner);
+ if (r >= 0)
+ fprintf(f, "%sOwnerUID=%s"UID_FMT"%s", prefix, color, owner, suffix);
+ if (c->mask & SD_BUS_CREDS_GID)
+ fprintf(f, "%sGID=%s"GID_FMT"%s", prefix, color, c->gid, suffix);
+ if (c->mask & SD_BUS_CREDS_EGID)
+ fprintf(f, "%sEGID=%s"GID_FMT"%s", prefix, color, c->egid, suffix);
+ if (c->mask & SD_BUS_CREDS_SGID)
+ fprintf(f, "%sSGID=%s"GID_FMT"%s", prefix, color, c->sgid, suffix);
+ if (c->mask & SD_BUS_CREDS_FSGID)
+ fprintf(f, "%sFSGID=%s"GID_FMT"%s", prefix, color, c->fsgid, suffix);
+
+ if (c->mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) {
+ unsigned i;
+
+ fprintf(f, "%sSupplementaryGIDs=%s", prefix, color);
+ for (i = 0; i < c->n_supplementary_gids; i++)
+ fprintf(f, "%s" GID_FMT, i > 0 ? " " : "", c->supplementary_gids[i]);
+ fprintf(f, "%s", suffix);
+ }
+
+ if (terse && ((c->mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID|
+ SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID|
+ SD_BUS_CREDS_SUPPLEMENTARY_GIDS)) || r >= 0))
+ fputs("\n", f);
+
+ if (c->mask & SD_BUS_CREDS_COMM)
+ fprintf(f, "%sComm=%s%s%s", prefix, color, c->comm, suffix);
+ if (c->mask & SD_BUS_CREDS_TID_COMM)
+ fprintf(f, "%sTIDComm=%s%s%s", prefix, color, c->tid_comm, suffix);
+ if (c->mask & SD_BUS_CREDS_EXE)
+ fprintf(f, "%sExe=%s%s%s", prefix, color, strna(c->exe), suffix);
+
+ if (terse && (c->mask & (SD_BUS_CREDS_EXE|SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM)))
+ fputs("\n", f);
+
+ r = sd_bus_creds_get_cmdline(c, &cmdline);
+ if (r >= 0) {
+ char **i;
+
+ fprintf(f, "%sCommandLine=%s", prefix, color);
+ STRV_FOREACH(i, cmdline) {
+ if (i != cmdline)
+ fputc(' ', f);
+
+ fputs(*i, f);
+ }
+
+ fprintf(f, "%s", suffix);
+ } else if (r != -ENODATA)
+ fprintf(f, "%sCommandLine=%sn/a%s", prefix, color, suffix);
+
+ if (c->mask & SD_BUS_CREDS_SELINUX_CONTEXT)
+ fprintf(f, "%sLabel=%s%s%s", prefix, color, c->label, suffix);
+ if (c->mask & SD_BUS_CREDS_DESCRIPTION)
+ fprintf(f, "%sDescription=%s%s%s", prefix, color, c->description, suffix);
+
+ if (terse && (c->mask & (SD_BUS_CREDS_SELINUX_CONTEXT|SD_BUS_CREDS_DESCRIPTION)))
+ fputs("\n", f);
+
+ if (c->mask & SD_BUS_CREDS_CGROUP)
+ fprintf(f, "%sCGroup=%s%s%s", prefix, color, c->cgroup, suffix);
+ s = NULL;
+#if 0 /// elogind does not support systemd units
+ r = sd_bus_creds_get_unit(c, &s);
+ if (r != -ENODATA)
+ fprintf(f, "%sUnit=%s%s%s", prefix, color, strna(s), suffix);
+ s = NULL;
+#endif // 0
+ v = sd_bus_creds_get_slice(c, &s);
+ if (v != -ENODATA)
+ fprintf(f, "%sSlice=%s%s%s", prefix, color, strna(s), suffix);
+ s = NULL;
+#if 0 /// elogind does not support systemd units
+ q = sd_bus_creds_get_user_unit(c, &s);
+ if (q != -ENODATA)
+ fprintf(f, "%sUserUnit=%s%s%s", prefix, color, strna(s), suffix);
+ s = NULL;
+#endif // 0
+ w = sd_bus_creds_get_user_slice(c, &s);
+ if (w != -ENODATA)
+ fprintf(f, "%sUserSlice=%s%s%s", prefix, color, strna(s), suffix);
+ s = NULL;
+ z = sd_bus_creds_get_session(c, &s);
+ if (z != -ENODATA)
+ fprintf(f, "%sSession=%s%s%s", prefix, color, strna(s), suffix);
+
+#if 0 /// elogind does not support systemd units, and q is only used with them until now
+ if (terse && ((c->mask & SD_BUS_CREDS_CGROUP) || r != -ENODATA || q != -ENODATA || v != -ENODATA || w != -ENODATA || z != -ENODATA))
+#else
+ if (terse && ((c->mask & SD_BUS_CREDS_CGROUP) || r != -ENODATA || v != -ENODATA || w != -ENODATA || z != -ENODATA))
+#endif // 0
+ fputs("\n", f);
+
+ r = sd_bus_creds_get_audit_login_uid(c, &audit_loginuid);
+ if (r >= 0)
+ fprintf(f, "%sAuditLoginUID=%s"UID_FMT"%s", prefix, color, audit_loginuid, suffix);
+ else if (r != -ENODATA)
+ fprintf(f, "%sAuditLoginUID=%sn/a%s", prefix, color, suffix);
+ q = sd_bus_creds_get_audit_session_id(c, &audit_sessionid);
+ if (q >= 0)
+ fprintf(f, "%sAuditSessionID=%s%"PRIu32"%s", prefix, color, audit_sessionid, suffix);
+ else if (q != -ENODATA)
+ fprintf(f, "%sAuditSessionID=%sn/a%s", prefix, color, suffix);
+
+ if (terse && (r != -ENODATA || q != -ENODATA))
+ fputs("\n", f);
+
+ if (c->mask & SD_BUS_CREDS_UNIQUE_NAME)
+ fprintf(f, "%sUniqueName=%s%s%s", prefix, color, c->unique_name, suffix);
+
+ if (sd_bus_creds_get_well_known_names(c, &well_known) >= 0) {
+ char **i;
+
+ fprintf(f, "%sWellKnownNames=%s", prefix, color);
+ STRV_FOREACH(i, well_known) {
+ if (i != well_known)
+ fputc(' ', f);
+
+ fputs(*i, f);
+ }
+
+ fprintf(f, "%s", suffix);
+ }
+
+ if (terse && (c->mask & SD_BUS_CREDS_UNIQUE_NAME || well_known))
+ fputc('\n', f);
+
+ dump_capabilities(c, f, "EffectiveCapabilities", terse, sd_bus_creds_has_effective_cap);
+ dump_capabilities(c, f, "PermittedCapabilities", terse, sd_bus_creds_has_permitted_cap);
+ dump_capabilities(c, f, "InheritableCapabilities", terse, sd_bus_creds_has_inheritable_cap);
+ dump_capabilities(c, f, "BoundingCapabilities", terse, sd_bus_creds_has_bounding_cap);
+
+ return 0;
+}
+
+/*
+ * For details about the file format, see:
+ *
+ * http://wiki.wireshark.org/Development/LibpcapFileFormat
+ */
+
+typedef struct _packed_ pcap_hdr_s {
+ uint32_t magic_number; /* magic number */
+ uint16_t version_major; /* major version number */
+ uint16_t version_minor; /* minor version number */
+ int32_t thiszone; /* GMT to local correction */
+ uint32_t sigfigs; /* accuracy of timestamps */
+ uint32_t snaplen; /* max length of captured packets, in octets */
+ uint32_t network; /* data link type */
+} pcap_hdr_t ;
+
+typedef struct _packed_ pcaprec_hdr_s {
+ uint32_t ts_sec; /* timestamp seconds */
+ uint32_t ts_usec; /* timestamp microseconds */
+ uint32_t incl_len; /* number of octets of packet saved in file */
+ uint32_t orig_len; /* actual length of packet */
+} pcaprec_hdr_t;
+
+int bus_pcap_header(size_t snaplen, FILE *f) {
+
+ pcap_hdr_t hdr = {
+ .magic_number = 0xa1b2c3d4U,
+ .version_major = 2,
+ .version_minor = 4,
+ .thiszone = 0, /* UTC */
+ .sigfigs = 0,
+ .network = 231, /* D-Bus */
+ };
+
+ if (!f)
+ f = stdout;
+
+ assert(snaplen > 0);
+ assert((size_t) (uint32_t) snaplen == snaplen);
+
+ hdr.snaplen = (uint32_t) snaplen;
+
+ fwrite(&hdr, 1, sizeof(hdr), f);
+
+ return fflush_and_check(f);
+}
+
+int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f) {
+ struct bus_body_part *part;
+ pcaprec_hdr_t hdr = {};
+ struct timeval tv;
+ unsigned i;
+ size_t w;
+
+ if (!f)
+ f = stdout;
+
+ assert(m);
+ assert(snaplen > 0);
+ assert((size_t) (uint32_t) snaplen == snaplen);
+
+ if (m->realtime != 0)
+ timeval_store(&tv, m->realtime);
+ else
+ assert_se(gettimeofday(&tv, NULL) >= 0);
+
+ hdr.ts_sec = tv.tv_sec;
+ hdr.ts_usec = tv.tv_usec;
+ hdr.orig_len = BUS_MESSAGE_SIZE(m);
+ hdr.incl_len = MIN(hdr.orig_len, snaplen);
+
+ /* write the pcap header */
+ fwrite(&hdr, 1, sizeof(hdr), f);
+
+ /* write the dbus header */
+ w = MIN(BUS_MESSAGE_BODY_BEGIN(m), snaplen);
+ fwrite(m->header, 1, w, f);
+ snaplen -= w;
+
+ /* write the dbus body */
+ MESSAGE_FOREACH_PART(part, i, m) {
+ if (snaplen <= 0)
+ break;
+
+ w = MIN(part->size, snaplen);
+ fwrite(part->data, 1, w, f);
+ snaplen -= w;
+ }
+
+ return fflush_and_check(f);
+}
diff --git a/src/libelogind/sd-bus/bus-dump.h b/src/libelogind/sd-bus/bus-dump.h
new file mode 100644
index 0000000..8e47411
--- /dev/null
+++ b/src/libelogind/sd-bus/bus-dump.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+/***
+***/
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "sd-bus.h"
+
+enum {
+ BUS_MESSAGE_DUMP_WITH_HEADER = 1,
+ BUS_MESSAGE_DUMP_SUBTREE_ONLY = 2,
+};
+
+int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags);
+
+int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse);
+
+int bus_pcap_header(size_t snaplen, FILE *f);
+int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f);
diff --git a/src/libelogind/sd-bus/bus-error.c b/src/libelogind/sd-bus/bus-error.c
index ec359ac..c974531 100644
--- a/src/libelogind/sd-bus/bus-error.c
+++ b/src/libelogind/sd-bus/bus-error.c
@@ -310,6 +310,30 @@ finish:
return -bus_error_name_to_errno(e->name);
}
+#if 0 /// UNNEEDED by elogind
+int sd_bus_error_move(sd_bus_error *dest, sd_bus_error *e) {
+ int r;
+
+ if (!sd_bus_error_is_set(e)) {
+
+ if (dest)
+ *dest = SD_BUS_ERROR_NULL;
+
+ return 0;
+ }
+
+ r = -bus_error_name_to_errno(e->name);
+
+ if (dest) {
+ *dest = *e;
+ *e = SD_BUS_ERROR_NULL;
+ } else
+ sd_bus_error_free(e);
+
+ return r;
+}
+#endif // 0
+
_public_ int sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message) {
if (!name)
return 0;
diff --git a/src/libelogind/sd-bus/bus-error.h b/src/libelogind/sd-bus/bus-error.h
index 93cb9ac..9c6f653 100644
--- a/src/libelogind/sd-bus/bus-error.h
+++ b/src/libelogind/sd-bus/bus-error.h
@@ -47,3 +47,7 @@ int bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_lis
#define BUS_ERROR_MAP_END_MARKER -'x'
BUS_ERROR_MAP_ELF_USE(bus_standard_errors);
+
+#if 0 /// UNNEEDED by elogind
+int sd_bus_error_move(sd_bus_error *dest, sd_bus_error *e);
+#endif // 0
diff --git a/src/libelogind/sd-bus/bus-internal.h b/src/libelogind/sd-bus/bus-internal.h
index 904d610..df33ca2 100644
--- a/src/libelogind/sd-bus/bus-internal.h
+++ b/src/libelogind/sd-bus/bus-internal.h
@@ -396,14 +396,10 @@ void bus_close_io_fds(sd_bus *b);
_cleanup_(sd_bus_unrefp) _unused_ sd_bus *_dont_destroy_##bus = sd_bus_ref(bus)
int bus_set_address_system(sd_bus *bus);
-#if 0 /// UNNEEDED by elogind
int bus_set_address_user(sd_bus *bus);
-#endif // 0
int bus_set_address_system_remote(sd_bus *b, const char *host);
int bus_set_address_system_machine(sd_bus *b, const char *machine);
-#if 0 /// UNNEEDED by elogind
-#endif // 0
int bus_get_root_path(sd_bus *bus);
int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error);
diff --git a/src/libelogind/sd-bus/bus-introspect.c b/src/libelogind/sd-bus/bus-introspect.c
index 483c299..2890f41 100644
--- a/src/libelogind/sd-bus/bus-introspect.c
+++ b/src/libelogind/sd-bus/bus-introspect.c
@@ -63,7 +63,7 @@ int introspect_write_child_nodes(struct introspect *i, Set *s, const char *prefi
return 0;
}
-static void introspect_write_flags(struct introspect *i, int type, int flags) {
+static void introspect_write_flags(struct introspect *i, int type, uint64_t flags) {
if (flags & SD_BUS_VTABLE_DEPRECATED)
fputs(" <annotation name=\"org.freedesktop.DBus.Deprecated\" value=\"true\"/>\n", i->f);
diff --git a/src/libelogind/sd-bus/bus-kernel.c b/src/libelogind/sd-bus/bus-kernel.c
index e0f521b..1f61bd3 100644
--- a/src/libelogind/sd-bus/bus-kernel.c
+++ b/src/libelogind/sd-bus/bus-kernel.c
@@ -50,5 +50,3 @@ void bus_flush_memfd(sd_bus *b) {
for (i = 0; i < b->n_memfd_cache; i++)
close_and_munmap(b->memfd_cache[i].fd, b->memfd_cache[i].address, b->memfd_cache[i].mapped);
}
-#if 0 /// UNNEEDED by elogind
-#endif // 0
diff --git a/src/libelogind/sd-bus/bus-kernel.h b/src/libelogind/sd-bus/bus-kernel.h
index 9fcbeb0..fbbc43f 100644
--- a/src/libelogind/sd-bus/bus-kernel.h
+++ b/src/libelogind/sd-bus/bus-kernel.h
@@ -1,9 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
-/***
-***/
-
#include "sd-bus.h"
#define MEMFD_CACHE_MAX 32
@@ -23,9 +20,5 @@ struct memfd_cache {
size_t allocated;
};
-#if 0 /// UNNEEDED by elogind
-#endif // 0
void close_and_munmap(int fd, void *address, size_t size);
void bus_flush_memfd(sd_bus *bus);
-#if 0 /// UNNEEDED by elogind
-#endif // 0
diff --git a/src/libelogind/sd-bus/bus-message.c b/src/libelogind/sd-bus/bus-message.c
index 4fb06c2..ca4186e 100644
--- a/src/libelogind/sd-bus/bus-message.c
+++ b/src/libelogind/sd-bus/bus-message.c
@@ -77,19 +77,38 @@ static void message_reset_parts(sd_bus_message *m) {
m->cached_rindex_part_begin = 0;
}
-static void message_reset_containers(sd_bus_message *m) {
- unsigned i;
+static struct bus_container *message_get_container(sd_bus_message *m) {
+ assert(m);
+
+ if (m->n_containers == 0)
+ return &m->root_container;
+
+ assert(m->containers);
+ return m->containers + m->n_containers - 1;
+}
+static void message_free_last_container(sd_bus_message *m) {
+ struct bus_container *c;
+
+ c = message_get_container(m);
+
+ free(c->signature);
+ free(c->peeked_signature);
+ free(c->offsets);
+
+ /* Move to previous container, but not if we are on root container */
+ if (m->n_containers > 0)
+ m->n_containers--;
+}
+
+static void message_reset_containers(sd_bus_message *m) {
assert(m);
- for (i = 0; i < m->n_containers; i++) {
- free(m->containers[i].signature);
- free(m->containers[i].offsets);
- }
+ while (m->n_containers > 0)
+ message_free_last_container(m);
m->containers = mfree(m->containers);
-
- m->n_containers = m->containers_allocated = 0;
+ m->containers_allocated = 0;
m->root_container.index = 0;
}
@@ -112,10 +131,8 @@ static sd_bus_message* message_free(sd_bus_message *m) {
free(m->iovec);
message_reset_containers(m);
- free(m->root_container.signature);
- free(m->root_container.offsets);
-
- free(m->root_container.peeked_signature);
+ assert(m->n_containers == 0);
+ message_free_last_container(m);
bus_creds_done(&m->creds);
return mfree(m);
@@ -1113,16 +1130,6 @@ _public_ int sd_bus_message_set_allow_interactive_authorization(sd_bus_message *
return 0;
}
-static struct bus_container *message_get_container(sd_bus_message *m) {
- assert(m);
-
- if (m->n_containers == 0)
- return &m->root_container;
-
- assert(m->containers);
- return m->containers + m->n_containers - 1;
-}
-
struct bus_body_part *message_append_part(sd_bus_message *m) {
struct bus_body_part *part;
@@ -1949,7 +1956,7 @@ _public_ int sd_bus_message_open_container(
char type,
const char *contents) {
- struct bus_container *c, *w;
+ struct bus_container *c;
uint32_t *array_size = NULL;
_cleanup_free_ char *signature = NULL;
size_t before, begin = 0;
@@ -1994,16 +2001,14 @@ _public_ int sd_bus_message_open_container(
return r;
/* OK, let's fill it in */
- w = m->containers + m->n_containers++;
- w->enclosing = type;
- w->signature = TAKE_PTR(signature);
- w->index = 0;
- w->array_size = array_size;
- w->before = before;
- w->begin = begin;
- w->n_offsets = w->offsets_allocated = 0;
- w->offsets = NULL;
- w->need_offsets = need_offsets;
+ m->containers[m->n_containers++] = (struct bus_container) {
+ .enclosing = type,
+ .signature = TAKE_PTR(signature),
+ .array_size = array_size,
+ .before = before,
+ .begin = begin,
+ .need_offsets = need_offsets,
+ };
return 0;
}
@@ -3135,6 +3140,7 @@ static int container_next_item(sd_bus_message *m, struct bus_container *c, size_
assert(alignment > 0);
*rindex = ALIGN_TO(c->offsets[c->offset_index], alignment);
+ assert(c->offsets[c->offset_index+1] >= *rindex);
c->item_size = c->offsets[c->offset_index+1] - *rindex;
} else {
@@ -3174,6 +3180,7 @@ static int container_next_item(sd_bus_message *m, struct bus_container *c, size_
assert(alignment > 0);
*rindex = ALIGN_TO(c->offsets[c->offset_index], alignment);
+ assert(c->offsets[c->offset_index+1] >= *rindex);
c->item_size = c->offsets[c->offset_index+1] - *rindex;
c->offset_index++;
@@ -3312,6 +3319,12 @@ _public_ int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p) {
if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) {
bool ok;
+ /* D-Bus spec: The marshalling formats for the string-like types all end
+ * with a single zero (NUL) byte, but that byte is not considered to be part
+ * of the text. */
+ if (c->item_size == 0)
+ return -EBADMSG;
+
r = message_peek_body(m, &rindex, 1, c->item_size, &q);
if (r < 0)
return r;
@@ -3406,6 +3419,10 @@ _public_ int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p) {
return r;
l = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q);
+ if (l == UINT32_MAX)
+ /* avoid overflow right below */
+ return -EBADMSG;
+
r = message_peek_body(m, &rindex, 1, l+1, &q);
if (r < 0)
return r;
@@ -3428,6 +3445,10 @@ _public_ int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p) {
return r;
l = *(uint8_t*) q;
+ if (l == UINT8_MAX)
+ /* avoid overflow right below */
+ return -EBADMSG;
+
r = message_peek_body(m, &rindex, 1, l+1, &q);
if (r < 0)
return r;
@@ -3519,7 +3540,7 @@ static int bus_message_enter_array(
size_t rindex;
void *q;
- int r, alignment;
+ int r;
assert(m);
assert(c);
@@ -3545,6 +3566,7 @@ static int bus_message_enter_array(
if (!BUS_MESSAGE_IS_GVARIANT(m)) {
/* dbus1 */
+ int alignment;
r = message_peek_body(m, &rindex, 4, 4, &q);
if (r < 0)
@@ -3578,7 +3600,8 @@ static int bus_message_enter_array(
*n_offsets = 0;
} else {
- size_t where, p = 0, framing, sz;
+ size_t where, previous = 0, framing, sz;
+ int alignment;
unsigned i;
/* gvariant: variable length array */
@@ -3606,17 +3629,22 @@ static int bus_message_enter_array(
if (!*offsets)
return -ENOMEM;
+ alignment = bus_gvariant_get_alignment(c->signature);
+ assert(alignment > 0);
+
for (i = 0; i < *n_offsets; i++) {
- size_t x;
+ size_t x, start;
+
+ start = ALIGN_TO(previous, alignment);
x = bus_gvariant_read_word_le((uint8_t*) q + i * sz, sz);
if (x > c->item_size - sz)
return -EBADMSG;
- if (x < p)
+ if (x < start)
return -EBADMSG;
(*offsets)[i] = rindex + x;
- p = x;
+ previous = x;
}
*item_size = (*offsets)[0] - rindex;
@@ -3686,6 +3714,10 @@ static int bus_message_enter_variant(
return r;
l = *(uint8_t*) q;
+ if (l == UINT8_MAX)
+ /* avoid overflow right below */
+ return -EBADMSG;
+
r = message_peek_body(m, &rindex, 1, l+1, &q);
if (r < 0)
return r;
@@ -3714,7 +3746,7 @@ static int build_struct_offsets(
size_t *n_offsets) {
unsigned n_variable = 0, n_total = 0, v;
- size_t previous = 0, where;
+ size_t previous, where;
const char *p;
size_t sz;
void *q;
@@ -3793,6 +3825,7 @@ static int build_struct_offsets(
/* Second, loop again and build an offset table */
p = signature;
+ previous = m->rindex;
while (*p != 0) {
size_t n, offset;
int k;
@@ -3806,37 +3839,34 @@ static int build_struct_offsets(
memcpy(t, p, n);
t[n] = 0;
+ size_t align = bus_gvariant_get_alignment(t);
+ assert(align > 0);
+
+ /* The possible start of this member after including alignment */
+ size_t start = ALIGN_TO(previous, align);
+
k = bus_gvariant_get_size(t);
if (k < 0) {
size_t x;
- /* variable size */
+ /* Variable size */
if (v > 0) {
v--;
x = bus_gvariant_read_word_le((uint8_t*) q + v*sz, sz);
if (x >= size)
return -EBADMSG;
- if (m->rindex + x < previous)
- return -EBADMSG;
} else
- /* The last item's end
- * is determined from
- * the start of the
- * offset array */
+ /* The last item's end is determined
+ * from the start of the offset array */
x = size - (n_variable * sz);
offset = m->rindex + x;
-
- } else {
- size_t align;
-
- /* fixed size */
- align = bus_gvariant_get_alignment(t);
- assert(align > 0);
-
- offset = (*n_offsets == 0 ? m->rindex : ALIGN_TO((*offsets)[*n_offsets-1], align)) + k;
- }
+ if (offset < start)
+ return -EBADMSG;
+ } else
+ /* Fixed size */
+ offset = start + k;
}
previous = (*offsets)[(*n_offsets)++] = offset;
@@ -3966,10 +3996,10 @@ static int bus_message_enter_dict_entry(
_public_ int sd_bus_message_enter_container(sd_bus_message *m,
char type,
const char *contents) {
- struct bus_container *c, *w;
+ struct bus_container *c;
uint32_t *array_size = NULL;
_cleanup_free_ char *signature = NULL;
- size_t before;
+ size_t before, end;
_cleanup_free_ size_t *offsets = NULL;
size_t n_offsets = 0, item_size = 0;
int r;
@@ -4048,28 +4078,26 @@ _public_ int sd_bus_message_enter_container(sd_bus_message *m,
return r;
/* OK, let's fill it in */
- w = m->containers + m->n_containers++;
- w->enclosing = type;
- w->signature = TAKE_PTR(signature);
- w->peeked_signature = NULL;
- w->index = 0;
-
- w->before = before;
- w->begin = m->rindex;
-
- /* Unary type has fixed size of 1, but virtual size of 0 */
if (BUS_MESSAGE_IS_GVARIANT(m) &&
type == SD_BUS_TYPE_STRUCT &&
isempty(signature))
- w->end = m->rindex + 0;
+ end = m->rindex + 0;
else
- w->end = m->rindex + c->item_size;
-
- w->array_size = array_size;
- w->item_size = item_size;
- w->offsets = TAKE_PTR(offsets);
- w->n_offsets = n_offsets;
- w->offset_index = 0;
+ end = m->rindex + c->item_size;
+
+ m->containers[m->n_containers++] = (struct bus_container) {
+ .enclosing = type,
+ .signature = TAKE_PTR(signature),
+
+ .before = before,
+ .begin = m->rindex,
+ /* Unary type has fixed size of 1, but virtual size of 0 */
+ .end = end,
+ .array_size = array_size,
+ .item_size = item_size,
+ .offsets = TAKE_PTR(offsets),
+ .n_offsets = n_offsets,
+ };
return 1;
}
@@ -4102,13 +4130,9 @@ _public_ int sd_bus_message_exit_container(sd_bus_message *m) {
return -EBUSY;
}
- free(c->signature);
- free(c->peeked_signature);
- free(c->offsets);
- m->n_containers--;
+ message_free_last_container(m);
c = message_get_container(m);
-
saved = c->index;
c->index = c->saved_index;
r = container_next_item(m, c, &m->rindex);
@@ -4126,16 +4150,13 @@ static void message_quit_container(sd_bus_message *m) {
assert(m->sealed);
assert(m->n_containers > 0);
- c = message_get_container(m);
-
/* Undo seeks */
+ c = message_get_container(m);
assert(m->rindex >= c->before);
m->rindex = c->before;
/* Free container */
- free(c->signature);
- free(c->offsets);
- m->n_containers--;
+ message_free_last_container(m);
/* Correct index of new top-level container */
c = message_get_container(m);
@@ -4169,20 +4190,20 @@ _public_ int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char
if (contents) {
size_t l;
- char *sig;
r = signature_element_length(c->signature+c->index+1, &l);
if (r < 0)
return r;
- assert(l >= 1);
+ /* signature_element_length does verification internally */
- sig = strndup(c->signature + c->index + 1, l);
- if (!sig)
+ /* The array element must not be empty */
+ assert(l >= 1);
+ if (free_and_strndup(&c->peeked_signature,
+ c->signature + c->index + 1, l) < 0)
return -ENOMEM;
- free(c->peeked_signature);
- *contents = c->peeked_signature = sig;
+ *contents = c->peeked_signature;
}
if (type)
@@ -4195,19 +4216,17 @@ _public_ int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char
if (contents) {
size_t l;
- char *sig;
r = signature_element_length(c->signature+c->index, &l);
if (r < 0)
return r;
- assert(l >= 2);
- sig = strndup(c->signature + c->index + 1, l - 2);
- if (!sig)
+ assert(l >= 3);
+ if (free_and_strndup(&c->peeked_signature,
+ c->signature + c->index + 1, l - 2) < 0)
return -ENOMEM;
- free(c->peeked_signature);
- *contents = c->peeked_signature = sig;
+ *contents = c->peeked_signature;
}
if (type)
@@ -4247,9 +4266,8 @@ _public_ int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char
if (k > c->item_size)
return -EBADMSG;
- free(c->peeked_signature);
- c->peeked_signature = strndup((char*) q + 1, k - 1);
- if (!c->peeked_signature)
+ if (free_and_strndup(&c->peeked_signature,
+ (char*) q + 1, k - 1) < 0)
return -ENOMEM;
if (!signature_is_valid(c->peeked_signature, true))
@@ -4265,6 +4283,10 @@ _public_ int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char
return r;
l = *(uint8_t*) q;
+ if (l == UINT8_MAX)
+ /* avoid overflow right below */
+ return -EBADMSG;
+
r = message_peek_body(m, &rindex, 1, l+1, &q);
if (r < 0)
return r;
@@ -4850,6 +4872,10 @@ static int message_peek_field_string(
if (r < 0)
return r;
+ if (l == UINT32_MAX)
+ /* avoid overflow right below */
+ return -EBADMSG;
+
r = message_peek_fields(m, ri, 1, l+1, &q);
if (r < 0)
return r;
@@ -4901,6 +4927,10 @@ static int message_peek_field_signature(
return r;
l = *(uint8_t*) q;
+ if (l == UINT8_MAX)
+ /* avoid overflow right below */
+ return -EBADMSG;
+
r = message_peek_fields(m, ri, 1, l+1, &q);
if (r < 0)
return r;
@@ -4982,18 +5012,18 @@ static int message_skip_fields(
} else if (t == SD_BUS_TYPE_ARRAY) {
- r = signature_element_length(*signature+1, &l);
+ r = signature_element_length(*signature + 1, &l);
if (r < 0)
return r;
assert(l >= 1);
{
- char sig[l-1], *s;
+ char sig[l + 1], *s = sig;
uint32_t nas;
int alignment;
- strncpy(sig, *signature + 1, l-1);
- s = sig;
+ strncpy(sig, *signature + 1, l);
+ sig[l] = '\0';
alignment = bus_type_get_alignment(sig[0]);
if (alignment < 0)
@@ -5037,9 +5067,9 @@ static int message_skip_fields(
assert(l >= 2);
{
- char sig[l-1], *s;
- strncpy(sig, *signature + 1, l-1);
- s = sig;
+ char sig[l + 1], *s = sig;
+ strncpy(sig, *signature + 1, l);
+ sig[l] = '\0';
r = message_skip_fields(m, ri, (uint32_t) -1, (const char**) &s);
if (r < 0)
@@ -5048,7 +5078,7 @@ static int message_skip_fields(
*signature += l;
} else
- return -EINVAL;
+ return -EBADMSG;
}
}
@@ -5079,25 +5109,21 @@ int bus_message_parse_fields(sd_bus_message *m) {
if (*p == 0) {
size_t l;
- char *c;
/* We found the beginning of the signature
* string, yay! We require the body to be a
* structure, so verify it and then strip the
* opening/closing brackets. */
- l = ((char*) m->footer + m->footer_accessible) - p - (1 + sz);
+ l = (char*) m->footer + m->footer_accessible - p - (1 + sz);
if (l < 2 ||
p[1] != SD_BUS_TYPE_STRUCT_BEGIN ||
p[1 + l - 1] != SD_BUS_TYPE_STRUCT_END)
return -EBADMSG;
- c = strndup(p + 1 + 1, l - 2);
- if (!c)
+ if (free_and_strndup(&m->root_container.signature,
+ p + 1 + 1, l - 2) < 0)
return -ENOMEM;
-
- free(m->root_container.signature);
- m->root_container.signature = c;
break;
}
@@ -5419,6 +5445,8 @@ int bus_message_parse_fields(sd_bus_message *m) {
&m->root_container.item_size,
&m->root_container.offsets,
&m->root_container.n_offsets);
+ if (r == -EINVAL)
+ return -EBADMSG;
if (r < 0)
return r;
}
@@ -5433,6 +5461,7 @@ int bus_message_parse_fields(sd_bus_message *m) {
_public_ int sd_bus_message_set_destination(sd_bus_message *m, const char *destination) {
assert_return(m, -EINVAL);
assert_return(destination, -EINVAL);
+ assert_return(service_name_is_valid(destination), -EINVAL);
assert_return(!m->sealed, -EPERM);
assert_return(!m->destination, -EEXIST);
@@ -5442,6 +5471,7 @@ _public_ int sd_bus_message_set_destination(sd_bus_message *m, const char *desti
_public_ int sd_bus_message_set_sender(sd_bus_message *m, const char *sender) {
assert_return(m, -EINVAL);
assert_return(sender, -EINVAL);
+ assert_return(service_name_is_valid(sender), -EINVAL);
assert_return(!m->sealed, -EPERM);
assert_return(!m->sender, -EEXIST);
diff --git a/src/libelogind/sd-bus/bus-signature.c b/src/libelogind/sd-bus/bus-signature.c
index 18c91e8..1ca37cb 100644
--- a/src/libelogind/sd-bus/bus-signature.c
+++ b/src/libelogind/sd-bus/bus-signature.c
@@ -58,6 +58,12 @@ static int signature_element_length_internal(
p += t;
}
+ if (p - s < 2)
+ /* D-Bus spec: Empty structures are not allowed; there
+ * must be at least one type code between the parentheses.
+ */
+ return -EINVAL;
+
*l = p - s + 1;
return 0;
}
diff --git a/src/libelogind/sd-bus/bus-slot.c b/src/libelogind/sd-bus/bus-slot.c
index fbf3732..6790061 100644
--- a/src/libelogind/sd-bus/bus-slot.c
+++ b/src/libelogind/sd-bus/bus-slot.c
@@ -81,7 +81,10 @@ void bus_slot_disconnect(sd_bus_slot *slot) {
(void) bus_remove_match_internal(slot->bus, slot->match_callback.match_string);
if (slot->match_callback.install_slot) {
- bus_slot_disconnect(slot->match_callback.install_slot);
+ if (slot->match_callback.install_slot->bus) {
+ bus_slot_disconnect(slot->match_callback.install_slot);
+ sd_bus_slot_unref(slot->match_callback.install_slot);
+ }
slot->match_callback.install_slot = sd_bus_slot_unref(slot->match_callback.install_slot);
}
diff --git a/src/libelogind/sd-bus/bus-socket.c b/src/libelogind/sd-bus/bus-socket.c
index b147a38..a5513d1 100644
--- a/src/libelogind/sd-bus/bus-socket.c
+++ b/src/libelogind/sd-bus/bus-socket.c
@@ -248,10 +248,7 @@ static bool line_begins(const char *s, size_t m, const char *word) {
const char *p;
p = memory_startswith(s, m, word);
- if (!p)
- return false;
-
- return IN_SET(*p, 0, ' ');
+ return p && (p == (s + m) || *p == ' ');
}
static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) {
diff --git a/src/libelogind/sd-bus/sd-bus.c b/src/libelogind/sd-bus/sd-bus.c
index 2a49040..0b325e4 100644
--- a/src/libelogind/sd-bus/sd-bus.c
+++ b/src/libelogind/sd-bus/sd-bus.c
@@ -1203,19 +1203,15 @@ _public_ int sd_bus_open_with_description(sd_bus **ret, const char *description)
if (e) {
if (streq(e, "system"))
return sd_bus_open_system_with_description(ret, description);
-#if 0 /// elogind does not support systemd user instances
else if (STR_IN_SET(e, "session", "user"))
-#endif // 0
return sd_bus_open_user_with_description(ret, description);
}
e = secure_getenv("DBUS_STARTER_ADDRESS");
if (!e) {
-#if 0 /// elogind does not support systemd user instances
if (cg_pid_get_owner_uid(0, NULL) >= 0)
return sd_bus_open_user_with_description(ret, description);
else
-#endif // 0
return sd_bus_open_system_with_description(ret, description);
}
@@ -1299,7 +1295,6 @@ _public_ int sd_bus_open_system(sd_bus **ret) {
return sd_bus_open_system_with_description(ret, NULL);
}
-#if 0 /// elogind can not open/use a user bus
int bus_set_address_user(sd_bus *b) {
const char *e;
_cleanup_free_ char *ee = NULL, *s = NULL;
@@ -1325,10 +1320,8 @@ int bus_set_address_user(sd_bus *b) {
return 0;
}
-#endif // 0
_public_ int sd_bus_open_user_with_description(sd_bus **ret, const char *description) {
-#if 0 /// elogind does not support user buses
_cleanup_(bus_freep) sd_bus *b = NULL;
int r;
@@ -1362,9 +1355,6 @@ _public_ int sd_bus_open_user_with_description(sd_bus **ret, const char *descrip
*ret = TAKE_PTR(b);
return 0;
-#else
- return sd_bus_open_system_with_description(ret, description);
-#endif // 0
}
_public_ int sd_bus_open_user(sd_bus **ret) {
@@ -3276,8 +3266,6 @@ finish:
return r;
}
-#if 0 /// UNNEEDED by elogind
-#endif // 0
_public_ int sd_bus_add_match(
sd_bus *bus,
sd_bus_slot **slot,
@@ -3651,11 +3639,7 @@ _public_ int sd_bus_default_system(sd_bus **ret) {
}
_public_ int sd_bus_default_user(sd_bus **ret) {
-#if 0 /// elogind does not support user buses
return bus_default(sd_bus_open_user, &default_user_bus, ret);
-#else
- return sd_bus_default_system(ret);
-#endif // 0
}
_public_ int sd_bus_default(sd_bus **ret) {
@@ -4030,9 +4014,7 @@ static void flush_close(sd_bus *bus) {
_public_ void sd_bus_default_flush_close(void) {
flush_close(default_starter_bus);
-#if 0 /// elogind does not support user buses
flush_close(default_user_bus);
-#endif // 0
flush_close(default_system_bus);
}
diff --git a/src/libelogind/sd-bus/test-bus-signature.c b/src/libelogind/sd-bus/test-bus-signature.c
index 1ba1909..a716cd1 100644
--- a/src/libelogind/sd-bus/test-bus-signature.c
+++ b/src/libelogind/sd-bus/test-bus-signature.c
@@ -16,9 +16,9 @@ int main(int argc, char *argv[]) {
assert_se(signature_is_single("v", false));
assert_se(signature_is_single("as", false));
assert_se(signature_is_single("(ss)", false));
- assert_se(signature_is_single("()", false));
- assert_se(signature_is_single("(()()()()())", false));
- assert_se(signature_is_single("(((())))", false));
+ assert_se(!signature_is_single("()", false));
+ assert_se(!signature_is_single("(()()()()())", false));
+ assert_se(!signature_is_single("(((())))", false));
assert_se(signature_is_single("((((s))))", false));
assert_se(signature_is_single("{ss}", true));
assert_se(signature_is_single("a{ss}", false));
@@ -63,7 +63,7 @@ int main(int argc, char *argv[]) {
assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas", false));
assert_se(!signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau", false));
- assert_se(signature_is_valid("(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))", false));
+ assert_se(signature_is_valid("((((((((((((((((((((((((((((((((s))))))))))))))))))))))))))))))))", false));
assert_se(!signature_is_valid("((((((((((((((((((((((((((((((((()))))))))))))))))))))))))))))))))", false));
assert_se(namespace_complex_pattern("", ""));
diff --git a/src/login/70-uaccess.rules b/src/login/70-uaccess.rules.m4
index 3515d29..d55e5bf 100644
--- a/src/login/70-uaccess.rules
+++ b/src/login/70-uaccess.rules.m4
@@ -46,6 +46,10 @@ SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x014001*", TAG+="uaccess"
# DRI video devices
SUBSYSTEM=="drm", KERNEL=="card*", TAG+="uaccess"
+m4_ifdef(`DEV_KVM_UACCESS',``
+# KVM
+SUBSYSTEM=="misc", KERNEL=="kvm", TAG+="uaccess"''
+)m4_dnl
# smart-card readers
ENV{ID_SMARTCARD_READER}=="?*", TAG+="uaccess"
diff --git a/src/login/elogind-dbus.c b/src/login/elogind-dbus.c
index f359e18..03ec050 100644
--- a/src/login/elogind-dbus.c
+++ b/src/login/elogind-dbus.c
@@ -83,7 +83,7 @@ static int run_helper(const char *helper, const char *arg_verb) {
arguments[0] = NULL;
arguments[1] = (char*)arg_verb;
arguments[2] = NULL;
- execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments);
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL);
r = safe_fork_full(helper, NULL, 0, FORK_RESET_SIGNALS|FORK_REOPEN_LOG, NULL);
diff --git a/src/login/elogind.c b/src/login/elogind.c
index 9fe52a9..7762248 100644
--- a/src/login/elogind.c
+++ b/src/login/elogind.c
@@ -156,7 +156,7 @@ static int elogind_daemonize(void) {
/// Simple tool to see, if elogind is already running
static pid_t elogind_is_already_running(bool need_pid_file) {
- _cleanup_free_ char *s = NULL;
+ _cleanup_free_ char *s = NULL, *comm = NULL;
pid_t pid;
int r;
@@ -170,8 +170,17 @@ static pid_t elogind_is_already_running(bool need_pid_file) {
if (r < 0)
goto we_are_alone;
- if ( (pid != getpid_cached()) && pid_is_alive(pid))
- return pid;
+ if ( (pid != getpid_cached()) && pid_is_alive(pid)) {
+ /* If the old elogind process currently running was forked into
+ * background, its name will be "elogind-daemon", while this
+ * process will be "elogind".
+ * Therefore check comm with startswith().
+ */
+ get_process_comm(pid, &comm);
+ if (NULL == startswith(strna(comm), program_invocation_short_name))
+ goto we_are_alone;
+ }
+ return pid;
we_are_alone:
@@ -454,27 +463,28 @@ void elogind_manager_reset_config(Manager* m) {
#endif // ENABLE_DEBUG_ELOGIND
}
-
/// Add-On for manager_startup()
-int elogind_manager_startup(Manager *m) {
- int r;
-
- assert(m);
+int elogind_manager_startup(Manager* m) {
+ int r, e = 0;
- assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, -1) >= 0);
+ /* Install our signal handler */
r = sd_event_add_signal(m->event, NULL, SIGINT, elogind_signal_handler, m);
- if (r < 0)
- return log_error_errno(r, "Failed to register SIGINT handler: %m");
+ if (r < 0) {
+ if (e == 0) e = r;
+ log_error_errno(r, "Failed to register SIGINT handler: %m");
+ }
- assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGQUIT, -1) >= 0);
r = sd_event_add_signal(m->event, NULL, SIGQUIT, elogind_signal_handler, m);
- if (r < 0)
- return log_error_errno(r, "Failed to register SIGQUIT handler: %m");
+ if (r < 0) {
+ if (e == 0) e = r;
+ log_error_errno(r, "Failed to register SIGQUIT handler: %m");
+ }
- assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGTERM, -1) >= 0);
r = sd_event_add_signal(m->event, NULL, SIGTERM, elogind_signal_handler, m);
- if (r < 0)
- return log_error_errno(r, "Failed to register SIGTERM handler: %m");
+ if (r < 0) {
+ if (e == 0) e = r;
+ log_error_errno(r, "Failed to register SIGTERM handler: %m");
+ }
- return 0;
+ return e;
}
diff --git a/src/login/elogind.h b/src/login/elogind.h
index 60b9f80..37bcb1e 100644
--- a/src/login/elogind.h
+++ b/src/login/elogind.h
@@ -42,7 +42,6 @@ int elogind_manager_new(Manager* m);
void elogind_manager_reset_config(Manager* m);
/// Add-On for manager_startup()
-int elogind_manager_startup(Manager *m);
-
+int elogind_manager_startup(Manager* m);
#endif // ELOGIND_SRC_LOGIN_ELOGIN_H_INCLUDED
diff --git a/src/login/logind-core.c b/src/login/logind-core.c
index db0df19..e751779 100644
--- a/src/login/logind-core.c
+++ b/src/login/logind-core.c
@@ -5,6 +5,9 @@
#include <sys/ioctl.h>
#include <sys/types.h>
#include <linux/vt.h>
+#if ENABLE_UTMP
+#include <utmpx.h>
+#endif
#include "alloc-util.h"
#include "bus-error.h"
@@ -14,6 +17,7 @@
#include "fd-util.h"
#include "logind.h"
#include "parse-util.h"
+#include "path-util.h"
#include "process-util.h"
#include "strv.h"
#include "terminal-util.h"
@@ -29,6 +33,8 @@ void manager_reset_config(Manager *m) {
#endif // 0
m->remove_ipc = true;
m->inhibit_delay_max = 5 * USEC_PER_SEC;
+ m->user_stop_delay = 10 * USEC_PER_SEC;
+
m->handle_power_key = HANDLE_POWEROFF;
m->handle_suspend_key = HANDLE_SUSPEND;
m->handle_hibernate_key = HANDLE_HIBERNATE;
@@ -102,15 +108,16 @@ int manager_add_device(Manager *m, const char *sysfs, bool master, Device **_dev
int manager_add_seat(Manager *m, const char *id, Seat **_seat) {
Seat *s;
+ int r;
assert(m);
assert(id);
s = hashmap_get(m->seats, id);
if (!s) {
- s = seat_new(m, id);
- if (!s)
- return -ENOMEM;
+ r = seat_new(&s, m, id);
+ if (r < 0)
+ return r;
}
if (_seat)
@@ -121,15 +128,16 @@ int manager_add_seat(Manager *m, const char *id, Seat **_seat) {
int manager_add_session(Manager *m, const char *id, Session **_session) {
Session *s;
+ int r;
assert(m);
assert(id);
s = hashmap_get(m->sessions, id);
if (!s) {
- s = session_new(m, id);
- if (!s)
- return -ENOMEM;
+ r = session_new(&s, m, id);
+ if (r < 0)
+ return r;
}
if (_session)
@@ -138,7 +146,14 @@ int manager_add_session(Manager *m, const char *id, Session **_session) {
return 0;
}
-int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user) {
+int manager_add_user(
+ Manager *m,
+ uid_t uid,
+ gid_t gid,
+ const char *name,
+ const char *home,
+ User **_user) {
+
User *u;
int r;
@@ -147,7 +162,7 @@ int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **
u = hashmap_get(m->users, UID_TO_PTR(uid));
if (!u) {
- r = user_new(&u, m, uid, gid, name);
+ r = user_new(&u, m, uid, gid, name, home);
if (r < 0)
return r;
}
@@ -158,7 +173,12 @@ int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **
return 0;
}
-int manager_add_user_by_name(Manager *m, const char *name, User **_user) {
+int manager_add_user_by_name(
+ Manager *m,
+ const char *name,
+ User **_user) {
+
+ const char *home = NULL;
uid_t uid;
gid_t gid;
int r;
@@ -166,11 +186,11 @@ int manager_add_user_by_name(Manager *m, const char *name, User **_user) {
assert(m);
assert(name);
- r = get_user_creds(&name, &uid, &gid, NULL, NULL);
+ r = get_user_creds(&name, &uid, &gid, &home, NULL);
if (r < 0)
return r;
- return manager_add_user(m, uid, gid, name, _user);
+ return manager_add_user(m, uid, gid, name, home, _user);
}
int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) {
@@ -183,7 +203,7 @@ int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) {
if (!p)
return errno > 0 ? -errno : -ENOENT;
- return manager_add_user(m, uid, p->pw_gid, p->pw_name, _user);
+ return manager_add_user(m, uid, p->pw_gid, p->pw_name, p->pw_dir, _user);
}
int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **_inhibitor) {
@@ -337,26 +357,29 @@ int manager_get_session_by_pid(Manager *m, pid_t pid, Session **ret) {
if (!pid_is_valid(pid))
return -EINVAL;
+ s = hashmap_get(m->sessions_by_leader, PID_TO_PTR(pid));
+ if (!s) {
#if 0 /// elogind does not support systemd units, but its own session system
- r = cg_pid_get_unit(pid, &unit);
- if (r < 0)
- goto not_found;
+ r = cg_pid_get_unit(pid, &unit);
+ if (r < 0)
+ goto not_found;
- s = hashmap_get(m->session_units, unit);
- if (!s)
- goto not_found;
+ s = hashmap_get(m->session_units, unit);
+ if (!s)
+ goto not_found;
#else
- log_debug_elogind("Searching session for PID %u", pid);
- r = cg_pid_get_session(pid, &session_name);
- if (r < 0)
- goto not_found;
+ log_debug_elogind("Searching session for PID %u", pid);
+ r = cg_pid_get_session(pid, &session_name);
+ if (r < 0)
+ goto not_found;
- s = hashmap_get(m->sessions, session_name);
- log_debug_elogind("Session Name \"%s\" -> Session \"%s\"",
- session_name, s && s->id ? s->id : "NULL");
- if (NULL == s)
- goto not_found;
+ s = hashmap_get(m->sessions, session_name);
+ log_debug_elogind("Session Name \"%s\" -> Session \"%s\"",
+ session_name, s && s->id ? s->id : "NULL");
+ if (NULL == s)
+ goto not_found;
#endif // 0
+ }
if (ret)
*ret = s;
@@ -722,3 +745,142 @@ bool manager_all_buttons_ignored(Manager *m) {
return true;
}
+
+int manager_read_utmp(Manager *m) {
+#if ENABLE_UTMP
+ int r;
+
+ assert(m);
+
+ if (utmpxname(_PATH_UTMPX) < 0)
+ return log_error_errno(errno, "Failed to set utmp path to " _PATH_UTMPX ": %m");
+
+ setutxent();
+
+ for (;;) {
+ _cleanup_free_ char *t = NULL;
+ struct utmpx *u;
+ const char *c;
+ Session *s;
+
+ errno = 0;
+ u = getutxent();
+ if (!u) {
+ if (errno != 0)
+ log_warning_errno(errno, "Failed to read " _PATH_UTMPX ", ignoring: %m");
+ r = 0;
+ break;
+ }
+
+ if (u->ut_type != USER_PROCESS)
+ continue;
+
+ if (!pid_is_valid(u->ut_pid))
+ continue;
+
+ t = strndup(u->ut_line, sizeof(u->ut_line));
+ if (!t) {
+ r = log_oom();
+ break;
+ }
+
+ c = path_startswith(t, "/dev/");
+ if (c) {
+ r = free_and_strdup(&t, c);
+ if (r < 0) {
+ log_oom();
+ break;
+ }
+ }
+
+ if (isempty(t))
+ continue;
+
+ s = hashmap_get(m->sessions_by_leader, PID_TO_PTR(u->ut_pid));
+ if (!s)
+ continue;
+
+ if (s->tty_validity == TTY_FROM_UTMP && !streq_ptr(s->tty, t)) {
+ /* This may happen on multiplexed SSH connection (i.e. 'SSH connection sharing'). In
+ * this case PAM and utmp sessions don't match. In such a case let's invalidate the TTY
+ * information and never acquire it again. */
+
+ s->tty = mfree(s->tty);
+ s->tty_validity = TTY_UTMP_INCONSISTENT;
+ log_debug("Session '%s' has inconsistent TTY information, dropping TTY information.", s->id);
+ continue;
+ }
+
+ /* Never override what we figured out once */
+ if (s->tty || s->tty_validity >= 0)
+ continue;
+
+ s->tty = TAKE_PTR(t);
+ s->tty_validity = TTY_FROM_UTMP;
+ log_debug("Acquired TTY information '%s' from utmp for session '%s'.", s->tty, s->id);
+ }
+
+ endutxent();
+ return r;
+#else
+ return 0
+#endif
+}
+
+#if ENABLE_UTMP
+static int manager_dispatch_utmp(sd_event_source *s, const struct inotify_event *event, void *userdata) {
+ Manager *m = userdata;
+
+ assert(m);
+
+ /* If there's indication the file itself might have been removed or became otherwise unavailable, then let's
+ * reestablish the watch on whatever there's now. */
+ if ((event->mask & (IN_ATTRIB|IN_DELETE_SELF|IN_MOVE_SELF|IN_Q_OVERFLOW|IN_UNMOUNT)) != 0)
+ manager_connect_utmp(m);
+
+ (void) manager_read_utmp(m);
+ return 0;
+}
+#endif
+
+void manager_connect_utmp(Manager *m) {
+#if ENABLE_UTMP
+ sd_event_source *s = NULL;
+ int r;
+
+ assert(m);
+
+ /* Watch utmp for changes via inotify. We do this to deal with tools such as ssh, which will register the PAM
+ * session early, and acquire a TTY only much later for the connection. Thus during PAM the TTY won't be known
+ * yet. ssh will register itself with utmp when it finally acquired the TTY. Hence, let's make use of this, and
+ * watch utmp for the TTY asynchronously. We use the PAM session's leader PID as key, to find the right entry.
+ *
+ * Yes, relying on utmp is pretty ugly, but it's good enough for informational purposes, as well as idle
+ * detection (which, for tty sessions, relies on the TTY used) */
+
+ r = sd_event_add_inotify(m->event, &s, _PATH_UTMPX, IN_MODIFY|IN_MOVE_SELF|IN_DELETE_SELF|IN_ATTRIB, manager_dispatch_utmp, m);
+ if (r < 0)
+ log_full_errno(r == -ENOENT ? LOG_DEBUG: LOG_WARNING, r, "Failed to create inotify watch on " _PATH_UTMPX ", ignoring: %m");
+ else {
+ r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IDLE);
+ if (r < 0)
+ log_warning_errno(r, "Failed to adjust utmp event source priority, ignoring: %m");
+
+ (void) sd_event_source_set_description(s, "utmp");
+ }
+
+ sd_event_source_unref(m->utmp_event_source);
+ m->utmp_event_source = s;
+#endif
+}
+
+void manager_reconnect_utmp(Manager *m) {
+#if ENABLE_UTMP
+ assert(m);
+
+ if (m->utmp_event_source)
+ return;
+
+ manager_connect_utmp(m);
+#endif
+}
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
index acf50e6..44f08c5 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -775,6 +775,9 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
} while (hashmap_get(m->sessions, id));
}
+ /* If we are not watching utmp aleady, try again */
+ manager_reconnect_utmp(m);
+
r = manager_add_user_by_uid(m, uid, &user);
if (r < 0)
goto fail;
@@ -784,9 +787,8 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
goto fail;
session_set_user(session, user);
+ session_set_leader(session, leader);
- session->leader = leader;
- session->audit_id = audit_id;
session->type = t;
session->class = c;
session->remote = remote;
@@ -798,6 +800,8 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
r = -ENOMEM;
goto fail;
}
+
+ session->tty_validity = TTY_FROM_PAM;
}
if (!isempty(display)) {
@@ -848,9 +852,9 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
r = sd_bus_message_enter_container(message, 'a', "(sv)");
if (r < 0)
- return r;
+ goto fail;
- r = session_start(session, message);
+ r = session_start(session, message, error);
if (r < 0)
goto fail;
@@ -1842,8 +1846,9 @@ static int method_do_shutdown_or_sleep(
#if 0 /// Within elogind the manager m must be provided, too
r = can_sleep(sleep_verb);
if (r == -ENOSPC)
- return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
- "Not enough swap space for hibernation");
+ return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, "Not enough swap space for hibernation");
+ if (r == -EADV)
+ return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, "Resume not configured, can't hibernate");
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED,
"Sleep verb \"%s\" not supported", sleep_verb);
@@ -2285,6 +2290,7 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd
if (cancelled && m->enable_wall_messages) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ _cleanup_free_ char *username = NULL;
const char *tty = NULL;
uid_t uid = 0;
int r;
@@ -2295,9 +2301,10 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd
(void) sd_bus_creds_get_tty(creds, &tty);
}
+ username = uid_to_name(uid);
#if 0 /// elogind wants to allow extra cancellation messages
utmp_wall("The system shutdown has been cancelled",
- uid_to_name(uid), tty, logind_wall_tty_filter, m);
+ username, tty, logind_wall_tty_filter, m);
#else
r = asprintf(&l, "%s%sThe system shutdown has been cancelled!",
strempty(m->wall_message),
@@ -2345,7 +2352,7 @@ static int method_can_shutdown_or_sleep(
#else
r = can_sleep(m, sleep_verb);
#endif // 0
- if (IN_SET(r, 0, -ENOSPC))
+ if (IN_SET(r, 0, -ENOSPC, -EADV))
return sd_bus_reply_method_return(message, "s", "na");
if (r < 0)
return r;
@@ -2367,7 +2374,6 @@ static int method_can_shutdown_or_sleep(
blocked = manager_is_inhibited(m, w, INHIBIT_BLOCK, NULL, false, true, uid, NULL);
handle = handle_action_from_string(sleep_verb);
-
if (handle >= 0) {
#if 0 /// elogind uses its own variant, which can use the handle directly.
const char *target;
@@ -2385,7 +2391,6 @@ static int method_can_shutdown_or_sleep(
goto finish;
}
}
- }
#else
log_debug_elogind("CanShutDownOrSleep: %s [%d] %s blocked",
sleep_verb, handle, blocked ? "is" : "not");
@@ -2819,6 +2824,7 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_PROPERTY("BlockInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("DelayInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("InhibitDelayMaxUSec", "t", NULL, offsetof(Manager, inhibit_delay_max), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("UserStopDelayUSec", "t", NULL, offsetof(Manager, user_stop_delay), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("HandlePowerKey", "s", property_get_handle_action, offsetof(Manager, handle_power_key), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("HandleSuspendKey", "s", property_get_handle_action, offsetof(Manager, handle_suspend_key), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("HandleHibernateKey", "s", property_get_handle_action, offsetof(Manager, handle_hibernate_key), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -2900,24 +2906,20 @@ const sd_bus_vtable manager_vtable[] = {
#if 0 /// UNNEEDED by elogind
static int session_jobs_reply(Session *s, const char *unit, const char *result) {
- int r = 0;
-
assert(s);
assert(unit);
if (!s->started)
- return r;
+ return 0;
- if (streq(result, "done"))
- r = session_send_create_reply(s, NULL);
- else {
+ if (result && !streq(result, "done")) {
_cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
- sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Start job for unit %s failed with '%s'", unit, result);
- r = session_send_create_reply(s, &e);
+ sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Start job for unit '%s' failed with '%s'", unit, result);
+ return session_send_create_reply(s, &e);
}
- return r;
+ return session_send_create_reply(s, NULL);
}
int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
@@ -2950,30 +2952,29 @@ int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *err
}
session = hashmap_get(m->session_units, unit);
- if (session && streq_ptr(path, session->scope_job)) {
- session->scope_job = mfree(session->scope_job);
- session_jobs_reply(session, unit, result);
+ if (session) {
+ if (streq_ptr(path, session->scope_job)) {
+ session->scope_job = mfree(session->scope_job);
+ (void) session_jobs_reply(session, unit, result);
+
+ session_save(session);
+ user_save(session->user);
+ }
- session_save(session);
- user_save(session->user);
session_add_to_gc_queue(session);
}
user = hashmap_get(m->user_units, unit);
- if (user &&
- (streq_ptr(path, user->service_job) ||
- streq_ptr(path, user->slice_job))) {
-
- if (streq_ptr(path, user->service_job))
+ if (user) {
+ if (streq_ptr(path, user->service_job)) {
user->service_job = mfree(user->service_job);
- if (streq_ptr(path, user->slice_job))
- user->slice_job = mfree(user->slice_job);
+ LIST_FOREACH(sessions_by_user, session, user->sessions)
+ (void) session_jobs_reply(session, unit, NULL /* don't propagate user service failures to the client */);
- LIST_FOREACH(sessions_by_user, session, user->sessions)
- session_jobs_reply(session, unit, result);
+ user_save(user);
+ }
- user_save(user);
user_add_to_gc_queue(user);
}
@@ -3107,13 +3108,15 @@ int manager_start_scope(
pid_t pid,
const char *slice,
const char *description,
- const char *after,
- const char *after2,
+ char **wants,
+ char **after,
+ const char *requires_mounts_for,
sd_bus_message *more_properties,
sd_bus_error *error,
char **job) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ char **i;
int r;
assert(manager);
@@ -3151,14 +3154,20 @@ int manager_start_scope(
return r;
}
- if (!isempty(after)) {
- r = sd_bus_message_append(m, "(sv)", "After", "as", 1, after);
+ STRV_FOREACH(i, wants) {
+ r = sd_bus_message_append(m, "(sv)", "Wants", "as", 1, *i);
if (r < 0)
return r;
}
- if (!isempty(after2)) {
- r = sd_bus_message_append(m, "(sv)", "After", "as", 1, after2);
+ STRV_FOREACH(i, after) {
+ r = sd_bus_message_append(m, "(sv)", "After", "as", 1, *i);
+ if (r < 0)
+ return r;
+ }
+
+ if (!empty_or_root(requires_mounts_for)) {
+ r = sd_bus_message_append(m, "(sv)", "RequiresMountsFor", "as", 1, requires_mounts_for);
if (r < 0)
return r;
}
@@ -3255,7 +3264,8 @@ int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, c
return strdup_job(reply, job);
}
-int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *error) {
+int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *ret_error) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *path = NULL;
int r;
@@ -3272,17 +3282,16 @@ int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *err
path,
"org.freedesktop.systemd1.Scope",
"Abandon",
- error,
+ &error,
NULL,
NULL);
if (r < 0) {
- if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) ||
- sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED) ||
- sd_bus_error_has_name(error, BUS_ERROR_SCOPE_NOT_RUNNING)) {
- sd_bus_error_free(error);
+ if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) ||
+ sd_bus_error_has_name(&error, BUS_ERROR_LOAD_FAILED) ||
+ sd_bus_error_has_name(&error, BUS_ERROR_SCOPE_NOT_RUNNING))
return 0;
- }
+ sd_bus_error_move(ret_error, &error);
return r;
}
@@ -3304,7 +3313,7 @@ int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo
"ssi", unit, who == KILL_LEADER ? "main" : "all", signo);
}
-int manager_unit_is_active(Manager *manager, const char *unit) {
+int manager_unit_is_active(Manager *manager, const char *unit, sd_bus_error *ret_error) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ char *path = NULL;
@@ -3340,17 +3349,18 @@ int manager_unit_is_active(Manager *manager, const char *unit) {
sd_bus_error_has_name(&error, BUS_ERROR_LOAD_FAILED))
return false;
+ sd_bus_error_move(ret_error, &error);
return r;
}
r = sd_bus_message_read(reply, "s", &state);
if (r < 0)
- return -EINVAL;
+ return r;
- return !streq(state, "inactive") && !streq(state, "failed");
+ return !STR_IN_SET(state, "inactive", "failed");
}
-int manager_job_is_active(Manager *manager, const char *path) {
+int manager_job_is_active(Manager *manager, const char *path, sd_bus_error *ret_error) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
int r;
@@ -3375,6 +3385,7 @@ int manager_job_is_active(Manager *manager, const char *path) {
if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_OBJECT))
return false;
+ sd_bus_error_move(ret_error, &error);
return r;
}
diff --git a/src/login/logind-device.c b/src/login/logind-device.c
index 9b5b3e8..e724365 100644
--- a/src/login/logind-device.c
+++ b/src/login/logind-device.c
@@ -99,6 +99,8 @@ void device_attach(Device *d, Seat *s) {
}
}
- if (!had_master && d->master)
+ if (!had_master && d->master && s->started) {
+ seat_save(s);
seat_send_changed(s, "CanGraphical", NULL);
+ }
}
diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf
index c8523fb..fe593d5 100644
--- a/src/login/logind-gperf.gperf
+++ b/src/login/logind-gperf.gperf
@@ -34,6 +34,7 @@ Login.KillUserProcesses, config_parse_bool, 0, offse
Login.KillOnlyUsers, config_parse_strv, 0, offsetof(Manager, kill_only_users)
Login.KillExcludeUsers, config_parse_strv, 0, offsetof(Manager, kill_exclude_users)
Login.InhibitDelayMaxSec, config_parse_sec, 0, offsetof(Manager, inhibit_delay_max)
+Login.UserStopDelaySec, config_parse_sec, 0, offsetof(Manager, user_stop_delay)
Login.HandlePowerKey, config_parse_handle_action, 0, offsetof(Manager, handle_power_key)
Login.HandleSuspendKey, config_parse_handle_action, 0, offsetof(Manager, handle_suspend_key)
Login.HandleHibernateKey, config_parse_handle_action, 0, offsetof(Manager, handle_hibernate_key)
diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c
index 0ee5f70..c8fc4bb 100644
--- a/src/login/logind-seat.c
+++ b/src/login/logind-seat.c
@@ -21,33 +21,42 @@
#include "terminal-util.h"
#include "util.h"
-Seat *seat_new(Manager *m, const char *id) {
- Seat *s;
+int seat_new(Seat** ret, Manager *m, const char *id) {
+ _cleanup_(seat_freep) Seat *s = NULL;
+ int r;
+ assert(ret);
assert(m);
assert(id);
- s = new0(Seat, 1);
+ if (!seat_name_is_valid(id))
+ return -EINVAL;
+
+ s = new(Seat, 1);
if (!s)
- return NULL;
+ return -ENOMEM;
+
+ *s = (Seat) {
+ .manager = m,
+ };
s->state_file = strappend("/run/systemd/seats/", id);
if (!s->state_file)
- return mfree(s);
+ return -ENOMEM;
s->id = basename(s->state_file);
- s->manager = m;
- if (hashmap_put(m->seats, s->id, s) < 0) {
- free(s->state_file);
- return mfree(s);
- }
+ r = hashmap_put(m->seats, s->id, s);
+ if (r < 0)
+ return r;
- return s;
+ *ret = TAKE_PTR(s);
+ return 0;
}
-void seat_free(Seat *s) {
- assert(s);
+Seat* seat_free(Seat *s) {
+ if (!s)
+ return NULL;
if (s->in_gc_queue)
LIST_REMOVE(gc_queue, s->manager->seat_gc_queue, s);
@@ -64,7 +73,8 @@ void seat_free(Seat *s) {
free(s->positions);
free(s->state_file);
- free(s);
+
+ return mfree(s);
}
int seat_save(Seat *s) {
@@ -166,7 +176,7 @@ static int vt_allocate(unsigned int vtnr) {
xsprintf(p, "/dev/tty%u", vtnr);
fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
- return -errno;
+ return fd;
return 0;
}
@@ -190,10 +200,8 @@ int seat_preallocate_vts(Seat *s) {
int q;
q = vt_allocate(i);
- if (q < 0) {
- log_error_errno(q, "Failed to preallocate VT %u: %m", i);
- r = q;
- }
+ if (q < 0)
+ r = log_error_errno(q, "Failed to preallocate VT %u: %m", i);
}
return r;
@@ -212,9 +220,9 @@ int seat_apply_acls(Seat *s, Session *old_active) {
!!s->active, s->active ? s->active->user->uid : 0);
if (r < 0)
- log_error_errno(r, "Failed to apply ACLs: %m");
+ return log_error_errno(r, "Failed to apply ACLs: %m");
- return r;
+ return 0;
}
int seat_set_active(Seat *s, Session *session) {
@@ -234,7 +242,7 @@ int seat_set_active(Seat *s, Session *session) {
session_send_changed(old_active, "Active", NULL);
}
- seat_apply_acls(s, old_active);
+ (void) seat_apply_acls(s, old_active);
if (session && session->started) {
session_send_changed(session, "Active", NULL);
@@ -427,7 +435,7 @@ int seat_start(Seat *s) {
}
int seat_stop(Seat *s, bool force) {
- int r = 0;
+ int r;
assert(s);
@@ -437,9 +445,9 @@ int seat_stop(Seat *s, bool force) {
"SEAT_ID=%s", s->id,
LOG_MESSAGE("Removed seat %s.", s->id));
- seat_stop_sessions(s, force);
+ r = seat_stop_sessions(s, force);
- unlink(s->state_file);
+ (void) unlink(s->state_file);
seat_add_to_gc_queue(s);
if (s->started)
diff --git a/src/login/logind-seat.h b/src/login/logind-seat.h
index 6d45062..0446384 100644
--- a/src/login/logind-seat.h
+++ b/src/login/logind-seat.h
@@ -27,8 +27,10 @@ struct Seat {
LIST_FIELDS(Seat, gc_queue);
};
-Seat *seat_new(Manager *m, const char *id);
-void seat_free(Seat *s);
+int seat_new(Seat **ret, Manager *m, const char *id);
+Seat* seat_free(Seat *s);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Seat *, seat_free);
int seat_save(Seat *s);
int seat_load(Seat *s);
diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c
index 8a95fc7..e7f64fd 100644
--- a/src/login/logind-session-dbus.c
+++ b/src/login/logind-session-dbus.c
@@ -689,6 +689,17 @@ int session_send_lock_all(Manager *m, bool lock) {
return r;
}
+#if 0 /// elogind does not support scope and service jobs
+static bool session_ready(Session *s) {
+ assert(s);
+
+ /* Returns true when the session is ready, i.e. all jobs we enqueued for it are done (regardless if successful or not) */
+
+ return !s->scope_job &&
+ !s->user->service_job;
+}
+#endif // 0
+
int session_send_create_reply(Session *s, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
_cleanup_close_ int fifo_fd = -1;
@@ -696,21 +707,18 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
assert(s);
- /* This is called after the session scope and the user service
- * were successfully created, and finishes where
+ /* This is called after the session scope and the user service were successfully created, and finishes where
* bus_manager_create_session() left off. */
if (!s->create_message)
return 0;
#if 0 /// elogind does not support scope and service jobs
- if (!sd_bus_error_is_set(error) && (s->scope_job || s->user->service_job))
+ if (!sd_bus_error_is_set(error) && !session_ready(s))
return 0;
#endif // 0
- c = s->create_message;
- s->create_message = NULL;
-
+ c = TAKE_PTR(s->create_message);
if (error)
return sd_bus_reply_method_error(c, error);
@@ -718,8 +726,7 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
if (fifo_fd < 0)
return fifo_fd;
- /* Update the session state file before we notify the client
- * about the result. */
+ /* Update the session state file before we notify the client about the result. */
session_save(s);
#if 1 /// Additionally elogind saves the user state file
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index fedf583..dce0f73 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -5,6 +5,7 @@
#include <linux/kd.h>
#include <linux/vt.h>
#include <signal.h>
+#include <stdio_ext.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
@@ -24,7 +25,9 @@
#include "mkdir.h"
#include "parse-util.h"
#include "path-util.h"
+//#include "process-util.h"
#include "string-table.h"
+//#include "strv.h"
#include "terminal-util.h"
#include "user-util.h"
#include "util.h"
@@ -37,47 +40,52 @@
static void session_remove_fifo(Session *s);
-Session* session_new(Manager *m, const char *id) {
- Session *s;
+int session_new(Session **ret, Manager *m, const char *id) {
+ _cleanup_(session_freep) Session *s = NULL;
+ int r;
+ assert(ret);
assert(m);
assert(id);
- assert(session_id_valid(id));
- s = new0(Session, 1);
+ if (!session_id_valid(id))
+ return -EINVAL;
+
+ s = new(Session, 1);
if (!s)
- return NULL;
+ return -ENOMEM;
+
+ *s = (Session) {
+ .manager = m,
+ .fifo_fd = -1,
+ .vtfd = -1,
+ .audit_id = AUDIT_SESSION_INVALID,
+ .tty_validity = _TTY_VALIDITY_INVALID,
+ };
s->state_file = strappend("/run/systemd/sessions/", id);
if (!s->state_file)
- return mfree(s);
-
- s->devices = hashmap_new(&devt_hash_ops);
- if (!s->devices) {
- free(s->state_file);
- return mfree(s);
- }
+ return -ENOMEM;
s->id = basename(s->state_file);
- if (hashmap_put(m->sessions, s->id, s) < 0) {
- hashmap_free(s->devices);
- free(s->state_file);
- return mfree(s);
- }
+ s->devices = hashmap_new(&devt_hash_ops);
+ if (!s->devices)
+ return -ENOMEM;
- s->manager = m;
- s->fifo_fd = -1;
- s->vtfd = -1;
- s->audit_id = AUDIT_SESSION_INVALID;
+ r = hashmap_put(m->sessions, s->id, s);
+ if (r < 0)
+ return r;
- return s;
+ *ret = TAKE_PTR(s);
+ return 0;
}
-void session_free(Session *s) {
+Session* session_free(Session *s) {
SessionDevice *sd;
- assert(s);
+ if (!s)
+ return NULL;
if (s->in_gc_queue)
LIST_REMOVE(gc_queue, s->manager->session_gc_queue, s);
@@ -98,6 +106,8 @@ void session_free(Session *s) {
if (s->user->display == s)
s->user->display = NULL;
+
+ user_update_last_session_timer(s->user);
}
if (s->seat) {
@@ -110,10 +120,15 @@ void session_free(Session *s) {
LIST_REMOVE(sessions_by_seat, s->seat->sessions, s);
}
+#if 0 /// elogind does not support systemd units and scope_jobs
if (s->scope) {
hashmap_remove(s->manager->session_units, s->scope);
free(s->scope);
}
+#endif // 0
+
+ if (pid_is_valid(s->leader))
+ (void) hashmap_remove_value(s->manager->sessions_by_leader, PID_TO_PTR(s->leader), s);
#if 0 /// elogind does not support systemd scope_jobs
free(s->scope_job);
@@ -131,7 +146,8 @@ void session_free(Session *s) {
hashmap_remove(s->manager->sessions, s->id);
free(s->state_file);
- free(s);
+
+ return mfree(s);
}
void session_set_user(Session *s, User *u) {
@@ -140,6 +156,32 @@ void session_set_user(Session *s, User *u) {
s->user = u;
LIST_PREPEND(sessions_by_user, u->sessions, s);
+
+ user_update_last_session_timer(u);
+}
+
+int session_set_leader(Session *s, pid_t pid) {
+ int r;
+
+ assert(s);
+
+ if (!pid_is_valid(pid))
+ return -EINVAL;
+
+ if (s->leader == pid)
+ return 0;
+
+ r = hashmap_put(s->manager->sessions_by_leader, PID_TO_PTR(pid), s);
+ if (r < 0)
+ return r;
+
+ if (pid_is_valid(s->leader))
+ (void) hashmap_remove_value(s->manager->sessions_by_leader, PID_TO_PTR(s->leader), s);
+
+ s->leader = pid;
+ (void) audit_session_from_pid(pid, &s->audit_id);
+
+ return 1;
}
static void session_save_devices(Session *s, FILE *f) {
@@ -175,20 +217,21 @@ int session_save(Session *s) {
if (r < 0)
goto fail;
- assert(s->user);
-
- fchmod(fileno(f), 0644);
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ (void) fchmod(fileno(f), 0644);
fprintf(f,
"# This is private data. Do not parse.\n"
"UID="UID_FMT"\n"
"USER=%s\n"
"ACTIVE=%i\n"
+ "IS_DISPLAY=%i\n"
"STATE=%s\n"
"REMOTE=%i\n",
s->user->uid,
s->user->name,
session_is_active(s),
+ s->user->display == s,
session_state_to_string(session_get_state(s)),
s->remote);
@@ -214,6 +257,9 @@ int session_save(Session *s) {
if (s->tty)
fprintf(f, "TTY=%s\n", s->tty);
+ if (s->tty_validity >= 0)
+ fprintf(f, "TTY_VALIDITY=%s\n", tty_validity_to_string(s->tty_validity));
+
if (s->display)
fprintf(f, "DISPLAY=%s\n", s->display);
@@ -350,6 +396,7 @@ static int session_load_devices(Session *s, const char *devices) {
int session_load(Session *s) {
_cleanup_free_ char *remote = NULL,
*seat = NULL,
+ *tty_validity = NULL,
*vtnr = NULL,
*state = NULL,
*position = NULL,
@@ -361,7 +408,8 @@ int session_load(Session *s) {
*monotonic = NULL,
*controller = NULL,
*active = NULL,
- *devices = NULL;
+ *devices = NULL,
+ *is_display = NULL;
int k, r;
@@ -376,6 +424,7 @@ int session_load(Session *s) {
"FIFO", &s->fifo_path,
"SEAT", &seat,
"TTY", &s->tty,
+ "TTY_VALIDITY", &tty_validity,
"DISPLAY", &s->display,
"REMOTE_HOST", &s->remote_host,
"REMOTE_USER", &s->remote_user,
@@ -393,6 +442,7 @@ int session_load(Session *s) {
"CONTROLLER", &controller,
"ACTIVE", &active,
"DEVICES", &devices,
+ "IS_DISPLAY", &is_display,
NULL);
if (r < 0)
@@ -451,9 +501,27 @@ int session_load(Session *s) {
seat_claim_position(s->seat, s, npos);
}
+ if (tty_validity) {
+ TTYValidity v;
+
+ v = tty_validity_from_string(tty_validity);
+ if (v < 0)
+ log_debug("Failed to parse TTY validity: %s", tty_validity);
+ else
+ s->tty_validity = v;
+ }
+
if (leader) {
- if (parse_pid(leader, &s->leader) >= 0)
- (void) audit_session_from_pid(s->leader, &s->audit_id);
+ pid_t pid;
+
+ r = parse_pid(leader, &pid);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse leader PID of session: %s", leader);
+ else {
+ r = session_set_leader(s, pid);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set session leader PID, ignoring: %m");
+ }
}
if (type) {
@@ -500,6 +568,18 @@ int session_load(Session *s) {
s->was_active = k;
}
+ if (is_display) {
+ /* Note that when enumerating users are loaded before sessions, hence the display session to use is
+ * something we have to store along with the session and not the user, as in that case we couldn't
+ * apply it at the time we load the user. */
+
+ k = parse_boolean(is_display);
+ if (k < 0)
+ log_warning_errno(k, "Failed to parse IS_DISPLAY session property: %m");
+ else if (k > 0)
+ s->user->display = s;
+ }
+
if (controller) {
if (bus_name_has_owner(s->manager->bus, controller, NULL) > 0) {
session_set_controller(s, controller, false, false);
@@ -525,7 +605,7 @@ int session_activate(Session *s) {
/* on seats with VTs, we let VTs manage session-switching */
if (seat_has_vts(s->seat)) {
- if (!s->vtnr)
+ if (s->vtnr == 0)
return -EOPNOTSUPP;
return chvt(s->vtnr);
@@ -549,17 +629,18 @@ int session_activate(Session *s) {
}
#if 0 /// UNNEEDED by elogind
-static int session_start_scope(Session *s, sd_bus_message *properties) {
+static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_error *error) {
int r;
assert(s);
assert(s->user);
if (!s->scope) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- char *scope, *job = NULL;
+ _cleanup_free_ char *scope = NULL;
const char *description;
+ s->scope_job = mfree(s->scope_job);
+
scope = strjoin("session-", s->id, ".scope");
if (!scope)
return log_oom();
@@ -572,21 +653,16 @@ static int session_start_scope(Session *s, sd_bus_message *properties) {
s->leader,
s->user->slice,
description,
- "systemd-logind.service",
- "systemd-user-sessions.service",
+ STRV_MAKE(s->user->runtime_dir_service, s->user->service), /* These two have StopWhenUnneeded= set, hence add a dep towards them */
+ STRV_MAKE("systemd-logind.service", "systemd-user-sessions.service", s->user->runtime_dir_service, s->user->service), /* And order us after some more */
+ s->user->home,
properties,
- &error,
- &job);
- if (r < 0) {
- log_error_errno(r, "Failed to start session scope %s: %s", scope, bus_error_message(&error, r));
- free(scope);
- return r;
- } else {
- s->scope = scope;
+ error,
+ &s->scope_job);
+ if (r < 0)
+ return log_error_errno(r, "Failed to start session scope %s: %s", scope, bus_error_message(error, r));
- free(s->scope_job);
- s->scope_job = job;
- }
+ s->scope = TAKE_PTR(scope);
}
if (s->scope)
@@ -615,7 +691,7 @@ static int session_start_cgroup(Session *s) {
}
#endif // 0
-int session_start(Session *s, sd_bus_message *properties) {
+int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) {
int r;
assert(s);
@@ -623,6 +699,9 @@ int session_start(Session *s, sd_bus_message *properties) {
if (!s->user)
return -ESTALE;
+ if (s->stopping)
+ return -EINVAL;
+
if (s->started)
return 0;
@@ -630,9 +709,8 @@ int session_start(Session *s, sd_bus_message *properties) {
if (r < 0)
return r;
- /* Create cgroup */
#if 0 /// elogind does its own session management
- r = session_start_scope(s, properties);
+ r = session_start_scope(s, properties, error);
#else
r = session_start_cgroup(s);
#endif // 0
@@ -687,21 +765,24 @@ static int session_stop_scope(Session *s, bool force) {
* that is left in the scope is "left-over". Informing systemd about this has the benefit that it will log
* when killing any processes left after this point. */
r = manager_abandon_scope(s->manager, s->scope, &error);
- if (r < 0)
+ if (r < 0) {
log_warning_errno(r, "Failed to abandon session scope, ignoring: %s", bus_error_message(&error, r));
+ sd_bus_error_free(&error);
+ }
+
+ s->scope_job = mfree(s->scope_job);
/* Optionally, let's kill everything that's left now. */
if (force || manager_shall_kill(s->manager, s->user->name)) {
- char *job = NULL;
- r = manager_stop_unit(s->manager, s->scope, &error, &job);
- if (r < 0)
- return log_error_errno(r, "Failed to stop session scope: %s", bus_error_message(&error, r));
+ r = manager_stop_unit(s->manager, s->scope, &error, &s->scope_job);
+ if (r < 0) {
+ if (force)
+ return log_error_errno(r, "Failed to stop session scope: %s", bus_error_message(&error, r));
- free(s->scope_job);
- s->scope_job = job;
+ log_warning_errno(r, "Failed to stop session scope, ignoring: %s", bus_error_message(&error, r));
+ }
} else {
- s->scope_job = mfree(s->scope_job);
/* With no killing, this session is allowed to persist in "closing" state indefinitely.
* Therefore session stop and session removal may be two distinct events.
@@ -739,8 +820,17 @@ int session_stop(Session *s, bool force) {
assert(s);
+ /* This is called whenever we begin with tearing down a session record. It's called in four cases: explicit API
+ * request via the bus (either directly for the session object or for the seat or user object this session
+ * belongs to; 'force' is true), or due to automatic GC (i.e. scope vanished; 'force' is false), or because the
+ * session FIFO saw an EOF ('force' is false), or because the release timer hit ('force' is false). */
+
if (!s->user)
return -ESTALE;
+ if (!s->started)
+ return 0;
+ if (s->stopping)
+ return 0;
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
@@ -839,7 +929,7 @@ int session_release(Session *s) {
return sd_event_add_time(s->manager->event,
&s->timer_event_source,
CLOCK_MONOTONIC,
- now(CLOCK_MONOTONIC) + RELEASE_USEC, 0,
+ usec_add(now(CLOCK_MONOTONIC), RELEASE_USEC), 0,
release_timeout_callback, s);
}
@@ -918,7 +1008,7 @@ int session_get_idle_hint(Session *s, dual_timestamp *t) {
/* For sessions with a leader but no explicitly configured
* tty, let's check the controlling tty of the leader */
- if (s->leader > 0) {
+ if (pid_is_valid(s->leader)) {
r = get_process_ctty_atime(s->leader, &atime);
if (r >= 0)
goto found_atime;
@@ -1002,7 +1092,8 @@ int session_create_fifo(Session *s) {
if (r < 0)
return r;
- if (asprintf(&s->fifo_path, "/run/systemd/sessions/%s.ref", s->id) < 0)
+ s->fifo_path = strjoin("/run/systemd/sessions/", s->id, ".ref");
+ if (!s->fifo_path)
return -ENOMEM;
if (mkfifo(s->fifo_path, 0600) < 0 && errno != EEXIST)
@@ -1014,7 +1105,6 @@ int session_create_fifo(Session *s) {
s->fifo_fd = open(s->fifo_path, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
if (s->fifo_fd < 0)
return -errno;
-
}
if (!s->fifo_event_source) {
@@ -1044,12 +1134,16 @@ static void session_remove_fifo(Session *s) {
s->fifo_fd = safe_close(s->fifo_fd);
if (s->fifo_path) {
- unlink(s->fifo_path);
+ (void) unlink(s->fifo_path);
s->fifo_path = mfree(s->fifo_path);
}
}
bool session_may_gc(Session *s, bool drop_not_started) {
+#if 0 /// UNNEEDED by elogind
+ int r;
+#endif // 0
+
assert(s);
if (drop_not_started && !s->started)
@@ -1064,11 +1158,25 @@ bool session_may_gc(Session *s, bool drop_not_started) {
}
#if 0 /// elogind supports neither scopes nor jobs
- if (s->scope_job && manager_job_is_active(s->manager, s->scope_job))
- return false;
+ if (s->scope_job) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- if (s->scope && manager_unit_is_active(s->manager, s->scope))
- return false;
+ r = manager_job_is_active(s->manager, s->scope_job, &error);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", s->scope_job, bus_error_message(&error, r));
+ if (r != 0)
+ return false;
+ }
+
+ if (s->scope) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ r = manager_unit_is_active(s->manager, s->scope, &error);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether unit '%s' is active, ignoring: %s", s->scope, bus_error_message(&error, r));
+ if (r != 0)
+ return false;
+ }
#endif // 0
return true;
@@ -1395,3 +1503,11 @@ static const char* const kill_who_table[_KILL_WHO_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);
+
+static const char* const tty_validity_table[_TTY_VALIDITY_MAX] = {
+ [TTY_FROM_PAM] = "from-pam",
+ [TTY_FROM_UTMP] = "from-utmp",
+ [TTY_UTMP_INCONSISTENT] = "utmp-inconsistent",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(tty_validity, TTYValidity);
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
index 117f468..0bca768 100644
--- a/src/login/logind-session.h
+++ b/src/login/logind-session.h
@@ -46,6 +46,14 @@ enum KillWho {
_KILL_WHO_INVALID = -1
};
+typedef enum TTYValidity {
+ TTY_FROM_PAM,
+ TTY_FROM_UTMP,
+ TTY_UTMP_INCONSISTENT, /* may happen on ssh sessions with multiplexed TTYs */
+ _TTY_VALIDITY_MAX,
+ _TTY_VALIDITY_INVALID = -1,
+} TTYValidity;
+
struct Session {
Manager *manager;
@@ -60,8 +68,9 @@ struct Session {
dual_timestamp timestamp;
- char *tty;
char *display;
+ char *tty;
+ TTYValidity tty_validity;
bool remote;
char *remote_user;
@@ -99,6 +108,7 @@ struct Session {
sd_bus_message *create_message;
+ /* Set up when a client requested to release the session via the bus */
sd_event_source *timer_event_source;
char *controller;
@@ -111,9 +121,13 @@ struct Session {
LIST_FIELDS(Session, gc_queue);
};
-Session *session_new(Manager *m, const char *id);
-void session_free(Session *s);
+int session_new(Session **ret, Manager *m, const char *id);
+Session* session_free(Session *s);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Session *, session_free);
+
void session_set_user(Session *s, User *u);
+int session_set_leader(Session *s, pid_t pid);
bool session_may_gc(Session *s, bool drop_not_started);
void session_add_to_gc_queue(Session *s);
int session_activate(Session *s);
@@ -123,7 +137,7 @@ void session_set_idle_hint(Session *s, bool b);
int session_get_locked_hint(Session *s);
void session_set_locked_hint(Session *s, bool b);
int session_create_fifo(Session *s);
-int session_start(Session *s, sd_bus_message *properties);
+int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error);
int session_stop(Session *s, bool force);
int session_finalize(Session *s);
int session_release(Session *s);
@@ -157,6 +171,9 @@ SessionClass session_class_from_string(const char *s) _pure_;
const char *kill_who_to_string(KillWho k) _const_;
KillWho kill_who_from_string(const char *s) _pure_;
+const char* tty_validity_to_string(TTYValidity t) _const_;
+TTYValidity tty_validity_from_string(const char *s) _pure_;
+
int session_prepare_vt(Session *s);
void session_restore_vt(Session *s);
void session_leave_vt(Session *s);
diff --git a/src/login/logind-user-dbus.c b/src/login/logind-user-dbus.c
index c662a26..9620fb0 100644
--- a/src/login/logind-user-dbus.c
+++ b/src/login/logind-user-dbus.c
@@ -109,7 +109,7 @@ static int property_get_idle_since_hint(
assert(reply);
assert(u);
- user_get_idle_hint(u, &t);
+ (void) user_get_idle_hint(u, &t);
k = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
return sd_bus_message_append(reply, "t", k);
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
index 38a15b2..78822c9 100644
--- a/src/login/logind-user.c
+++ b/src/login/logind-user.c
@@ -26,40 +26,54 @@
#include "special.h"
#include "stdio-util.h"
#include "string-table.h"
+//#include "strv.h"
#include "unit-name.h"
#include "user-util.h"
//#include "util.h"
/// Additional includes needed by elogind
#include "user-runtime-dir.h"
-int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name) {
+int user_new(User **ret,
+ Manager *m,
+ uid_t uid,
+ gid_t gid,
+ const char *name,
+ const char *home) {
+
_cleanup_(user_freep) User *u = NULL;
char lu[DECIMAL_STR_MAX(uid_t) + 1];
int r;
- assert(out);
+ assert(ret);
assert(m);
assert(name);
- u = new0(User, 1);
+ u = new(User, 1);
if (!u)
return -ENOMEM;
- u->manager = m;
- u->uid = uid;
- u->gid = gid;
- xsprintf(lu, UID_FMT, uid);
+ *u = (User) {
+ .manager = m,
+ .uid = uid,
+ .gid = gid,
+ .last_session_timestamp = USEC_INFINITY,
+ };
u->name = strdup(name);
if (!u->name)
return -ENOMEM;
+ u->home = strdup(home);
+ if (!u->home)
+ return -ENOMEM;
+
if (asprintf(&u->state_file, "/run/systemd/users/"UID_FMT, uid) < 0)
return -ENOMEM;
if (asprintf(&u->runtime_path, "/run/user/"UID_FMT, uid) < 0)
return -ENOMEM;
+ xsprintf(lu, UID_FMT, uid);
r = slice_build_subslice(SPECIAL_USER_SLICE, lu, &u->slice);
if (r < 0)
return r;
@@ -68,10 +82,15 @@ int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name) {
if (r < 0)
return r;
+ r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_service);
+ if (r < 0)
+ return r;
+
r = hashmap_put(m->users, UID_TO_PTR(uid), u);
if (r < 0)
return r;
+#if 0 /// elogind does not support systemd units
r = hashmap_put(m->user_units, u->slice, u);
if (r < 0)
return r;
@@ -80,8 +99,12 @@ int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name) {
if (r < 0)
return r;
- *out = TAKE_PTR(u);
+ r = hashmap_put(m->user_units, u->runtime_dir_service, u);
+ if (r < 0)
+ return r;
+#endif // 0
+ *ret = TAKE_PTR(u);
return 0;
}
@@ -95,24 +118,32 @@ User *user_free(User *u) {
while (u->sessions)
session_free(u->sessions);
+#if 0 /// elogind does not support systemd units
if (u->service)
hashmap_remove_value(u->manager->user_units, u->service, u);
+ if (u->runtime_dir_service)
+ hashmap_remove_value(u->manager->user_units, u->runtime_dir_service, u);
+
if (u->slice)
hashmap_remove_value(u->manager->user_units, u->slice, u);
+#endif // 0
hashmap_remove_value(u->manager->users, UID_TO_PTR(u->uid), u);
+ (void) sd_event_source_unref(u->timer_event_source);
+
#if 0 /// elogind neither supports slice nor service jobs.
- u->slice_job = mfree(u->slice_job);
u->service_job = mfree(u->service_job);
#endif // 0
u->service = mfree(u->service);
+ u->runtime_dir_service = mfree(u->runtime_dir_service);
u->slice = mfree(u->slice);
u->runtime_path = mfree(u->runtime_path);
u->state_file = mfree(u->state_file);
u->name = mfree(u->name);
+ u->home = mfree(u->home);
return mfree(u);
}
@@ -139,9 +170,11 @@ static int user_save_internal(User *u) {
fprintf(f,
"# This is private data. Do not parse.\n"
"NAME=%s\n"
- "STATE=%s\n",
+ "STATE=%s\n" /* friendly user-facing state */
+ "STOPPING=%s\n", /* low-level state */
u->name,
- user_state_to_string(user_get_state(u)));
+ user_state_to_string(user_get_state(u)),
+ yes_no(u->stopping));
/* LEGACY: no-one reads RUNTIME= anymore, drop it at some point */
if (u->runtime_path)
@@ -151,10 +184,7 @@ static int user_save_internal(User *u) {
if (u->service_job)
fprintf(f, "SERVICE_JOB=%s\n", u->service_job);
- if (u->slice_job)
- fprintf(f, "SLICE_JOB=%s\n", u->slice_job);
#endif // 0
-
if (u->display)
fprintf(f, "DISPLAY=%s\n", u->display->id);
@@ -165,6 +195,10 @@ static int user_save_internal(User *u) {
u->timestamp.realtime,
u->timestamp.monotonic);
+ if (u->last_session_timestamp != USEC_INFINITY)
+ fprintf(f, "LAST_SESSION_TIMESTAMP=" USEC_FMT "\n",
+ u->last_session_timestamp);
+
if (u->sessions) {
Session *i;
bool first;
@@ -278,120 +312,96 @@ int user_save(User *u) {
if (!u->started)
return 0;
- return user_save_internal (u);
+ return user_save_internal(u);
}
int user_load(User *u) {
- _cleanup_free_ char *display = NULL, *realtime = NULL, *monotonic = NULL;
- Session *s = NULL;
+ _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL;
int r;
assert(u);
r = parse_env_file(NULL, u->state_file, NEWLINE,
#if 0 /// elogind neither supports service nor slice jobs
- "SERVICE_JOB", &u->service_job,
- "SLICE_JOB", &u->slice_job,
+ "SERVICE_JOB", &u->service_job,
#endif // 0
- "DISPLAY", &display,
- "REALTIME", &realtime,
- "MONOTONIC", &monotonic,
+ "STOPPING", &stopping,
+ "REALTIME", &realtime,
+ "MONOTONIC", &monotonic,
+ "LAST_SESSION_TIMESTAMP", &last_session_timestamp,
NULL);
- if (r < 0) {
- if (r == -ENOENT)
- return 0;
-
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
return log_error_errno(r, "Failed to read %s: %m", u->state_file);
- }
-
- if (display)
- s = hashmap_get(u->manager->sessions, display);
- if (s && s->display && display_is_local(s->display))
- u->display = s;
+ if (stopping) {
+ r = parse_boolean(stopping);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse 'STOPPING' boolean: %s", stopping);
+ else
+ u->stopping = r;
+ }
if (realtime)
- timestamp_deserialize(realtime, &u->timestamp.realtime);
+ (void) timestamp_deserialize(realtime, &u->timestamp.realtime);
if (monotonic)
- timestamp_deserialize(monotonic, &u->timestamp.monotonic);
+ (void) timestamp_deserialize(monotonic, &u->timestamp.monotonic);
+ if (last_session_timestamp)
+ (void) timestamp_deserialize(last_session_timestamp, &u->last_session_timestamp);
- return r;
+ return 0;
}
-static int user_start_service(User *u) {
-#if 0 /// elogind can not ask systemd via dbus to start user services
+#if 0 /// elogind neither spawns systemd --user nor suports systemd units and services.
+static void user_start_service(User *u) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- char *job;
int r;
assert(u);
+ /* Start the service containing the "systemd --user" instance (user@.service). Note that we don't explicitly
+ * start the per-user slice or the systemd-runtime-dir@.service instance, as those are pulled in both by
+ * user@.service and the session scopes as dependencies. */
+
u->service_job = mfree(u->service_job);
- r = manager_start_unit(
- u->manager,
- u->service,
- &error,
- &job);
+ r = manager_start_unit(u->manager, u->service, &error, &u->service_job);
if (r < 0)
- /* we don't fail due to this, let's try to continue */
- log_error_errno(r, "Failed to start user service, ignoring: %s", bus_error_message(&error, r));
- else
- u->service_job = job;
-#else
- assert(u);
-
- hashmap_put(u->manager->user_units, u->service, u);
-#endif // 0
-
- return 0;
+ log_warning_errno(r, "Failed to start user service '%s', ignoring: %s", u->service, bus_error_message(&error, r));
}
+#endif // 0
int user_start(User *u) {
- int r;
-
assert(u);
if (u->started && !u->stopping)
return 0;
- /*
- * If u->stopping is set, the user is marked for removal and the slice
- * and service stop-jobs are queued. We have to clear that flag before
- * queing the start-jobs again. If they succeed, the user object can be
- * re-used just fine (pid1 takes care of job-ordering and proper
- * restart), but if they fail, we want to force another user_stop() so
- * possibly pending units are stopped.
- * Note that we don't clear u->started, as we have no clue what state
- * the user is in on failure here. Hence, we pretend the user is
- * running so it will be properly taken down by GC. However, we clearly
- * return an error from user_start() in that case, so no further
- * reference to the user is taken.
- */
+ /* If u->stopping is set, the user is marked for removal and service stop-jobs are queued. We have to clear
+ * that flag before queing the start-jobs again. If they succeed, the user object can be re-used just fine
+ * (pid1 takes care of job-ordering and proper restart), but if they fail, we want to force another user_stop()
+ * so possibly pending units are stopped. */
u->stopping = false;
-#if 0 /// elogind has to prepare the XDG_RUNTIME_DIR by itself
if (!u->started)
log_debug("Starting services for new user %s.", u->name);
-#else
- if (!u->started) {
- log_debug("Starting services for new user %s.", u->name);
- r = user_runtime_dir("start", u);
- if (r < 0)
- return r;
- }
+
+#if 1 /// elogind has to prepare the XDG_RUNTIME_DIR by itself
+ int r;
+ r = user_runtime_dir("start", u);
+ if (r < 0)
+ return r;
#endif // 1
- /* Save the user data so far, because pam_systemd will read the
- * XDG_RUNTIME_DIR out of it while starting up systemd --user.
- * We need to do user_save_internal() because we have not
- * "officially" started yet. */
+ /* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it while starting up
+ * systemd --user. We need to do user_save_internal() because we have not "officially" started yet. */
user_save_internal(u);
- /* Spawn user systemd */
- r = user_start_service(u);
- if (r < 0)
- return r;
+#if 0 /// elogind does not spawn user instances of systemd
+ /* Start user@UID.service */
+ user_start_service(u);
+#endif // 0
if (!u->started) {
if (!dual_timestamp_is_set(&u->timestamp))
@@ -406,84 +416,59 @@ int user_start(User *u) {
return 0;
}
-#if 0 /// UNNEEDED by elogind
-static int user_stop_slice(User *u) {
+#if 0 /// elogind does not support user services and systemd units
+static void user_stop_service(User *u) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- char *job;
int r;
assert(u);
+ assert(u->service);
- r = manager_stop_unit(u->manager, u->slice, &error, &job);
- if (r < 0) {
- log_error("Failed to stop user slice: %s", bus_error_message(&error, r));
- return r;
- }
-
- free(u->slice_job);
- u->slice_job = job;
-
- return r;
-}
-
-static int user_stop_service(User *u) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- char *job;
- int r;
+ /* The reverse of user_start_service(). Note that we only stop user@UID.service here, and let StopWhenUnneeded=
+ * deal with the slice and the user-runtime-dir@.service instance. */
- assert(u);
-
- r = manager_stop_unit(u->manager, u->service, &error, &job);
- if (r < 0) {
- log_error("Failed to stop user service: %s", bus_error_message(&error, r));
- return r;
- }
+ u->service_job = mfree(u->service_job);
- free_and_replace(u->service_job, job);
- return r;
+ r = manager_stop_unit(u->manager, u->service, &error, &u->service_job);
+ if (r < 0)
+ log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s", u->service, bus_error_message(&error, r));
}
#endif // 0
int user_stop(User *u, bool force) {
Session *s;
- int r = 0, k;
+ int r = 0;
assert(u);
- /* Stop jobs have already been queued */
- if (u->stopping) {
+ /* This is called whenever we begin with tearing down a user record. It's called in two cases: explicit API
+ * request to do so via the bus (in which case 'force' is true) and automatically due to GC, if there's no
+ * session left pinning it (in which case 'force' is false). Note that this just initiates tearing down of the
+ * user, the User object will remain in memory until user_finalize() is called, see below. */
+
+ if (!u->started)
+ return 0;
+
+ if (u->stopping) { /* Stop jobs have already been queued */
user_save(u);
-#if 1 /// elogind must queue this user again
- user_add_to_gc_queue(u);
-#endif // 1
- return r;
+ return 0;
}
LIST_FOREACH(sessions_by_user, s, u->sessions) {
+ int k;
+
k = session_stop(s, force);
if (k < 0)
r = k;
}
- /* Kill systemd */
#if 0 /// elogind does not support service or slice jobs
- k = user_stop_service(u);
- if (k < 0)
- r = k;
-
- /* Kill cgroup */
- k = user_stop_slice(u);
- if (k < 0)
- r = k;
+ user_stop_service(u);
#endif // 0
u->stopping = true;
user_save(u);
-#if 1 /// elogind must queue this user again
- user_add_to_gc_queue(u);
-#endif // 1
-
return r;
}
@@ -493,6 +478,9 @@ int user_finalize(User *u) {
assert(u);
+ /* Called when the user is really ready to be freed, i.e. when all unit stop jobs and suchlike for it are
+ * done. This is called as a result of an earlier user_done() when all jobs are completed. */
+
if (u->started)
log_debug("User %s logged out.", u->name);
@@ -508,7 +496,6 @@ int user_finalize(User *u) {
if (k < 0)
r = k;
#endif // 1
-
/* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs
* are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to
* system components. Since enable RemoveIPC= globally for all users, we need to be a bit careful with such
@@ -521,7 +508,7 @@ int user_finalize(User *u) {
r = k;
}
- unlink(u->state_file);
+ (void) unlink(u->state_file);
user_add_to_gc_queue(u);
if (u->started) {
@@ -577,11 +564,44 @@ int user_check_linger_file(User *u) {
return -ENOMEM;
p = strjoina("/var/lib/elogind/linger/", cc);
+ if (access(p, F_OK) < 0) {
+ if (errno != ENOENT)
+ return -errno;
+
+ return false;
+ }
+
+ return true;
+}
+
+#if 0 /// elogind does not support systemd units
+static bool user_unit_active(User *u) {
+ const char *i;
+ int r;
+
+ assert(u->service);
+ assert(u->runtime_dir_service);
+ assert(u->slice);
+
+ FOREACH_STRING(i, u->service, u->runtime_dir_service, u->slice) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ r = manager_unit_is_active(u->manager, i, &error);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether unit '%s' is active, ignoring: %s", u->service, bus_error_message(&error, r));
+ if (r != 0)
+ return true;
+ }
- return access(p, F_OK) >= 0;
+ return false;
}
+#endif // 0
bool user_may_gc(User *u, bool drop_not_started) {
+#if 0 /// UNNEEDED by elogind
+ int r;
+#endif // 0
+
assert(u);
if (drop_not_started && !u->started)
@@ -590,15 +610,39 @@ bool user_may_gc(User *u, bool drop_not_started) {
if (u->sessions)
return false;
+ if (u->last_session_timestamp != USEC_INFINITY) {
+ /* All sessions have been closed. Let's see if we shall leave the user record around for a bit */
+
+ if (u->manager->user_stop_delay == USEC_INFINITY)
+ return false; /* Leave it around forever! */
+ if (u->manager->user_stop_delay > 0 &&
+ now(CLOCK_MONOTONIC) < usec_add(u->last_session_timestamp, u->manager->user_stop_delay))
+ return false; /* Leave it around for a bit longer. */
+ }
+
+ /* Is this a user that shall stay around forever ("linger")? Before we say "no" to GC'ing for lingering users, let's check
+ * if any of the three units that we maintain for this user is still around. If none of them is,
+ * there's no need to keep this user around even if lingering is enabled. */
+#if 0 /// elogind does not support systemd units
+ if (user_check_linger_file(u) > 0 && user_unit_active(u))
+#else
if (user_check_linger_file(u) > 0)
+#endif // 0
return false;
#if 0 /// elogind neither supports service nor slice jobs
- if (u->slice_job && manager_job_is_active(u->manager, u->slice_job))
- return false;
+ /* Check if our job is still pending */
+ if (u->service_job) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- if (u->service_job && manager_job_is_active(u->manager, u->service_job))
- return false;
+ r = manager_job_is_active(u->manager, u->service_job, &error);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", u->service_job, bus_error_message(&error, r));
+ if (r != 0)
+ return false;
+ }
+ /* Note that we don't care if the three units we manage for each user object are up or not, as we are managing
+ * their state rather than tracking it. */
#endif // 0
return true;
@@ -623,7 +667,7 @@ UserState user_get_state(User *u) {
return USER_CLOSING;
#if 0 /// elogind neither supports service nor slice jobs.
- if (!u->started || u->slice_job || u->service_job)
+ if (!u->started || u->service_job)
#else
if (!u->started)
#endif // 0
@@ -645,7 +689,11 @@ UserState user_get_state(User *u) {
return all_closing ? USER_CLOSING : USER_ONLINE;
}
+#if 0 /// elogind does not support systemd units
+ if (user_check_linger_file(u) > 0 && user_unit_active(u))
+#else
if (user_check_linger_file(u) > 0)
+#endif // 0
return USER_LINGERING;
return USER_CLOSING;
@@ -673,11 +721,10 @@ int user_kill(User *u, int signo) {
}
static bool elect_display_filter(Session *s) {
- /* Return true if the session is a candidate for the user’s ‘primary
- * session’ or ‘display’. */
+ /* Return true if the session is a candidate for the user’s ‘primary session’ or ‘display’. */
assert(s);
- return (s->class == SESSION_USER && !s->stopping);
+ return s->class == SESSION_USER && s->started && !s->stopping;
}
static int elect_display_compare(Session *s1, Session *s2) {
@@ -723,9 +770,8 @@ void user_elect_display(User *u) {
assert(u);
- /* This elects a primary session for each user, which we call
- * the "display". We try to keep the assignment stable, but we
- * "upgrade" to better choices. */
+ /* This elects a primary session for each user, which we call the "display". We try to keep the assignment
+ * stable, but we "upgrade" to better choices. */
log_debug("Electing new display for user %s", u->name);
LIST_FOREACH(sessions_by_user, s, u->sessions) {
@@ -741,6 +787,59 @@ void user_elect_display(User *u) {
}
}
+static int user_stop_timeout_callback(sd_event_source *es, uint64_t usec, void *userdata) {
+ User *u = userdata;
+
+ assert(u);
+ user_add_to_gc_queue(u);
+
+ return 0;
+}
+
+void user_update_last_session_timer(User *u) {
+ int r;
+
+ assert(u);
+
+ if (u->sessions) {
+ /* There are sessions, turn off the timer */
+ u->last_session_timestamp = USEC_INFINITY;
+ u->timer_event_source = sd_event_source_unref(u->timer_event_source);
+ return;
+ }
+
+ if (u->last_session_timestamp != USEC_INFINITY)
+ return; /* Timer already started */
+
+ u->last_session_timestamp = now(CLOCK_MONOTONIC);
+
+ assert(!u->timer_event_source);
+
+ if (u->manager->user_stop_delay == 0 || u->manager->user_stop_delay == USEC_INFINITY)
+ return;
+
+ if (sd_event_get_state(u->manager->event) == SD_EVENT_FINISHED) {
+ log_debug("Not allocating user stop timeout, since we are already exiting.");
+ return;
+ }
+
+ r = sd_event_add_time(u->manager->event,
+ &u->timer_event_source,
+ CLOCK_MONOTONIC,
+ usec_add(u->last_session_timestamp, u->manager->user_stop_delay), 0,
+ user_stop_timeout_callback, u);
+ if (r < 0)
+ log_warning_errno(r, "Failed to enqueue user stop event source, ignoring: %m");
+
+ if (DEBUG_LOGGING) {
+ char s[FORMAT_TIMESPAN_MAX];
+
+ log_debug("Last session of user '%s' logged out, terminating user context in %s.",
+ u->name,
+ format_timespan(s, sizeof(s), u->manager->user_stop_delay, USEC_PER_MSEC));
+ }
+}
+
static const char* const user_state_table[_USER_STATE_MAX] = {
[USER_OFFLINE] = "offline",
[USER_OPENING] = "opening",
@@ -813,7 +912,7 @@ int config_parse_compat_user_tasks_max(
log_syntax(unit, LOG_NOTICE, filename, line, 0,
"Support for option %s= has been removed.",
lvalue);
- log_info("Hint: try creating /etc/elogind/system/user-.slice/50-limits.conf with:\n"
+ log_info("Hint: try creating /etc/elogind/system/user-.slice.d/50-limits.conf with:\n"
" [Slice]\n"
" TasksMax=%s",
rvalue);
diff --git a/src/login/logind-user.h b/src/login/logind-user.h
index afb6f4b..ca404f3 100644
--- a/src/login/logind-user.h
+++ b/src/login/logind-user.h
@@ -23,29 +23,36 @@ struct User {
uid_t uid;
gid_t gid;
char *name;
+ char *home;
char *state_file;
char *runtime_path;
- char *slice;
- char *service;
+
+ char *slice; /* user-UID.slice */
+ char *service; /* user@UID.service */
+ char *runtime_dir_service; /* user-runtime-dir@UID.service */
#if 0 /// UNNEEDED by elogind
char *service_job;
- char *slice_job;
#endif // 0
Session *display;
- dual_timestamp timestamp;
+ dual_timestamp timestamp; /* When this User object was 'started' the first time */
+ usec_t last_session_timestamp; /* When the number of sessions of this user went from 1 to 0 the last time */
+
+ /* Set up when the last session of the user logs out */
+ sd_event_source *timer_event_source;
bool in_gc_queue:1;
- bool started:1;
- bool stopping:1;
+
+ bool started:1; /* Whenever the user being started, has been started or is being stopped again. */
+ bool stopping:1; /* Whenever the user is being stopped or has been stopped. */
LIST_HEAD(Session, sessions);
LIST_FIELDS(User, gc_queue);
};
-int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name);
+int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name, const char *home);
User *user_free(User *u);
DEFINE_TRIVIAL_CLEANUP_FUNC(User *, user_free);
@@ -62,6 +69,7 @@ int user_load(User *u);
int user_kill(User *u, int signo);
int user_check_linger_file(User *u);
void user_elect_display(User *u);
+void user_update_last_session_timer(User *u);
extern const sd_bus_vtable user_vtable[];
int user_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);
diff --git a/src/login/logind-utmp.c b/src/login/logind-utmp.c
index f181476..d3489f5 100644
--- a/src/login/logind-utmp.c
+++ b/src/login/logind-utmp.c
@@ -61,7 +61,7 @@ bool logind_wall_tty_filter(const char *tty, void *userdata) {
static int warn_wall(Manager *m, usec_t n) {
char date[FORMAT_TIMESTAMP_MAX] = {};
- _cleanup_free_ char *l = NULL;
+ _cleanup_free_ char *l = NULL, *username = NULL;
usec_t left;
int r;
@@ -83,8 +83,8 @@ static int warn_wall(Manager *m, usec_t n) {
return 0;
}
- utmp_wall(l, uid_to_name(m->scheduled_shutdown_uid),
- m->scheduled_shutdown_tty, logind_wall_tty_filter, m);
+ username = uid_to_name(m->scheduled_shutdown_uid);
+ utmp_wall(l, username, m->scheduled_shutdown_tty, logind_wall_tty_filter, m);
return 1;
}
diff --git a/src/login/logind.c b/src/login/logind.c
index c7acb05..558733b 100644
--- a/src/login/logind.c
+++ b/src/login/logind.c
@@ -45,28 +45,35 @@ static int manager_new(Manager **ret) {
assert(ret);
- m = new0(Manager, 1);
+ m = new(Manager, 1);
if (!m)
return -ENOMEM;
- m->console_active_fd = -1;
-#if 0 /// UNNEEDED by elogind
- m->reserve_vt_fd = -1;
+ *m = (Manager) {
+ .console_active_fd = -1,
+#if 0 /// elogind does not support autospawning of vts
+ .reserve_vt_fd = -1,
#endif // 0
+ };
m->idle_action_not_before_usec = now(CLOCK_MONOTONIC);
m->devices = hashmap_new(&string_hash_ops);
m->seats = hashmap_new(&string_hash_ops);
m->sessions = hashmap_new(&string_hash_ops);
+ m->sessions_by_leader = hashmap_new(NULL);
m->users = hashmap_new(NULL);
m->inhibitors = hashmap_new(&string_hash_ops);
m->buttons = hashmap_new(&string_hash_ops);
+#if 0 /// elogind does not support units
m->user_units = hashmap_new(&string_hash_ops);
m->session_units = hashmap_new(&string_hash_ops);
- if (!m->devices || !m->seats || !m->sessions || !m->users || !m->inhibitors || !m->buttons || !m->user_units || !m->session_units)
+ if (!m->devices || !m->seats || !m->sessions || !m->sessions_by_leader || !m->users || !m->inhibitors || !m->buttons || !m->user_units || !m->session_units)
+#else
+ if (!m->devices || !m->seats || !m->sessions || !m->sessions_by_leader || !m->users || !m->inhibitors || !m->buttons)
+#endif // 0
return -ENOMEM;
#if 1 /// elogind needs some more data
@@ -82,6 +89,7 @@ static int manager_new(Manager **ret) {
if (r < 0)
return r;
+#if 0 /// elogind uses its own signal handler, installed at elogind_manager_startup()
r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
if (r < 0)
return r;
@@ -89,6 +97,7 @@ static int manager_new(Manager **ret) {
r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
if (r < 0)
return r;
+#endif // 0
(void) sd_event_set_watchdog(m->event, true);
@@ -130,12 +139,15 @@ static Manager* manager_unref(Manager *m) {
hashmap_free(m->devices);
hashmap_free(m->seats);
hashmap_free(m->sessions);
+ hashmap_free(m->sessions_by_leader);
hashmap_free(m->users);
hashmap_free(m->inhibitors);
hashmap_free(m->buttons);
+#if 0 /// elogind does not support systemd units.
hashmap_free(m->user_units);
hashmap_free(m->session_units);
+#endif // 0
sd_event_source_unref(m->idle_action_event_source);
sd_event_source_unref(m->inhibit_timeout_source);
@@ -150,6 +162,10 @@ static Manager* manager_unref(Manager *m) {
sd_event_source_unref(m->udev_button_event_source);
sd_event_source_unref(m->lid_switch_ignore_event_source);
+#if ENABLE_UTMP
+ sd_event_source_unref(m->utmp_event_source);
+#endif
+
safe_close(m->console_active_fd);
udev_monitor_unref(m->udev_seat_monitor);
@@ -852,28 +868,28 @@ static int manager_connect_console(Manager *m) {
assert(m);
assert(m->console_active_fd < 0);
- /* On certain architectures (S390 and Xen, and containers),
- /dev/tty0 does not exist, so don't fail if we can't open
- it. */
+ /* On certain systems (such as S390, Xen, and containers) /dev/tty0 does not exist (as there is no VC), so
+ * don't fail if we can't open it. */
+
if (access("/dev/tty0", F_OK) < 0)
return 0;
m->console_active_fd = open("/sys/class/tty/tty0/active", O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (m->console_active_fd < 0) {
- /* On some systems the device node /dev/tty0 may exist
- * even though /sys/class/tty/tty0 does not. */
- if (errno == ENOENT)
+ /* On some systems /dev/tty0 may exist even though /sys/class/tty/tty0 does not. These are broken, but
+ * common. Let's complain but continue anyway. */
+ if (errno == ENOENT) {
+ log_warning_errno(errno, "System has /dev/tty0 but not /sys/class/tty/tty0/active which is broken, ignoring: %m");
return 0;
+ }
return log_error_errno(errno, "Failed to open /sys/class/tty/tty0/active: %m");
}
r = sd_event_add_io(m->event, &m->console_active_event_source, m->console_active_fd, 0, manager_dispatch_console, m);
- if (r < 0) {
- log_error("Failed to watch foreground console");
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch foreground console: %m");
/*
* SIGRTMIN is used as global VT-release signal, SIGRTMIN + 1 is used
@@ -892,7 +908,7 @@ static int manager_connect_console(Manager *m) {
r = sd_event_add_signal(m->event, NULL, SIGRTMIN, manager_vt_switch, m);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to subscribe to signal: %m");
return 0;
}
@@ -1018,13 +1034,13 @@ static void manager_gc(Manager *m, bool drop_not_started) {
/* First, if we are not closing yet, initiate stopping */
if (session_may_gc(session, drop_not_started) &&
session_get_state(session) != SESSION_CLOSING)
- session_stop(session, false);
+ (void) session_stop(session, false);
/* Normally, this should make the session referenced
* again, if it doesn't then let's get rid of it
* immediately */
if (session_may_gc(session, drop_not_started)) {
- session_finalize(session);
+ (void) session_finalize(session);
session_free(session);
}
}
@@ -1035,11 +1051,11 @@ static void manager_gc(Manager *m, bool drop_not_started) {
/* First step: queue stop jobs */
if (user_may_gc(user, drop_not_started))
- user_stop(user, false);
+ (void) user_stop(user, false);
/* Second step: finalize user */
if (user_may_gc(user, drop_not_started)) {
- user_finalize(user);
+ (void) user_finalize(user);
user_free(user);
}
}
@@ -1137,9 +1153,15 @@ static int manager_startup(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to register SIGHUP handler: %m");
-#if 1 /// elogind needs some extra preparations before connecting...
- elogind_manager_startup(m);
+#if 1 /// install elogind specific signal handlers
+ r = elogind_manager_startup(m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register elogind signal handlers: %m");
#endif // 1
+
+ /* Connect to utmp */
+ manager_connect_utmp(m);
+
/* Connect to console */
r = manager_connect_console(m);
if (r < 0)
@@ -1197,15 +1219,18 @@ static int manager_startup(Manager *m) {
manager_reserve_vt(m);
#endif // 0
+ /* Read in utmp if it exists */
+ manager_read_utmp(m);
+
/* And start everything */
HASHMAP_FOREACH(seat, m->seats, i)
- seat_start(seat);
+ (void) seat_start(seat);
HASHMAP_FOREACH(user, m->users, i)
- user_start(user);
+ (void) user_start(user);
HASHMAP_FOREACH(session, m->sessions, i)
- session_start(session, NULL);
+ (void) session_start(session, NULL, NULL);
HASHMAP_FOREACH(inhibitor, m->inhibitors, i)
inhibitor_start(inhibitor);
@@ -1307,7 +1332,11 @@ int main(int argc, char *argv[]) {
return log_error_errno(r, "Failed to create /run/systemd/machines : %m");
#endif // 0
+#if 0 /// elogind also blocks SIGQUIT, and installs a signal handler for it
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGHUP, SIGTERM, SIGINT, -1) >= 0);
+#else
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGHUP, SIGTERM, SIGINT, SIGQUIT, -1) >= 0);
+#endif // 0
r = manager_new(&m);
if (r < 0) {
diff --git a/src/login/logind.h b/src/login/logind.h
index 3f17d5d..6db015e 100644
--- a/src/login/logind.h
+++ b/src/login/logind.h
@@ -37,6 +37,7 @@ struct Manager {
Hashmap *devices;
Hashmap *seats;
Hashmap *sessions;
+ Hashmap *sessions_by_leader;
Hashmap *users;
Hashmap *inhibitors;
Hashmap *buttons;
@@ -54,6 +55,10 @@ struct Manager {
sd_event_source *udev_vcsa_event_source;
sd_event_source *udev_button_event_source;
+#if ENABLE_UTMP
+ sd_event_source *utmp_event_source;
+#endif
+
#if 0 /// elogind does not support autospawning of vts
int console_active_fd;
@@ -89,10 +94,13 @@ struct Manager {
unsigned long session_counter;
unsigned long inhibit_counter;
+#if 0 /// elogind does not support units
Hashmap *session_units;
Hashmap *user_units;
+#endif // 0
usec_t inhibit_delay_max;
+ usec_t user_stop_delay;
/* If an action is currently being executed or is delayed,
* this is != 0 and encodes what is being done */
@@ -171,7 +179,7 @@ int manager_add_device(Manager *m, const char *sysfs, bool master, Device **_dev
int manager_add_button(Manager *m, const char *name, Button **_button);
int manager_add_seat(Manager *m, const char *id, Seat **_seat);
int manager_add_session(Manager *m, const char *id, Session **_session);
-int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, User **_user);
+int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, const char *home, User **_user);
int manager_add_user_by_name(Manager *m, const char *name, User **_user);
int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user);
int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **_inhibitor);
@@ -194,6 +202,10 @@ bool manager_is_docked_or_external_displays(Manager *m);
bool manager_is_on_external_power(void);
bool manager_all_buttons_ignored(Manager *m);
+int manager_read_utmp(Manager *m);
+void manager_connect_utmp(Manager *m);
+void manager_reconnect_utmp(Manager *m);
+
extern const sd_bus_vtable manager_vtable[];
#if 0 /// UNNEEDED by elogind
@@ -212,13 +224,13 @@ int bus_manager_shutdown_or_sleep_now_or_later(Manager *m, HandleAction action,
int manager_send_changed(Manager *manager, const char *property, ...) _sentinel_;
#if 0 /// UNNEEDED by elogind
-int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, const char *after, const char *after2, sd_bus_message *more_properties, sd_bus_error *error, char **job);
+int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, char **wants, char **after, const char *requires_mounts_for, sd_bus_message *more_properties, sd_bus_error *error, char **job);
int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job);
int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job);
int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *error);
int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, sd_bus_error *error);
-int manager_unit_is_active(Manager *manager, const char *unit);
-int manager_job_is_active(Manager *manager, const char *path);
+int manager_unit_is_active(Manager *manager, const char *unit, sd_bus_error *error);
+int manager_job_is_active(Manager *manager, const char *path, sd_bus_error *error);
#endif // 0
/* gperf lookup function */
diff --git a/src/login/meson.build b/src/login/meson.build
index 0eac0d3..81f39e1 100644
--- a/src/login/meson.build
+++ b/src/login/meson.build
@@ -83,7 +83,6 @@ loginctl_sources = files('''
#if 0 /// UNNEEDED by elogind
# user_runtime_dir_sources = files('''
# user-runtime-dir.c
-# logind.h
# '''.split())
#endif // 0
@@ -125,10 +124,6 @@ loginctl_sources += files('''
install_data('70-power-switch.rules', install_dir : udevrulesdir)
- if conf.get('HAVE_ACL') == 1
- install_data('70-uaccess.rules', install_dir : udevrulesdir)
- endif
-
seat_rules = configure_file(
input : '71-seat.rules.in',
output : '71-seat.rules',
@@ -142,6 +137,14 @@ loginctl_sources += files('''
output : '73-seat-late.rules.m4',
configuration : substs)
#endif // 1
+ custom_target(
+ '70-uaccess.rules',
+ input : '70-uaccess.rules.m4',
+ output: '70-uaccess.rules',
+ command : [meson_apply_m4, config_h, '@INPUT@'],
+ capture : true,
+ install : conf.get('HAVE_ACL') == 1,
+ install_dir : udevrulesdir)
custom_target(
'73-seat-late.rules',
diff --git a/src/login/org.freedesktop.login1.policy b/src/login/org.freedesktop.login1.policy
index f1d1f95..78bee24 100644
--- a/src/login/org.freedesktop.login1.policy
+++ b/src/login/org.freedesktop.login1.policy
@@ -343,7 +343,7 @@
<defaults>
<allow_any>auth_admin_keep</allow_any>
<allow_inactive>auth_admin_keep</allow_inactive>
- <allow_active>auth_admin_keep</allow_active>
+ <allow_active>yes</allow_active>
</defaults>
</action>
diff --git a/src/login/pam_elogind.c b/src/login/pam_elogind.c
index 88c5705..c4550d3 100644
--- a/src/login/pam_elogind.c
+++ b/src/login/pam_elogind.c
@@ -130,7 +130,7 @@ static int get_seat_from_display(const char *display, const char **seat, uint32_
r = socket_from_display(display, &p);
if (r < 0)
return r;
- strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
+ strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path));
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (fd < 0)
@@ -160,40 +160,6 @@ static int get_seat_from_display(const char *display, const char **seat, uint32_
return 0;
}
-static int export_legacy_dbus_address(
- pam_handle_t *handle,
- uid_t uid,
- const char *runtime) {
-
- _cleanup_free_ char *s = NULL;
- int r = PAM_BUF_ERR;
-
- /* FIXME: We *really* should move the access() check into the
- * daemons that spawn dbus-daemon, instead of forcing
- * DBUS_SESSION_BUS_ADDRESS= here. */
-
- s = strjoin(runtime, "/bus");
- if (!s)
- goto error;
-
- if (access(s, F_OK) < 0)
- return PAM_SUCCESS;
-
- s = mfree(s);
- if (asprintf(&s, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0)
- goto error;
-
- r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
- if (r != PAM_SUCCESS)
- goto error;
-
- return PAM_SUCCESS;
-
-error:
- pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
- return r;
-}
-
static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
uint64_t val;
int r;
@@ -274,6 +240,36 @@ static int append_session_cg_weight(pam_handle_t *handle, sd_bus_message *m, con
return 0;
}
+static bool validate_runtime_directory(pam_handle_t *handle, const char *path, uid_t uid) {
+ struct stat st;
+
+ assert(path);
+
+ /* Just some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually set
+ * up properly for us. */
+
+ if (lstat(path, &st) < 0) {
+ pam_syslog(handle, LOG_ERR, "Failed to stat() runtime directory '%s': %s", path, strerror(errno));
+ goto fail;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path);
+ goto fail;
+ }
+
+ if (st.st_uid != uid) {
+ pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid);
+ goto fail;
+ }
+
+ return true;
+
+fail:
+ pam_syslog(handle, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order.");
+ return false;
+}
+
_public_ PAM_EXTERN int pam_sm_open_session(
pam_handle_t *handle,
int flags,
@@ -334,16 +330,14 @@ _public_ PAM_EXTERN int pam_sm_open_session(
if (asprintf(&rt, "/run/user/"UID_FMT, pw->pw_uid) < 0)
return PAM_BUF_ERR;
- r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
- if (r != PAM_SUCCESS) {
- pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
- return r;
+ if (validate_runtime_directory(handle, rt, pw->pw_uid)) {
+ r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
+ if (r != PAM_SUCCESS) {
+ pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
+ return r;
+ }
}
- r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
- if (r != PAM_SUCCESS)
- return r;
-
return PAM_SUCCESS;
}
@@ -381,28 +375,32 @@ _public_ PAM_EXTERN int pam_sm_open_session(
tty = strempty(tty);
if (strchr(tty, ':')) {
- /* A tty with a colon is usually an X11 display,
- * placed there to show up in utmp. We rearrange
- * things and don't pretend that an X display was a
- * tty. */
-
+ /* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things
+ * and don't pretend that an X display was a tty. */
if (isempty(display))
display = tty;
tty = NULL;
+
} else if (streq(tty, "cron")) {
- /* cron has been setting PAM_TTY to "cron" for a very
- * long time and it probably shouldn't stop doing that
- * for compatibility reasons. */
+ /* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but
+ * probably because it wants to set it to something as pam_time/pam_access/… require PAM_TTY to be set
+ * (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked
+ * off processes.) */
type = "unspecified";
class = "background";
tty = NULL;
+
} else if (streq(tty, "ssh")) {
- /* ssh has been setting PAM_TTY to "ssh" for a very
- * long time and probably shouldn't stop doing that
- * for compatibility reasons. */
+ /* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further
+ * details look for "PAM_TTY_KLUDGE" in the openssh sources). */
type ="tty";
class = "user";
- tty = NULL;
+ tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually
+ * associated with a pty — won't be tracked by their tty in logind. This is because ssh
+ * does the PAM session registration early for new connections, and registers a pty only
+ * much later (this is because it doesn't know yet if it needs one at all, as whether to
+ * register a pty or not is negotiated much later in the protocol). */
+
} else
/* Chop off leading /dev prefix that some clients specify, but others do not. */
tty = skip_dev_prefix(tty);
@@ -472,7 +470,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
r = sd_bus_message_append(m, "uusssssussbss",
(uint32_t) pw->pw_uid,
- (uint32_t) getpid_cached(),
+ 0,
service,
type,
class,
@@ -561,15 +559,13 @@ _public_ PAM_EXTERN int pam_sm_open_session(
* in privileged apps clobbering the runtime directory
* unnecessarily. */
- r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
- if (r != PAM_SUCCESS) {
- pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
- return r;
+ if (validate_runtime_directory(handle, runtime_path, pw->pw_uid)) {
+ r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
+ if (r != PAM_SUCCESS) {
+ pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
+ return r;
+ }
}
-
- r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
- if (r != PAM_SUCCESS)
- return r;
}
if (!isempty(seat)) {
diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c
index 3cd5afe..aff039b 100644
--- a/src/login/user-runtime-dir.c
+++ b/src/login/user-runtime-dir.c
@@ -3,13 +3,16 @@
#include <stdint.h>
#include <sys/mount.h>
+//#include "sd-bus.h"
+
+//#include "bus-error.h"
#include "fs-util.h"
#include "label.h"
-//#include "logind.h"
#include "mkdir.h"
#include "mount-util.h"
#include "path-util.h"
#include "rm-rf.h"
+//#include "selinux-util.h"
#include "smack-util.h"
#include "stdio-util.h"
#include "string-util.h"
@@ -19,22 +22,29 @@
#include "user-runtime-dir.h"
#if 0 /// UNNEEDED by elogind
-static int gather_configuration(size_t *runtime_dir_size) {
- Manager m = {};
+static int acquire_runtime_dir_size(uint64_t *ret) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
int r;
- manager_reset_config(&m);
+ r = sd_bus_default_system(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to system bus: %m");
- r = manager_parse_config_file(&m);
+ r = sd_bus_get_property_trivial(bus, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "RuntimeDirectorySize", &error, 't', ret);
if (r < 0)
- log_warning_errno(r, "Failed to parse logind.conf: %m");
+ return log_error_errno(r, "Failed to acquire runtime directory size: %s", bus_error_message(&error, r));
- *runtime_dir_size = m.runtime_dir_size;
return 0;
}
#endif // 0
-static int user_mkdir_runtime_path(const char *runtime_path, uid_t uid, gid_t gid, size_t runtime_dir_size) {
+static int user_mkdir_runtime_path(
+ const char *runtime_path,
+ uid_t uid,
+ gid_t gid,
+ uint64_t runtime_dir_size) {
+
int r;
assert(runtime_path);
@@ -52,10 +62,10 @@ static int user_mkdir_runtime_path(const char *runtime_path, uid_t uid, gid_t gi
char options[sizeof("mode=0700,uid=,gid=,size=,smackfsroot=*")
+ DECIMAL_STR_MAX(uid_t)
+ DECIMAL_STR_MAX(gid_t)
- + DECIMAL_STR_MAX(size_t)];
+ + DECIMAL_STR_MAX(uint64_t)];
xsprintf(options,
- "mode=0700,uid=" UID_FMT ",gid=" GID_FMT ",size=%zu%s",
+ "mode=0700,uid=" UID_FMT ",gid=" GID_FMT ",size=%" PRIu64 "%s",
uid, gid, runtime_dir_size,
mac_smack_use() ? ",smackfsroot=*" : "");
@@ -99,27 +109,42 @@ static int user_remove_runtime_path(const char *runtime_path) {
r = rm_rf(runtime_path, 0);
if (r < 0)
- log_error_errno(r, "Failed to remove runtime directory %s (before unmounting): %m", runtime_path);
+ log_debug_errno(r, "Failed to remove runtime directory %s (before unmounting), ignoring: %m", runtime_path);
- /* Ignore cases where the directory isn't mounted, as that's
- * quite possible, if we lacked the permissions to mount
- * something */
+ /* Ignore cases where the directory isn't mounted, as that's quite possible, if we lacked the permissions to
+ * mount something */
r = umount2(runtime_path, MNT_DETACH);
if (r < 0 && !IN_SET(errno, EINVAL, ENOENT))
- log_error_errno(errno, "Failed to unmount user runtime directory %s: %m", runtime_path);
+ log_debug_errno(errno, "Failed to unmount user runtime directory %s, ignoring: %m", runtime_path);
r = rm_rf(runtime_path, REMOVE_ROOT);
- if (r < 0)
- log_error_errno(r, "Failed to remove runtime directory %s (after unmounting): %m", runtime_path);
+ if (r < 0 && r != -ENOENT)
+ return log_error_errno(r, "Failed to remove runtime directory %s (after unmounting): %m", runtime_path);
- return r;
+ return 0;
}
#if 0 /// having a User instance, elogind can ask its manager directly.
-static int do_mount(const char *runtime_path, uid_t uid, gid_t gid) {
- size_t runtime_dir_size;
+static int do_mount(const char *user) {
+ char runtime_path[sizeof("/run/user") + DECIMAL_STR_MAX(uid_t)];
+ uint64_t runtime_dir_size;
+ uid_t uid;
+ gid_t gid;
+ int r;
- assert_se(gather_configuration(&runtime_dir_size) == 0);
+ r = get_user_creds(&user, &uid, &gid, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r,
+ r == -ESRCH ? "No such user \"%s\"" :
+ r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
+ : "Failed to look up user \"%s\": %m",
+ user);
+
+ r = acquire_runtime_dir_size(&runtime_dir_size);
+ if (r < 0)
+ return r;
+
+ xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
#else
static int do_mount(const char *runtime_path, size_t runtime_dir_size, uid_t uid, gid_t gid) {
#endif // 0
@@ -128,17 +153,35 @@ static int do_mount(const char *runtime_path, size_t runtime_dir_size, uid_t uid
return user_mkdir_runtime_path(runtime_path, uid, gid, runtime_dir_size);
}
+#if 0 /// elogind already has the runtime path
+static int do_umount(const char *user) {
+ char runtime_path[sizeof("/run/user") + DECIMAL_STR_MAX(uid_t)];
+ uid_t uid;
+ int r;
+
+ /* The user may be already removed. So, first try to parse the string by parse_uid(),
+ * and if it fails, fallback to get_user_creds().*/
+ if (parse_uid(user, &uid) < 0) {
+ r = get_user_creds(&user, &uid, NULL, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r,
+ r == -ESRCH ? "No such user \"%s\"" :
+ r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
+ : "Failed to look up user \"%s\": %m",
+ user);
+ }
+
+ xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
+#else
static int do_umount(const char *runtime_path) {
+#endif // 0
+
log_debug("Will remove %s", runtime_path);
return user_remove_runtime_path(runtime_path);
}
#if 0 /// elogind does this internally as we have no unit chain being init.
int main(int argc, char *argv[]) {
- const char *user;
- uid_t uid;
- gid_t gid;
- char runtime_path[sizeof("/run/user") + DECIMAL_STR_MAX(uid_t)];
int r;
log_parse_environment();
@@ -153,24 +196,18 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE;
}
- umask(0022);
-
- user = argv[2];
- r = get_user_creds(&user, &uid, &gid, NULL, NULL);
+ r = mac_selinux_init();
if (r < 0) {
- log_error_errno(r,
- r == -ESRCH ? "No such user \"%s\"" :
- r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
- : "Failed to look up user \"%s\": %m",
- user);
+ log_error_errno(r, "Could not initialize labelling: %m\n");
return EXIT_FAILURE;
}
- xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
+
+ umask(0022);
if (streq(argv[1], "start"))
- r = do_mount(runtime_path, uid, gid);
+ r = do_mount(argv[2]);
else if (streq(argv[1], "stop"))
- r = do_umount(runtime_path);
+ r = do_umount(argv[2]);
else
assert_not_reached("Unknown verb!");
diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c
index c3be787..c03fe2b 100644
--- a/src/shared/bus-util.c
+++ b/src/shared/bus-util.c
@@ -941,7 +941,7 @@ int bus_message_print_all_properties(
return log_oom();
r = set_put(*found_properties, name);
- if (r < 0 && r != EEXIST)
+ if (r < 0 && r != -EEXIST)
return log_oom();
}
@@ -1299,10 +1299,10 @@ int bus_connect_transport(BusTransport transport, const char *host, bool user, s
switch (transport) {
case BUS_TRANSPORT_LOCAL:
-#if 0 /// elogind does not support a user bus
if (user)
r = sd_bus_default_user(&bus);
else {
+#if 0 /// elogind is never used with systemd. Avoid useless check.
if (sd_booted() <= 0) {
/* Print a friendly message when the local system is actually not running systemd as PID 1. */
log_error("System has not been booted with systemd as init system (PID 1). Can't operate.");
@@ -1311,9 +1311,7 @@ int bus_connect_transport(BusTransport transport, const char *host, bool user, s
}
#endif // 0
r = sd_bus_default_system(&bus);
-#if 0 /// No closing bracket with elogind... Ain't we simple? ;-)
}
-#endif // 0
break;
case BUS_TRANSPORT_REMOTE:
@@ -1356,12 +1354,14 @@ int bus_connect_transport_systemd(BusTransport transport, const char *host, bool
if (user)
r = bus_connect_user_systemd(bus);
else {
+#if 0 /// elogind is never used with systemd. Avoid useless check.
if (sd_booted() <= 0) {
/* Print a friendly message when the local system is actually not running systemd as PID 1. */
log_error("System has not been booted with systemd as init system (PID 1). Can't operate.");
return -EHOSTDOWN;
}
+#endif // 0
r = bus_connect_system_systemd(bus);
}
break;
@@ -1484,12 +1484,10 @@ int bus_log_parse_error(int r) {
return log_error_errno(r, "Failed to parse bus message: %m");
}
-#if 0 /// UNNEEDED by elogind
int bus_log_create_error(int r) {
return log_error_errno(r, "Failed to create bus message: %m");
}
-#endif // 0
#if 0 /// UNNEEDED by elogind
/**
* bus_path_encode_unique() - encode unique object path
@@ -1758,7 +1756,7 @@ static void request_name_destroy_callback(void *userdata) {
assert(data);
assert(data->n_ref > 0);
- log_info("%s n_ref=%u", __func__, data->n_ref);
+ log_debug("%s n_ref=%u", __func__, data->n_ref);
data->n_ref--;
if (data->n_ref == 0)
diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h
index e7231be..153ea12 100644
--- a/src/shared/bus-util.h
+++ b/src/shared/bus-util.h
@@ -37,10 +37,10 @@ enum {
int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata);
-#if 0 /// UNNEEDED by elogind
-#endif // 0
int bus_message_map_all_properties(sd_bus_message *m, const struct bus_properties_map *map, unsigned flags, sd_bus_error *error, void *userdata);
+#if 0 /// UNNEEDED by elogind
int bus_message_map_properties_changed(sd_bus_message *m, const struct bus_properties_map *map, unsigned flags, sd_bus_error *error, void *userdata);
+#endif // 0
int bus_map_all_properties(sd_bus *bus, const char *destination, const char *path, const struct bus_properties_map *map,
unsigned flags, sd_bus_error *error, sd_bus_message **reply, void *userdata);
diff --git a/src/shared/enable-mempool.c b/src/shared/enable-mempool.c
new file mode 100644
index 0000000..a571b43
--- /dev/null
+++ b/src/shared/enable-mempool.c
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "mempool.h"
+
+const bool mempool_use_allowed = true;
diff --git a/src/shared/meson.build b/src/shared/meson.build
index b215e26..944909c 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -37,6 +37,7 @@
# dropin.h
# efivars.c
# efivars.h
+# enable-mempool.c
# fdset.c
# fdset.h
# firewall-util.h
@@ -64,6 +65,7 @@
# machine-image.h
# machine-pool.c
# machine-pool.h
+# module-util.h
# nsflags.c
# nsflags.h
# output-mode.c
@@ -112,12 +114,15 @@ shared_sources = '''
clean-ipc.h
conf-parser.c
conf-parser.h
+ enable-mempool.c
musl_missing.c
musl_missing.h
nsflags.c
nsflags.h
output-mode.c
output-mode.h
+ qsort_r_missing.c
+ qsort_r_missing.h
sleep-config.c
sleep-config.h
spawn-polkit-agent.c
@@ -148,6 +153,11 @@ endif
#endif // 0
libshared_name = 'elogind-shared-@0@'.format(meson.project_version())
+#if 0 /// elogind does not play around with kernel modules. Not its job.
+# if conf.get('HAVE_KMOD') == 1
+# shared_sources += files('module-util.c')
+# endif
+#endif // 0
#if 0 /// elogind doesn't need all this
# libshared_deps = [threads,
@@ -157,6 +167,7 @@ libshared_name = 'elogind-shared-@0@'.format(meson.project_version())
# libcryptsetup,
# libgcrypt,
# libiptc,
+# libkmod,
# libseccomp,
# libselinux,
# libidn,
diff --git a/src/shared/musl_missing.c b/src/shared/musl_missing.c
index 0f8b4d1..a34e4ea 100644
--- a/src/shared/musl_missing.c
+++ b/src/shared/musl_missing.c
@@ -26,17 +26,17 @@
#if HAVE_PROGRAM_INVOCATION_NAME == 0
char *program_invocation_name = NULL;
char *program_invocation_short_name = NULL;
-#endif // libc does not provide these variables
-const char *program_arg_name = NULL;
+const char *program_arg_name = NULL; /* Helper */
+#endif // libc does not provide program_invocation_[short_]name
#include "musl_missing.h"
+#if HAVE_PROGRAM_INVOCATION_NAME == 0
static void elogind_free_program_name(void) {
-
- if (program_invocation_name && (program_invocation_name != program_arg_name) && strlen(program_invocation_name))
+ if (program_invocation_name)
program_invocation_name = mfree(program_invocation_name);
- if (program_invocation_short_name && (program_invocation_short_name != program_arg_name) && strlen(program_invocation_short_name))
+ if (program_invocation_short_name)
program_invocation_short_name = mfree(program_invocation_short_name);
}
@@ -55,8 +55,11 @@ void elogind_set_program_name(const char* pcall) {
program_invocation_name = strdup(program_arg_name);
if (NULL == program_invocation_short_name)
program_invocation_short_name = strdup(basename(program_arg_name));
-#if HAVE_PROGRAM_INVOCATION_NAME == 0
atexit(elogind_free_program_name);
-#endif // libc does not provide these variables
}
+#else
+void elogind_set_program_name(const char* pcall) {
+ assert(pcall && pcall[0]);
+}
+#endif // libc does not provide program_invocation_[short_]name
diff --git a/src/shared/musl_missing.h b/src/shared/musl_missing.h
index cf5a014..6f1e25b 100644
--- a/src/shared/musl_missing.h
+++ b/src/shared/musl_missing.h
@@ -19,8 +19,9 @@
void elogind_set_program_name(const char* pcall);
+#include "qsort_r_missing.h"
+
#if !defined(__GLIBC__)
-#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h> /* for pthread_atfork */
diff --git a/src/shared/nsflags.h b/src/shared/nsflags.h
index 4fe5b55..ffb81bf 100644
--- a/src/shared/nsflags.h
+++ b/src/shared/nsflags.h
@@ -18,11 +18,11 @@
CLONE_NEWUSER| \
CLONE_NEWUTS))
-#if 0 /// UNNEEDED by elogind
-#endif // 0
#define NAMESPACE_FLAGS_INITIAL ((unsigned long) -1)
+#if 0 /// UNNEEDED by elogind
int namespace_flags_from_string(const char *name, unsigned long *ret);
+#endif // 0
int namespace_flags_to_string(unsigned long flags, char **ret);
struct namespace_flag_map {
diff --git a/src/shared/qsort_r_missing.c b/src/shared/qsort_r_missing.c
new file mode 100644
index 0000000..8cef9e7
--- /dev/null
+++ b/src/shared/qsort_r_missing.c
@@ -0,0 +1,497 @@
+/***
+ This file is part of elogind.
+
+ Copyright 2017-2018 Sven Eden
+
+ elogind is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ elogind is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with elogind; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "qsort_r_missing.h"
+
+#if HAVE_QSORT_R == 0
+
+#include <alloca.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+/***
+ Original disclaimer of glibc-2.28 msort.c concerning qsort_r() follows:
+***/
+
+/* An alternative to qsort, with an identical interface.
+ This file is part of the GNU C Library.
+ Copyright (C) 1992-2018 Free Software Foundation, Inc.
+ Written by Mike Haertel, September 1988.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+
+/* qsort_r() calls internal _quicksort() function from qsort.c. Disclaimer is as above. */
+static void quicksort ( void* const pbase, size_t total_elems, size_t size, compare_fn_t cmp, void* arg );
+
+struct msort_param {
+ size_t s;
+ size_t var;
+ compare_fn_t cmp;
+ void* arg;
+ char* t;
+};
+
+static void msort_with_tmp ( const struct msort_param* p, void* b, size_t n ) {
+ char* b1, *b2;
+ size_t n1, n2;
+
+ if ( n <= 1 )
+ return;
+
+ n1 = n / 2;
+ n2 = n - n1;
+ b1 = b;
+ b2 = ( char* ) b + ( n1 * p->s );
+
+ msort_with_tmp ( p, b1, n1 );
+ msort_with_tmp ( p, b2, n2 );
+
+ char* tmp = p->t;
+ const size_t s = p->s;
+ compare_fn_t cmp = p->cmp;
+ void* arg = p->arg;
+ switch ( p->var ) {
+ case 0:
+ while ( n1 > 0 && n2 > 0 ) {
+ if ( ( *cmp ) ( b1, b2, arg ) <= 0 ) {
+ *( uint32_t* ) tmp = *( uint32_t* ) b1;
+ b1 += sizeof ( uint32_t );
+ --n1;
+ } else {
+ *( uint32_t* ) tmp = *( uint32_t* ) b2;
+ b2 += sizeof ( uint32_t );
+ --n2;
+ }
+ tmp += sizeof ( uint32_t );
+ }
+ break;
+ case 1:
+ while ( n1 > 0 && n2 > 0 ) {
+ if ( ( *cmp ) ( b1, b2, arg ) <= 0 ) {
+ *( uint64_t* ) tmp = *( uint64_t* ) b1;
+ b1 += sizeof ( uint64_t );
+ --n1;
+ } else {
+ *( uint64_t* ) tmp = *( uint64_t* ) b2;
+ b2 += sizeof ( uint64_t );
+ --n2;
+ }
+ tmp += sizeof ( uint64_t );
+ }
+ break;
+ case 2:
+ while ( n1 > 0 && n2 > 0 ) {
+ unsigned long* tmpl = ( unsigned long* ) tmp;
+ unsigned long* bl;
+
+ tmp += s;
+ if ( ( *cmp ) ( b1, b2, arg ) <= 0 ) {
+ bl = ( unsigned long* ) b1;
+ b1 += s;
+ --n1;
+ } else {
+ bl = ( unsigned long* ) b2;
+ b2 += s;
+ --n2;
+ }
+ while ( tmpl < ( unsigned long* ) tmp )
+ *tmpl++ = *bl++;
+ }
+ break;
+ case 3:
+ while ( n1 > 0 && n2 > 0 ) {
+ if ( ( *cmp ) ( *( const void** ) b1, *( const void** ) b2, arg ) <= 0 ) {
+ *( void** ) tmp = *( void** ) b1;
+ b1 += sizeof ( void* );
+ --n1;
+ } else {
+ *( void** ) tmp = *( void** ) b2;
+ b2 += sizeof ( void* );
+ --n2;
+ }
+ tmp += sizeof ( void* );
+ }
+ break;
+ default:
+ while ( n1 > 0 && n2 > 0 ) {
+ if ( ( *cmp ) ( b1, b2, arg ) <= 0 ) {
+ tmp = ( char* ) memcpy ( tmp, b1, s );
+ b1 += s;
+ --n1;
+ } else {
+ tmp = ( char* ) memcpy ( tmp, b2, s );
+ b2 += s;
+ --n2;
+ }
+ }
+ break;
+ }
+
+ if ( n1 > 0 )
+ memcpy ( tmp, b1, n1 * s );
+ memcpy ( b, p->t, ( n - n2 ) * s );
+}
+
+void qsort_r ( void* b, size_t n, size_t s, compare_fn_t cmp, void* arg ) {
+ size_t size = n * s;
+ char* tmp = NULL;
+ struct msort_param p;
+
+ /* For large object sizes use indirect sorting. */
+ if ( s > 32 )
+ size = 2 * n * sizeof ( void* ) + s;
+
+ if ( size < 1024 )
+ /* The temporary array is small, so put it on the stack. */
+ p.t = alloca ( size );
+ else {
+ /* We should avoid allocating too much memory since this might
+ have to be backed up by swap space. */
+ static long int phys_pages;
+ static int pagesize;
+
+ if ( pagesize == 0 ) {
+ phys_pages = sysconf ( _SC_PHYS_PAGES );
+
+ if ( phys_pages == -1 )
+ /* Error while determining the memory size. So let's
+ assume there is enough memory. Otherwise the
+ implementer should provide a complete implementation of
+ the `sysconf' function. */
+ phys_pages = ( long int ) ( ~0ul >> 1 );
+
+ /* The following determines that we will never use more than
+ a quarter of the physical memory. */
+ phys_pages /= 4;
+
+ /* Make sure phys_pages is written to memory. */
+ __asm ( "" ::: "memory" ); /*atomic_write_barrier () */
+
+ pagesize = sysconf ( _SC_PAGESIZE );
+ }
+
+ /* Just a comment here. We cannot compute
+ phys_pages * pagesize
+ and compare the needed amount of memory against this value.
+ The problem is that some systems might have more physical
+ memory then can be represented with a `size_t' value (when
+ measured in bytes. */
+
+ /* If the memory requirements are too high don't allocate memory. */
+ if ( size / pagesize > ( size_t ) phys_pages ) {
+ quicksort ( b, n, s, cmp, arg );
+ return;
+ }
+
+ /* It's somewhat large, so malloc it. */
+ int save = errno;
+ tmp = malloc ( size );
+ errno = ( save );
+ if ( tmp == NULL ) {
+ /* Couldn't get space, so use the slower algorithm
+ that doesn't need a temporary array. */
+ quicksort ( b, n, s, cmp, arg );
+ return;
+ }
+ p.t = tmp;
+ }
+
+ p.s = s;
+ p.var = 4;
+ p.cmp = cmp;
+ p.arg = arg;
+
+ if ( s > 32 ) {
+ /* Indirect sorting. */
+ char* ip = ( char* ) b;
+ void** tp = ( void** ) ( p.t + n * sizeof ( void* ) );
+ void** t = tp;
+ void* tmp_storage = ( void* ) ( tp + n );
+
+ while ( ( void* ) t < tmp_storage ) {
+ *t++ = ip;
+ ip += s;
+ }
+ p.s = sizeof ( void* );
+ p.var = 3;
+ msort_with_tmp ( &p, p.t + n * sizeof ( void* ), n );
+
+ /* tp[0] .. tp[n - 1] is now sorted, copy around entries of
+ the original array. Knuth vol. 3 (2nd ed.) exercise 5.2-10. */
+ char* kp;
+ size_t i;
+ for ( i = 0, ip = ( char* ) b; i < n; i++, ip += s )
+ if ( ( kp = tp[i] ) != ip ) {
+ size_t j = i;
+ char* jp = ip;
+ memcpy ( tmp_storage, ip, s );
+
+ do {
+ size_t k = ( kp - ( char* ) b ) / s;
+ tp[j] = jp;
+ memcpy ( jp, kp, s );
+ j = k;
+ jp = kp;
+ kp = tp[k];
+ } while ( kp != ip );
+
+ tp[j] = jp;
+ memcpy ( jp, tmp_storage, s );
+ }
+ } else {
+ if ( ( s & ( sizeof ( uint32_t ) - 1 ) ) == 0
+ && ( ( char* ) b - ( char* ) 0 ) % __alignof__ ( uint32_t ) == 0 ) {
+ if ( s == sizeof ( uint32_t ) )
+ p.var = 0;
+ else if ( s == sizeof ( uint64_t )
+ && ( ( char* ) b - ( char* ) 0 ) % __alignof__ ( uint64_t ) == 0 )
+ p.var = 1;
+ else if ( ( s & ( sizeof ( unsigned long ) - 1 ) ) == 0
+ && ( ( char* ) b - ( char* ) 0 )
+ % __alignof__ ( unsigned long ) == 0 )
+ p.var = 2;
+ }
+ msort_with_tmp ( &p, b, n );
+ }
+ free ( tmp );
+}
+
+/**** quicksort from qsort.c follows ****/
+
+/* Byte-wise swap two items of size SIZE. */
+#define SWAP(a, b, size) \
+ do \
+ { \
+ size_t size_ = (size); \
+ char *a_ = (a), *b_ = (b); \
+ do \
+ { \
+ char tmp_ = *a_; \
+ *a_++ = *b_; \
+ *b_++ = tmp_; \
+ } while (--size_ > 0); \
+ } while (0)
+
+/* Discontinue quicksort algorithm when partition gets below this size.
+ This particular magic number was chosen to work best on a Sun 4/260. */
+#define MAX_THRESH 4
+
+/* Stack node declarations used to store unfulfilled partition obligations. */
+typedef struct {
+ char* lo;
+ char* hi;
+} stack_node;
+
+/* The next 4 #defines implement a very fast in-line stack abstraction. */
+/* The stack needs log (total_elements) entries (we could even subtract
+ log(MAX_THRESH)). Since total_elements has type size_t, we get as
+ upper bound for log (total_elements):
+ bits per byte (CHAR_BIT) * sizeof(size_t). */
+#define STACK_SIZE (CHAR_BIT * sizeof(size_t))
+#define PUSH(low, high) ((void) ((top->lo = (low)), (top->hi = (high)), ++top))
+#define POP(low, high) ((void) (--top, (low = top->lo), (high = top->hi)))
+#define STACK_NOT_EMPTY (stack < top)
+
+/* Order size using quicksort. This implementation incorporates
+ four optimizations discussed in Sedgewick:
+
+ 1. Non-recursive, using an explicit stack of pointer that store the
+ next array partition to sort. To save time, this maximum amount
+ of space required to store an array of SIZE_MAX is allocated on the
+ stack. Assuming a 32-bit (64 bit) integer for size_t, this needs
+ only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes).
+ Pretty cheap, actually.
+
+ 2. Chose the pivot element using a median-of-three decision tree.
+ This reduces the probability of selecting a bad pivot value and
+ eliminates certain extraneous comparisons.
+
+ 3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving
+ insertion sort to order the MAX_THRESH items within each partition.
+ This is a big win, since insertion sort is faster for small, mostly
+ sorted array segments.
+
+ 4. The larger of the two sub-partitions is always pushed onto the
+ stack first, with the algorithm then concentrating on the
+ smaller partition. This *guarantees* no more than log (total_elems)
+ stack size is needed (actually O(1) in this case)! */
+
+static void quicksort ( void* const pbase, size_t total_elems, size_t size, compare_fn_t cmp, void* arg ) {
+ char* base_ptr = ( char* ) pbase;
+
+ const size_t max_thresh = MAX_THRESH * size;
+
+ if ( total_elems == 0 )
+ /* Avoid lossage with unsigned arithmetic below. */
+ return;
+
+ if ( total_elems > MAX_THRESH ) {
+ char* lo = base_ptr;
+ char* hi = &lo[size * ( total_elems - 1 )];
+ stack_node stack[STACK_SIZE];
+ stack_node* top = stack;
+
+ PUSH ( NULL, NULL );
+
+ while ( STACK_NOT_EMPTY ) {
+ char* left_ptr;
+ char* right_ptr;
+
+ /* Select median value from among LO, MID, and HI. Rearrange
+ LO and HI so the three values are sorted. This lowers the
+ probability of picking a pathological pivot value and
+ skips a comparison for both the LEFT_PTR and RIGHT_PTR in
+ the while loops. */
+
+ char* mid = lo + size * ( ( hi - lo ) / size >> 1 );
+
+ if ( ( *cmp ) ( ( void* ) mid, ( void* ) lo, arg ) < 0 )
+ SWAP ( mid, lo, size );
+ if ( ( *cmp ) ( ( void* ) hi, ( void* ) mid, arg ) < 0 )
+ SWAP ( mid, hi, size );
+ else
+ goto jump_over;
+ if ( ( *cmp ) ( ( void* ) mid, ( void* ) lo, arg ) < 0 )
+ SWAP ( mid, lo, size );
+ jump_over:
+ ;
+
+ left_ptr = lo + size;
+ right_ptr = hi - size;
+
+ /* Here's the famous ``collapse the walls'' section of quicksort.
+ Gotta like those tight inner loops! They are the main reason
+ that this algorithm runs much faster than others. */
+ do {
+ while ( ( *cmp ) ( ( void* ) left_ptr, ( void* ) mid, arg ) < 0 )
+ left_ptr += size;
+
+ while ( ( *cmp ) ( ( void* ) mid, ( void* ) right_ptr, arg ) < 0 )
+ right_ptr -= size;
+
+ if ( left_ptr < right_ptr ) {
+ SWAP ( left_ptr, right_ptr, size );
+ if ( mid == left_ptr )
+ mid = right_ptr;
+ else if ( mid == right_ptr )
+ mid = left_ptr;
+ left_ptr += size;
+ right_ptr -= size;
+ } else if ( left_ptr == right_ptr ) {
+ left_ptr += size;
+ right_ptr -= size;
+ break;
+ }
+ } while ( left_ptr <= right_ptr );
+
+ /* Set up pointers for next iteration. First determine whether
+ left and right partitions are below the threshold size. If so,
+ ignore one or both. Otherwise, push the larger partition's
+ bounds on the stack and continue sorting the smaller one. */
+
+ if ( ( size_t ) ( right_ptr - lo ) <= max_thresh ) {
+ if ( ( size_t ) ( hi - left_ptr ) <= max_thresh )
+ /* Ignore both small partitions. */
+ POP ( lo, hi );
+ else
+ /* Ignore small left partition. */
+ lo = left_ptr;
+ } else if ( ( size_t ) ( hi - left_ptr ) <= max_thresh )
+ /* Ignore small right partition. */
+ hi = right_ptr;
+ else if ( ( right_ptr - lo ) > ( hi - left_ptr ) ) {
+ /* Push larger left partition indices. */
+ PUSH ( lo, right_ptr );
+ lo = left_ptr;
+ } else {
+ /* Push larger right partition indices. */
+ PUSH ( left_ptr, hi );
+ hi = right_ptr;
+ }
+ }
+ }
+
+ /* Once the BASE_PTR array is partially sorted by quicksort the rest
+ is completely sorted using insertion sort, since this is efficient
+ for partitions below MAX_THRESH size. BASE_PTR points to the beginning
+ of the array to sort, and END_PTR points at the very last element in
+ the array (*not* one beyond it!). */
+
+#define min(x, y) ((x) < (y) ? (x) : (y))
+
+ {
+ char* const end_ptr = &base_ptr[size * ( total_elems - 1 )];
+ char* tmp_ptr = base_ptr;
+ char* thresh = min( end_ptr, base_ptr + max_thresh );
+ char* run_ptr;
+
+ /* Find smallest element in first threshold and place it at the
+ array's beginning. This is the smallest array element,
+ and the operation speeds up insertion sort's inner loop. */
+
+ for ( run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size )
+ if ( ( *cmp ) ( ( void* ) run_ptr, ( void* ) tmp_ptr, arg ) < 0 )
+ tmp_ptr = run_ptr;
+
+ if ( tmp_ptr != base_ptr )
+ SWAP ( tmp_ptr, base_ptr, size );
+
+ /* Insertion sort, running from left-hand-side up to right-hand-side. */
+
+ run_ptr = base_ptr + size;
+ while ( ( run_ptr += size ) <= end_ptr ) {
+ tmp_ptr = run_ptr - size;
+ while ( ( *cmp ) ( ( void* ) run_ptr, ( void* ) tmp_ptr, arg ) < 0 )
+ tmp_ptr -= size;
+
+ tmp_ptr += size;
+ if ( tmp_ptr != run_ptr ) {
+ char* trav;
+
+ trav = run_ptr + size;
+ while ( --trav >= run_ptr ) {
+ char c = *trav;
+ char* hi, *lo;
+
+ for ( hi = lo = trav; ( lo -= size ) >= tmp_ptr; hi = lo )
+ * hi = *lo;
+ *hi = c;
+ }
+ }
+ }
+ }
+}
+
+#endif // HAVE_QSORT_R
diff --git a/src/shared/qsort_r_missing.h b/src/shared/qsort_r_missing.h
new file mode 100644
index 0000000..32c036c
--- /dev/null
+++ b/src/shared/qsort_r_missing.h
@@ -0,0 +1,34 @@
+#pragma once
+#ifndef ELOGIND_SRC_SHARED_QSORT_R_MISSING_H_INCLUDED
+#define ELOGIND_SRC_SHARED_QSORT_R_MISSING_H_INCLUDED
+
+/***
+ This file is part of elogind.
+
+ Copyright 2017-2018 Sven Eden
+
+ elogind is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ elogind is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with elogind; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+
+#if HAVE_QSORT_R == 0
+
+typedef int (*compare_fn_t) (const void *, const void *, void *);
+
+void qsort_r (void *base_, size_t nmemb_, size_t size_, compare_fn_t comp_, void *arg_);
+
+#endif // qsort_r() missing
+
+#endif // ELOGIND_SRC_SHARED_QSORT_R_MISSING_H_INCLUDED
diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c
index 77b380c..8045816 100644
--- a/src/shared/sleep-config.c
+++ b/src/shared/sleep-config.c
@@ -13,6 +13,7 @@
//#include <unistd.h>
#include "alloc-util.h"
+//#include "bootspec.h"
//#include "conf-parser.h"
//#include "def.h"
//#include "env-util.h"
@@ -21,13 +22,17 @@
//#include "log.h"
//#include "macro.h"
#include "parse-util.h"
+#include "path-util.h"
+#include "proc-cmdline.h"
#include "sleep-config.h"
#include "string-util.h"
#include "strv.h"
#if 0 /// UNNEEDED by elogind
-int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t *_delay) {
-
+int parse_sleep_config(const char *verb, bool *ret_allow, char ***ret_modes, char ***ret_states, usec_t *ret_delay) {
+ int allow_suspend = -1, allow_hibernate = -1,
+ allow_s2h = -1, allow_hybrid_sleep = -1;
+ bool allow;
_cleanup_strv_free_ char
**suspend_mode = NULL, **suspend_state = NULL,
**hibernate_mode = NULL, **hibernate_state = NULL,
@@ -36,13 +41,19 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t
usec_t delay = 180 * USEC_PER_MINUTE;
const ConfigTableItem items[] = {
- { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode },
- { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state },
- { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode },
- { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state },
- { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode },
- { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state },
- { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &delay},
+ { "Sleep", "AllowSuspend", config_parse_tristate, 0, &allow_suspend },
+ { "Sleep", "AllowHibernation", config_parse_tristate, 0, &allow_hibernate },
+ { "Sleep", "AllowSuspendThenHibernate", config_parse_tristate, 0, &allow_s2h },
+ { "Sleep", "AllowHybridSleep", config_parse_tristate, 0, &allow_hybrid_sleep },
+
+ { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode },
+ { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state },
+ { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode },
+ { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state },
+ { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode },
+ { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state },
+
+ { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &delay},
{}
};
@@ -52,6 +63,8 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t
CONFIG_PARSE_WARN, NULL);
if (streq(verb, "suspend")) {
+ allow = allow_suspend != 0;
+
/* empty by default */
modes = TAKE_PTR(suspend_mode);
@@ -61,6 +74,8 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t
states = strv_new("mem", "standby", "freeze", NULL);
} else if (streq(verb, "hibernate")) {
+ allow = allow_hibernate != 0;
+
if (hibernate_mode)
modes = TAKE_PTR(hibernate_mode);
else
@@ -72,6 +87,9 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t
states = strv_new("disk", NULL);
} else if (streq(verb, "hybrid-sleep")) {
+ allow = allow_hybrid_sleep > 0 ||
+ (allow_suspend != 0 && allow_hibernate != 0);
+
if (hybrid_mode)
modes = TAKE_PTR(hybrid_mode);
else
@@ -82,21 +100,26 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t
else
states = strv_new("disk", NULL);
- } else if (streq(verb, "suspend-then-hibernate"))
+ } else if (streq(verb, "suspend-then-hibernate")) {
+ allow = allow_s2h > 0 ||
+ (allow_suspend != 0 && allow_hibernate != 0);
+
modes = states = NULL;
- else
+ } else
assert_not_reached("what verb");
if ((!modes && STR_IN_SET(verb, "hibernate", "hybrid-sleep")) ||
(!states && !streq(verb, "suspend-then-hibernate")))
return log_oom();
- if (_modes)
- *_modes = TAKE_PTR(modes);
- if (_states)
- *_states = TAKE_PTR(states);
- if (_delay)
- *_delay = delay;
+ if (ret_allow)
+ *ret_allow = allow;
+ if (ret_modes)
+ *ret_modes = TAKE_PTR(modes);
+ if (ret_states)
+ *ret_states = TAKE_PTR(states);
+ if (ret_delay)
+ *ret_delay = delay;
return 0;
}
@@ -201,18 +224,30 @@ int find_hibernate_location(char **device, char **type, size_t *size, size_t *us
"%zu " /* used */
"%*i\n", /* priority */
&dev_field, &type_field, &size_field, &used_field);
+ if (k == EOF)
+ break;
if (k != 4) {
- if (k == EOF)
- break;
-
log_warning("Failed to parse /proc/swaps:%u", i);
continue;
}
- if (streq(type_field, "partition") && endswith(dev_field, "\\040(deleted)")) {
- log_warning("Ignoring deleted swapfile '%s'.", dev_field);
- continue;
+ if (streq(type_field, "file")) {
+
+ if (endswith(dev_field, "\\040(deleted)")) {
+ log_warning("Ignoring deleted swap file '%s'.", dev_field);
+ continue;
+ }
+
+ } else if (streq(type_field, "partition")) {
+ const char *fn;
+
+ fn = path_startswith(dev_field, "/dev/");
+ if (fn && startswith(fn, "zram")) {
+ log_debug("Ignoring compressed RAM swap device '%s'.", dev_field);
+ continue;
+ }
}
+
if (device)
*device = TAKE_PTR(dev_field);
if (type)
@@ -257,12 +292,96 @@ static bool enough_swap_for_hibernation(void) {
}
r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
- log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
- r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
+ log_debug("%s swap for hibernation, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
+ r ? "Enough" : "Not enough", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
return r;
}
+#if 0 /// elogind is not init and can not check boot devices and partitions.
+static int check_resume_keys(const char *key, const char *value, void *data) {
+ assert_se(key);
+ assert_se(data);
+
+ int *resume = data;
+
+ if (*resume == 0)
+ /* Exit if we already know we can't resume. */
+ return 0;
+
+ if (streq(key, "noresume")) {
+ log_debug("Found \"noresume\" on the kernel command line, hibernation is disabled.");
+ *resume = 0;
+
+ } else if (streq(key, "resume")) {
+ log_debug("Found resume= option on the kernel command line, hibernation is possible.");
+ *resume = 1;
+ }
+
+ return 0;
+}
+
+static int resume_configured_in_options(const char *options) {
+ int resume = -1, r;
+
+ /* We don't use PROC_CMDLINE_STRIP_RD_PREFIX here, so rd.resume is *not* supported. */
+ r = proc_cmdline_parse_given(options, check_resume_keys, &resume, 0);
+ if (r < 0)
+ return r;
+
+ if (resume < 0)
+ log_debug("Couldn't find resume= option, hibernation is disabled.");
+ return resume > 0;
+}
+
+static int resume_configured(void) {
+ _cleanup_(boot_config_free) BootConfig config = {};
+ const BootEntry *e;
+ int r;
+
+ /* Check whether a valid resume= option is present. If possible, we query the boot options
+ * for the default kernel. If the system is not using sd-boot, fall back to checking the
+ * current kernel command line. This is not perfect, but should suffice for most cases. */
+
+ r = find_default_boot_entry(NULL, NULL, &config, &e);
+ if (r == -ENOKEY)
+ log_debug_errno(r, "Cannot find the ESP partition mount point, falling back to other checks.");
+ else if (r < 0)
+ return log_debug_errno(r, "Cannot read boot configuration from ESP, assuming hibernation is not possible.");
+ else {
+ _cleanup_free_ char *options = NULL;
+
+ options = strv_join(e->options, " ");
+ if (!options)
+ return log_oom();
+
+ r = resume_configured_in_options(options);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse kernel options in \"%s\": %m",
+ strnull(e->path));
+ return r;
+ }
+
+ /* If we can't figure out the default boot entry, let's fall back to current kernel cmdline */
+ _cleanup_free_ char *line = NULL;
+ r = proc_cmdline(&line);
+ if (IN_SET(r, -EPERM, -EACCES, -ENOENT))
+ log_debug_errno(r, "Cannot access /proc/cmdline: %m");
+ else if (r < 0)
+ return log_error_errno(r, "Failed to query /proc/cmdline: %m");
+ else {
+ r = resume_configured_in_options(line);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse kernel proc cmdline: %m");
+
+ return r;
+ }
+
+ log_debug("Couldn't detect any resume mechanism, hibernation is disabled.");
+ return false;
+}
+#endif // 0
+
int read_fiemap(int fd, struct fiemap **ret) {
_cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL;
struct stat statinfo;
@@ -348,6 +467,12 @@ int read_fiemap(int fd, struct fiemap **ret) {
}
#if 0 /// elogind has to ask the manager for some stuff
+static int can_sleep_internal(const char *verb, bool check_allowed);
+#else
+static int can_sleep_internal(Manager *m, const char *verb, bool check_allowed);
+#endif // 0
+
+#if 0 /// elogind has to ask the manager for some stuff
static bool can_s2h(void) {
#else
static bool can_s2h(Manager *m) {
@@ -364,11 +489,11 @@ static bool can_s2h(Manager *m) {
FOREACH_STRING(p, "suspend", "hibernate") {
#if 0 /// elogind must transport a pointer to its managers instance
- r = can_sleep(p);
+ r = can_sleep_internal(p, false);
#else
r = can_sleep(m, p);
#endif // 0
- if (IN_SET(r, 0, -ENOSPC)) {
+ if (IN_SET(r, 0, -ENOSPC, -EADV)) {
log_debug("Unable to %s system.", p);
return false;
}
@@ -381,11 +506,15 @@ static bool can_s2h(Manager *m) {
#if 0 /// elogind has to ask the manager for some stuff
-int can_sleep(const char *verb) {
+static int can_sleep_internal(const char *verb, bool check_allowed) {
+ bool allow;
_cleanup_strv_free_ char **modes = NULL, **states = NULL;
int r;
+
+
+ assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
#else
-int can_sleep(Manager *m, const char *verb) {
+static int can_sleep_internal(Manager *m, const char *verb, bool check_allowed) {
assert(m);
char **modes = streq(verb, "suspend") ? m->suspend_mode :
@@ -396,7 +525,20 @@ int can_sleep(Manager *m, const char *verb) {
m->hybrid_sleep_state;
#endif // 0
- assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
+
+#if 0 /// already parsed by elogind config
+ r = parse_sleep_config(verb, &allow, &modes, &states, NULL);
+ if (r < 0)
+ return false;
+
+ if (check_allowed && !allow) {
+#else
+ if (check_allowed && !STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate")) {
+#endif // 0
+
+ log_debug("Sleep mode \"%s\" is disabled by configuration.", verb);
+ return false;
+ }
if (streq(verb, "suspend-then-hibernate"))
#if 0 /// elogind must transport a pointer to its managers instance
@@ -405,12 +547,6 @@ int can_sleep(Manager *m, const char *verb) {
return can_s2h(m);
#endif // 0
-#if 0 /// already parsed by elogind config
- r = parse_sleep_config(verb, &modes, &states, NULL);
- if (r < 0)
- return false;
-#endif // 0
-
if (!can_sleep_state(states) || !can_sleep_disk(modes))
return false;
@@ -420,5 +556,22 @@ int can_sleep(Manager *m, const char *verb) {
if (!enough_swap_for_hibernation())
return -ENOSPC;
+#if 0 /// elogind is not init and can not check this
+ r = resume_configured();
+ if (r <= 0)
+ /* We squash all errors (e.g. EPERM) into a single value for reporting. */
+ return -EADV;
+#endif // 0
+
return true;
}
+
+#if 0 /// elogind has to ask the manager for some stuff
+int can_sleep(const char *verb) {
+ return can_sleep_internal(verb, true);
+}
+#else
+int can_sleep(Manager *m, const char *verb) {
+ return can_sleep_internal(m, verb, true);
+}
+#endif // 0
diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h
index b291651..19bbaa4 100644
--- a/src/shared/sleep-config.h
+++ b/src/shared/sleep-config.h
@@ -6,7 +6,7 @@
int read_fiemap(int fd, struct fiemap **ret);
#if 0 /// UNNEEDED by elogind
-int parse_sleep_config(const char *verb, char ***modes, char ***states, usec_t *delay);
+int parse_sleep_config(const char *verb, bool *ret_allow, char ***ret_modes, char ***ret_states, usec_t *ret_delay);
#endif // 0
int find_hibernate_location(char **device, char **type, size_t *size, size_t *used);
diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c
index 87efd0a..e740a26 100644
--- a/src/sleep/sleep.c
+++ b/src/sleep/sleep.c
@@ -149,7 +149,6 @@ static int execute(Manager *m, const char *verb) {
streq(arg_verb, "hibernate") ? m->hibernate_state :
m->hybrid_sleep_state;
#endif // 0
-
char *arguments[] = {
NULL,
(char*) "pre",
@@ -180,7 +179,7 @@ static int execute(Manager *m, const char *verb) {
return log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");;
}
- execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments);
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL);
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR,
@@ -189,15 +188,18 @@ static int execute(Manager *m, const char *verb) {
r = write_state(&f, states);
if (r < 0)
- return log_error_errno(r, "Failed to write /sys/power/state: %m");
-
- log_struct(LOG_INFO,
- "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR,
- LOG_MESSAGE("System resumed."),
- "SLEEP=%s", arg_verb);
+ log_struct_errno(LOG_ERR, r,
+ "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR,
+ LOG_MESSAGE("Failed to suspend system. System resumed again: %m"),
+ "SLEEP=%s", arg_verb);
+ else
+ log_struct(LOG_INFO,
+ "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR,
+ LOG_MESSAGE("System resumed."),
+ "SLEEP=%s", arg_verb);
arguments[1] = (char*) "post";
- execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments);
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL);
return r;
}
@@ -242,13 +244,11 @@ static int execute_s2h(Manager *m) {
int r;
#if 0 /// Already parsed by elogind config
- r = parse_sleep_config("suspend", &suspend_modes, &suspend_states,
- NULL);
+ r = parse_sleep_config("suspend", NULL, &suspend_modes, &suspend_states, NULL);
if (r < 0)
return r;
- r = parse_sleep_config("hibernate", &hibernate_modes,
- &hibernate_states, NULL);
+ r = parse_sleep_config("hibernate", NULL, &hibernate_modes, &hibernate_states, NULL);
if (r < 0)
return r;
#endif // 0
@@ -363,6 +363,7 @@ static int parse_argv(int argc, char *argv[]) {
}
int main(int argc, char *argv[]) {
+ bool allow;
_cleanup_strv_free_ char **modes = NULL, **states = NULL;
usec_t delay = 0;
int r;
@@ -375,10 +376,15 @@ int main(int argc, char *argv[]) {
if (r <= 0)
goto finish;
- r = parse_sleep_config(arg_verb, &modes, &states, &delay);
+ r = parse_sleep_config(arg_verb, &allow, &modes, &states, &delay);
if (r < 0)
goto finish;
+ if (!allow) {
+ log_error("Sleep mode \"%s\" is disabled by configuration, refusing.", arg_verb);
+ return EXIT_FAILURE;
+ }
+
if (streq(arg_verb, "suspend-then-hibernate"))
r = execute_s2h(delay);
else
diff --git a/src/test/meson.build b/src/test/meson.build
index 0405fac..d08e612 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -67,6 +67,11 @@ tests += [
# libmount,
# libblkid]],
#
+# [['src/test/test-chown-rec.c'],
+# [libcore,
+# libshared],
+# []],
+#
# [['src/test/test-job-type.c'],
# [libcore,
# libshared],
@@ -426,6 +431,10 @@ tests += [
[],
[]],
+ [['src/test/test-set-disable-mempool.c'],
+ [],
+ [threads]],
+
#if 0 /// UNNEEDED in elogind
# [['src/test/test-bitmap.c'],
# [],
@@ -943,9 +952,16 @@ tests += [
[['src/libelogind/sd-login/test-login.c'],
[],
[]],
-]
#if 0 /// UNNEEDED in elogind
+# [['src/libelogind/sd-device/test-udev-device-thread.c'],
+# [libbasic,
+# libshared_static,
+# libudev],
+# [threads]],
+# ]
+#
+#
# if cxx.found()
# tests += [
# [['src/libelogind/sd-bus/test-bus-vtable-cc.cc'],
@@ -953,6 +969,8 @@ tests += [
# []]
# ]
# endif
+#else
+]
#endif // 0
############################################################
diff --git a/src/test/test-chown-rec.c b/src/test/test-chown-rec.c
new file mode 100644
index 0000000..f79577f
--- /dev/null
+++ b/src/test/test-chown-rec.c
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+//#include <sys/xattr.h>
+
+//#include "alloc-util.h"
+//#include "chown-recursive.h"
+//#include "fileio.h"
+//#include "log.h"
+//#include "rm-rf.h"
+//#include "string-util.h"
+//#include "tests.h"
+
+static const uint8_t acl[] = {
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0x02, 0x00, 0x07, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x07, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, 0x07, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0x20, 0x00, 0x05, 0x00,
+ 0xff, 0xff, 0xff, 0xff,
+};
+
+static const uint8_t default_acl[] = {
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0x04, 0x00, 0x07, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x07, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x07, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0x20, 0x00, 0x05, 0x00,
+ 0xff, 0xff, 0xff, 0xff,
+};
+
+static bool has_xattr(const char *p) {
+ char buffer[sizeof(acl) * 4];
+
+ if (lgetxattr(p, "system.posix_acl_access", buffer, sizeof(buffer)) < 0) {
+ if (IN_SET(errno, EOPNOTSUPP, ENOTTY, ENODATA, ENOSYS))
+ return false;
+ }
+
+ return true;
+}
+
+static void test_chown_recursive(void) {
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ struct stat st;
+ const char *p;
+
+ umask(022);
+ assert_se(mkdtemp_malloc(NULL, &t) >= 0);
+
+ p = strjoina(t, "/dir");
+ assert_se(mkdir(p, 0777) >= 0);
+ assert_se(lstat(p, &st) >= 0);
+ assert_se(S_ISDIR(st.st_mode));
+ assert_se((st.st_mode & 07777) == 0755);
+ assert_se(st.st_uid == 0);
+ assert_se(st.st_gid == 0);
+ assert_se(!has_xattr(p));
+
+ p = strjoina(t, "/dir/symlink");
+ assert_se(symlink("../../", p) >= 0);
+ assert_se(lstat(p, &st) >= 0);
+ assert_se(S_ISLNK(st.st_mode));
+ assert_se((st.st_mode & 07777) == 0777);
+ assert_se(st.st_uid == 0);
+ assert_se(st.st_gid == 0);
+ assert_se(!has_xattr(p));
+
+ p = strjoina(t, "/dir/reg");
+ assert_se(mknod(p, S_IFREG|0777, 0) >= 0);
+ assert_se(lstat(p, &st) >= 0);
+ assert_se(S_ISREG(st.st_mode));
+ assert_se((st.st_mode & 07777) == 0755);
+ assert_se(st.st_uid == 0);
+ assert_se(st.st_gid == 0);
+ assert_se(!has_xattr(p));
+
+ p = strjoina(t, "/dir/sock");
+ assert_se(mknod(p, S_IFSOCK|0777, 0) >= 0);
+ assert_se(lstat(p, &st) >= 0);
+ assert_se(S_ISSOCK(st.st_mode));
+ assert_se((st.st_mode & 07777) == 0755);
+ assert_se(st.st_uid == 0);
+ assert_se(st.st_gid == 0);
+ assert_se(!has_xattr(p));
+
+ p = strjoina(t, "/dir/fifo");
+ assert_se(mknod(p, S_IFIFO|0777, 0) >= 0);
+ assert_se(lstat(p, &st) >= 0);
+ assert_se(S_ISFIFO(st.st_mode));
+ assert_se((st.st_mode & 07777) == 0755);
+ assert_se(st.st_uid == 0);
+ assert_se(st.st_gid == 0);
+ assert_se(!has_xattr(p));
+
+ /* We now apply an xattr to the dir, and check it again */
+ p = strjoina(t, "/dir");
+ assert_se(setxattr(p, "system.posix_acl_access", acl, sizeof(acl), 0) >= 0);
+ assert_se(setxattr(p, "system.posix_acl_default", default_acl, sizeof(default_acl), 0) >= 0);
+ assert_se(lstat(p, &st) >= 0);
+ assert_se(S_ISDIR(st.st_mode));
+ assert_se((st.st_mode & 07777) == 0775); /* acl change changed the mode too */
+ assert_se(st.st_uid == 0);
+ assert_se(st.st_gid == 0);
+ assert_se(has_xattr(p));
+
+ assert_se(path_chown_recursive(t, 1, 2) >= 0);
+
+ p = strjoina(t, "/dir");
+ assert_se(lstat(p, &st) >= 0);
+ assert_se(S_ISDIR(st.st_mode));
+ assert_se((st.st_mode & 07777) == 0775);
+ assert_se(st.st_uid == 1);
+ assert_se(st.st_gid == 2);
+ assert_se(!has_xattr(p));
+
+ p = strjoina(t, "/dir/symlink");
+ assert_se(lstat(p, &st) >= 0);
+ assert_se(S_ISLNK(st.st_mode));
+ assert_se((st.st_mode & 07777) == 0777);
+ assert_se(st.st_uid == 1);
+ assert_se(st.st_gid == 2);
+ assert_se(!has_xattr(p));
+
+ p = strjoina(t, "/dir/reg");
+ assert_se(lstat(p, &st) >= 0);
+ assert_se(S_ISREG(st.st_mode));
+ assert_se((st.st_mode & 07777) == 0755);
+ assert_se(st.st_uid == 1);
+ assert_se(st.st_gid == 2);
+ assert_se(!has_xattr(p));
+
+ p = strjoina(t, "/dir/sock");
+ assert_se(lstat(p, &st) >= 0);
+ assert_se(S_ISSOCK(st.st_mode));
+ assert_se((st.st_mode & 07777) == 0755);
+ assert_se(st.st_uid == 1);
+ assert_se(st.st_gid == 2);
+ assert_se(!has_xattr(p));
+
+ p = strjoina(t, "/dir/fifo");
+ assert_se(lstat(p, &st) >= 0);
+ assert_se(S_ISFIFO(st.st_mode));
+ assert_se((st.st_mode & 07777) == 0755);
+ assert_se(st.st_uid == 1);
+ assert_se(st.st_gid == 2);
+ assert_se(!has_xattr(p));
+}
+
+int main(int argc, char *argv[]) {
+ log_set_max_level(LOG_DEBUG);
+ log_parse_environment();
+ log_open();
+
+ if (geteuid() != 0)
+ return EXIT_TEST_SKIP;
+
+ test_chown_recursive();
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/test/test-exec-util.c b/src/test/test-exec-util.c
index 0303283..24f8cd3 100644
--- a/src/test/test-exec-util.c
+++ b/src/test/test-exec-util.c
@@ -16,6 +16,7 @@
#include "fs-util.h"
#include "log.h"
#include "macro.h"
+//#include "path-util.h"
#include "rm-rf.h"
#include "string-util.h"
#include "strv.h"
@@ -115,9 +116,9 @@ static void test_execute_directory(bool gather_stdout) {
assert_se(chmod(mask2e, 0755) == 0);
if (gather_stdout)
- execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL);
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL, NULL);
else
- execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, NULL);
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, NULL, NULL);
assert_se(chdir(template_lo) == 0);
assert_se(access("it_works", F_OK) >= 0);
@@ -182,7 +183,7 @@ static void test_execution_order(void) {
assert_se(chmod(override, 0755) == 0);
assert_se(chmod(masked, 0755) == 0);
- execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL);
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL, NULL);
assert_se(read_full_file(output, &contents, NULL) >= 0);
assert_se(streq(contents, "30-override\n80-foo\n90-bar\nlast\n"));
@@ -264,7 +265,7 @@ static void test_stdout_gathering(void) {
assert_se(chmod(name2, 0755) == 0);
assert_se(chmod(name3, 0755) == 0);
- r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_stdout, args, NULL);
+ r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_stdout, args, NULL, NULL);
assert_se(r >= 0);
log_info("got: %s", output);
@@ -276,7 +277,7 @@ static void test_stdout_gathering(void) {
static void test_environment_gathering(void) {
char template[] = "/tmp/test-exec-util.XXXXXXX", **p;
const char *dirs[] = {template, NULL};
- const char *name, *name2, *name3;
+ const char *name, *name2, *name3, *old;
int r;
char **tmp = NULL; /* this is only used in the forked process, no cleanup here */
@@ -322,7 +323,32 @@ static void test_environment_gathering(void) {
assert_se(chmod(name2, 0755) == 0);
assert_se(chmod(name3, 0755) == 0);
- r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL);
+ /* When booting in containers or without initramfs there might not be
+ * any PATH in the environ and if there is no PATH /bin/sh built-in
+ * PATH may leak and override systemd's DEFAULT_PATH which is not
+ * good. Force our own PATH in environment, to prevent expansion of sh
+ * built-in $PATH */
+ old = getenv("PATH");
+ r = setenv("PATH", "no-sh-built-in-path", 1);
+ assert_se(r >= 0);
+
+ r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL, NULL);
+ assert_se(r >= 0);
+
+ STRV_FOREACH(p, env)
+ log_info("got env: \"%s\"", *p);
+
+ assert_se(streq(strv_env_get(env, "A"), "22:23:24"));
+ assert_se(streq(strv_env_get(env, "B"), "12"));
+ assert_se(streq(strv_env_get(env, "C"), "001"));
+ assert_se(streq(strv_env_get(env, "PATH"), "no-sh-built-in-path:/no/such/file"));
+
+ /* now retest with "default" path passed in, as created by
+ * manager_default_environment */
+ env = strv_free(env);
+ env = strv_new("PATH=" DEFAULT_PATH, NULL);
+
+ r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL, env);
assert_se(r >= 0);
STRV_FOREACH(p, env)
@@ -331,7 +357,10 @@ static void test_environment_gathering(void) {
assert_se(streq(strv_env_get(env, "A"), "22:23:24"));
assert_se(streq(strv_env_get(env, "B"), "12"));
assert_se(streq(strv_env_get(env, "C"), "001"));
- assert_se(endswith(strv_env_get(env, "PATH"), ":/no/such/file"));
+ assert_se(streq(strv_env_get(env, "PATH"), DEFAULT_PATH ":/no/such/file"));
+
+ /* reset environ PATH */
+ (void) setenv("PATH", old, 1);
}
#endif // 0
diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c
index adcc414..f94cdf6 100644
--- a/src/test/test-format-table.c
+++ b/src/test/test-format-table.c
@@ -5,6 +5,33 @@
#include "string-util.h"
#include "time-util.h"
+/// Additional includes needed by elogind
+#include "locale-util.h"
+
+static void test_issue_9549(void) {
+ _cleanup_(table_unrefp) Table *table = NULL;
+ _cleanup_free_ char *formatted = NULL;
+
+ assert_se(table = table_new("NAME", "TYPE", "RO", "USAGE", "CREATED", "MODIFIED"));
+ assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(3), 100) >= 0);
+ assert_se(table_add_many(table,
+ TABLE_STRING, "foooo",
+ TABLE_STRING, "raw",
+ TABLE_BOOLEAN, false,
+ TABLE_SIZE, (uint64_t) (673.7*1024*1024),
+ TABLE_STRING, "Wed 2018-07-11 00:10:33 JST",
+ TABLE_STRING, "Wed 2018-07-11 00:16:00 JST") >= 0);
+
+ table_set_width(table, 75);
+ assert_se(table_format(table, &formatted) >= 0);
+
+ printf("%s\n", formatted);
+ assert_se(streq(formatted,
+ "NAME TYPE RO USAGE CREATED MODIFIED \n"
+ "foooo raw no 673.6M Wed 2018-07-11 00:10:33 J… Wed 2018-07-11 00:16:00 JST\n"
+ ));
+}
+
int main(int argc, char *argv[]) {
_cleanup_(table_unrefp) Table *t = NULL;
@@ -50,6 +77,10 @@ int main(int argc, char *argv[]) {
table_set_width(t, 12);
assert_se(table_format(t, &formatted) >= 0);
+
+#if 1 /// elogind supports systems with non-UTF-8 locales, the next would fail there
+ if (is_locale_utf8()) {
+#endif // 1
printf("%s\n", formatted);
assert_se(streq(formatted,
@@ -80,6 +111,9 @@ int main(int argc, char *argv[]) {
"… … …\n"));
formatted = mfree(formatted);
+#if 1 /// elogind supports systems with non-UTF-8 locales, the previous would fail there
+ }
+#endif // 1
table_set_width(t, (size_t) -1);
assert_se(table_set_sort(t, (size_t) 0, (size_t) 2, (size_t) -1) >= 0);
@@ -96,6 +130,9 @@ int main(int argc, char *argv[]) {
table_set_header(t, false);
+#if 1 /// elogind supports systems with non-UTF-8 locales, the next would fail there
+ if (is_locale_utf8()) {
+#endif // 1
assert_se(table_add_many(t,
TABLE_STRING, "fäää",
TABLE_STRING, "uuu",
@@ -134,6 +171,11 @@ int main(int argc, char *argv[]) {
" yes fäää yes fäää fäää \n"
" yes xxx yes xxx xxx \n"
"5min 5min \n"));
+#if 1 /// elogind supports systems with non-UTF-8 locales, the previous would fail there
+ }
+#endif // 1
+
+ test_issue_9549();
return 0;
}
diff --git a/src/test/test-proc-cmdline.c b/src/test/test-proc-cmdline.c
index 275c377..d77c7f6 100644
--- a/src/test/test-proc-cmdline.c
+++ b/src/test/test-proc-cmdline.c
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
+//#include "env-util.h"
#include "log.h"
#include "macro.h"
#include "proc-cmdline.h"
@@ -19,29 +20,69 @@ static int parse_item(const char *key, const char *value, void *data) {
}
static void test_proc_cmdline_parse(void) {
- assert_se(proc_cmdline_parse(parse_item, &obj, true) >= 0);
+ log_info("/* %s */", __func__);
+
+ assert_se(proc_cmdline_parse(parse_item, &obj, PROC_CMDLINE_STRIP_RD_PREFIX) >= 0);
}
#if 0 /// UNNEEDED by elogind
-static void test_runlevel_to_target(void) {
- in_initrd_force(false);
- assert_se(streq_ptr(runlevel_to_target(NULL), NULL));
- assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL));
- assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL));
- assert_se(streq_ptr(runlevel_to_target("3"), SPECIAL_MULTI_USER_TARGET));
- assert_se(streq_ptr(runlevel_to_target("rd.rescue"), NULL));
+static void test_proc_cmdline_override(void) {
+ log_info("/* %s */", __func__);
- in_initrd_force(true);
- assert_se(streq_ptr(runlevel_to_target(NULL), NULL));
- assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL));
- assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL));
- assert_se(streq_ptr(runlevel_to_target("3"), NULL));
- assert_se(streq_ptr(runlevel_to_target("rd.rescue"), SPECIAL_RESCUE_TARGET));
+ assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm") == 0);
+
+ /* Test if the override works */
+ _cleanup_free_ char *line = NULL, *value = NULL;
+ assert_se(proc_cmdline(&line) >= 0);
+
+ /* Test if parsing makes uses of the override */
+ assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm"));
+ assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux"));
+}
+
+static int parse_item_given(const char *key, const char *value, void *data) {
+ assert_se(key);
+ assert_se(data);
+
+ bool *strip = data;
+
+ log_info("%s: option <%s> = <%s>", __func__, key, strna(value));
+ if (streq(key, "foo_bar"))
+ assert_se(streq(value, "quux"));
+ else if (streq(key, "wuff-piep"))
+ assert_se(streq(value, "tuet "));
+ else if (in_initrd() && *strip && streq(key, "zumm"))
+ assert_se(!value);
+ else if (in_initrd() && !*strip && streq(key, "rd.zumm"))
+ assert_se(!value);
+ else
+ assert_not_reached("Bad key!");
+
+ return 0;
+}
+
+static void test_proc_cmdline_given(bool flip_initrd) {
+ log_info("/* %s (flip: %s) */", __func__, yes_no(flip_initrd));
+
+ if (flip_initrd)
+ in_initrd_force(!in_initrd());
+
+ bool t = true, f = false;
+ assert_se(proc_cmdline_parse_given("foo_bar=quux wuff-piep=\"tuet \" rd.zumm",
+ parse_item_given, &t, PROC_CMDLINE_STRIP_RD_PREFIX) >= 0);
+
+ assert_se(proc_cmdline_parse_given("foo_bar=quux wuff-piep=\"tuet \" rd.zumm",
+ parse_item_given, &f, 0) >= 0);
+
+
+ if (flip_initrd)
+ in_initrd_force(!in_initrd());
}
static void test_proc_cmdline_get_key(void) {
_cleanup_free_ char *value = NULL;
+ log_info("/* %s */", __func__);
putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm");
assert_se(proc_cmdline_get_key("", 0, &value) == -EINVAL);
@@ -79,6 +120,7 @@ static void test_proc_cmdline_get_key(void) {
static void test_proc_cmdline_get_bool(void) {
bool value = false;
+ log_info("/* %s */", __func__);
putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar bar-waldo=1 x_y-z=0 quux=miep");
assert_se(proc_cmdline_get_bool("", &value) == -EINVAL);
@@ -96,6 +138,7 @@ static void test_proc_cmdline_get_bool(void) {
#endif // 0
static void test_proc_cmdline_key_streq(void) {
+ log_info("/* %s */", __func__);
assert_se(proc_cmdline_key_streq("", ""));
assert_se(proc_cmdline_key_streq("a", "a"));
@@ -113,6 +156,7 @@ static void test_proc_cmdline_key_streq(void) {
#if 0 /// UNNEEDED by elogind
static void test_proc_cmdline_key_startswith(void) {
+ log_info("/* %s */", __func__);
assert_se(proc_cmdline_key_startswith("", ""));
assert_se(proc_cmdline_key_startswith("x", ""));
@@ -126,6 +170,24 @@ static void test_proc_cmdline_key_startswith(void) {
assert_se(proc_cmdline_key_startswith("foo-bar", "foo_"));
assert_se(!proc_cmdline_key_startswith("foo-bar", "foo_xx"));
}
+
+static void test_runlevel_to_target(void) {
+ log_info("/* %s */", __func__);
+
+ in_initrd_force(false);
+ assert_se(streq_ptr(runlevel_to_target(NULL), NULL));
+ assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL));
+ assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL));
+ assert_se(streq_ptr(runlevel_to_target("3"), SPECIAL_MULTI_USER_TARGET));
+ assert_se(streq_ptr(runlevel_to_target("rd.rescue"), NULL));
+
+ in_initrd_force(true);
+ assert_se(streq_ptr(runlevel_to_target(NULL), NULL));
+ assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL));
+ assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL));
+ assert_se(streq_ptr(runlevel_to_target("3"), NULL));
+ assert_se(streq_ptr(runlevel_to_target("rd.rescue"), SPECIAL_RESCUE_TARGET));
+}
#endif // 0
int main(void) {
@@ -133,6 +195,12 @@ int main(void) {
log_open();
test_proc_cmdline_parse();
+#if 0 /// UNNEEDED by elogind
+ test_proc_cmdline_override();
+ test_proc_cmdline_given(false);
+ /* Repeat the same thing, but now flip our ininitrdness */
+ test_proc_cmdline_given(true);
+#endif // 0
test_proc_cmdline_key_streq();
#if 0 /// UNNEEDED by elogind
test_proc_cmdline_key_startswith();
diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c
index f19875d..6f0472d 100644
--- a/src/test/test-process-util.c
+++ b/src/test/test-process-util.c
@@ -27,6 +27,9 @@
#include "util.h"
#include "virt.h"
+/// Additional includes needed by elogind
+#include "locale-util.h"
+
static void test_get_process_comm(pid_t pid) {
struct stat st;
_cleanup_free_ char *a = NULL, *c = NULL, *d = NULL, *f = NULL, *i = NULL;
@@ -113,12 +116,18 @@ static void test_get_process_comm_escape(void) {
test_get_process_comm_escape_one("foo", "foo");
test_get_process_comm_escape_one("012345678901234", "012345678901234");
test_get_process_comm_escape_one("0123456789012345", "012345678901234");
+#if 1 /// elogind supports systems with non-UTF-8 locales, the next would fail there
+ if (is_locale_utf8()) {
+#endif // 1
test_get_process_comm_escape_one("äöüß", "\\303\\244\\303…");
test_get_process_comm_escape_one("xäöüß", "x\\303\\244…");
test_get_process_comm_escape_one("xxäöüß", "xx\\303\\244…");
test_get_process_comm_escape_one("xxxäöüß", "xxx\\303\\244…");
test_get_process_comm_escape_one("xxxxäöüß", "xxxx\\303\\244…");
test_get_process_comm_escape_one("xxxxxäöüß", "xxxxx\\303…");
+#if 1 /// elogind supports systems with non-UTF-8 locales, the previous would fail there
+ }
+#endif // 1
assert_se(prctl(PR_SET_NAME, saved) >= 0);
}
diff --git a/src/test/test-set-disable-mempool.c b/src/test/test-set-disable-mempool.c
new file mode 100644
index 0000000..b259c95
--- /dev/null
+++ b/src/test/test-set-disable-mempool.c
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <pthread.h>
+
+#include "process-util.h"
+#include "set.h"
+//#include "tests.h"
+
+#define NUM 100
+
+static void* thread(void *p) {
+ Set **s = p;
+
+ assert_se(s);
+ assert_se(*s);
+
+ assert_se(!is_main_thread());
+ assert_se(set_size(*s) == NUM);
+ *s = set_free(*s);
+
+ return NULL;
+}
+
+static void test_one(const char *val) {
+ pthread_t t;
+ int x[NUM] = {};
+ unsigned i;
+ Set *s;
+
+ log_info("Testing with SYSTEMD_MEMPOOL=%s", val);
+ assert_se(setenv("SYSTEMD_MEMPOOL", val, true) == 0);
+ assert_se(is_main_thread());
+
+ assert_se(s = set_new(NULL));
+ for (i = 0; i < NUM; i++)
+ assert_se(set_put(s, &x[i]));
+
+ assert_se(pthread_create(&t, NULL, thread, &s) == 0);
+ assert_se(pthread_join(t, NULL) == 0);
+
+ assert_se(!s);
+}
+
+int main(int argc, char *argv[]) {
+ log_set_max_level(LOG_DEBUG);
+ log_parse_environment();
+ log_open();
+
+ test_one("0");
+ /* The value $SYSTEMD_MEMPOOL= is cached. So the following
+ * test should also succeed. */
+ test_one("1");
+
+ return 0;
+}
diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c
index 202af8b..54f9b29 100644
--- a/src/test/test-string-util.c
+++ b/src/test/test-string-util.c
@@ -5,6 +5,7 @@
#include "macro.h"
#include "string-util.h"
#include "strv.h"
+//#include "tests.h"
#include "utf8.h"
static void test_string_erase(void) {
@@ -30,6 +31,64 @@ static void test_string_erase(void) {
assert_se(x[9] == '\0');
}
+static void test_free_and_strndup_one(char **t, const char *src, size_t l, const char *expected, bool change) {
+ int r;
+
+ log_debug("%s: \"%s\", \"%s\", %zd (expect \"%s\", %s)",
+ __func__, strnull(*t), strnull(src), l, strnull(expected), yes_no(change));
+
+ r = free_and_strndup(t, src, l);
+ assert_se(streq_ptr(*t, expected));
+ assert_se(r == change); /* check that change occurs only when necessary */
+}
+
+static void test_free_and_strndup(void) {
+ static const struct test_case {
+ const char *src;
+ size_t len;
+ const char *expected;
+ } cases[] = {
+ {"abc", 0, ""},
+ {"abc", 0, ""},
+ {"abc", 1, "a"},
+ {"abc", 2, "ab"},
+ {"abc", 3, "abc"},
+ {"abc", 4, "abc"},
+ {"abc", 5, "abc"},
+ {"abc", 5, "abc"},
+ {"abc", 4, "abc"},
+ {"abc", 3, "abc"},
+ {"abc", 2, "ab"},
+ {"abc", 1, "a"},
+ {"abc", 0, ""},
+
+ {"", 0, ""},
+ {"", 1, ""},
+ {"", 2, ""},
+ {"", 0, ""},
+ {"", 1, ""},
+ {"", 2, ""},
+ {"", 2, ""},
+ {"", 1, ""},
+ {"", 0, ""},
+
+ {NULL, 0, NULL},
+
+ {"foo", 3, "foo"},
+ {"foobar", 6, "foobar"},
+ };
+
+ _cleanup_free_ char *t = NULL;
+ const char *prev_expected = t;
+
+ for (unsigned i = 0; i < ELEMENTSOF(cases); i++) {
+ test_free_and_strndup_one(&t,
+ cases[i].src, cases[i].len, cases[i].expected,
+ !streq_ptr(cases[i].expected, prev_expected));
+ prev_expected = t;
+ }
+}
+
#if 0 /// UNNEEDED by elogind
static void test_ascii_strcasecmp_n(void) {
@@ -112,6 +171,9 @@ static void test_cellescape(void) {
assert_se(streq(cellescape(buf, 1, "\020"), ""));
assert_se(streq(cellescape(buf, 2, "\020"), "."));
assert_se(streq(cellescape(buf, 3, "\020"), ".."));
+#if 1 /// elogind supports systems with non-UTF-8 locales, the next would fail there
+ if (is_locale_utf8()) {
+#endif // 1
assert_se(streq(cellescape(buf, 4, "\020"), "…"));
assert_se(streq(cellescape(buf, 5, "\020"), "\\020"));
@@ -119,26 +181,47 @@ static void test_cellescape(void) {
assert_se(streq(cellescape(buf, 6, "1234\020"), "12…"));
assert_se(streq(cellescape(buf, 7, "1234\020"), "123…"));
assert_se(streq(cellescape(buf, 8, "1234\020"), "1234…"));
+#if 1 /// elogind supports systems with non-UTF-8 locales, the previous would fail there
+ }
+#endif // 1
assert_se(streq(cellescape(buf, 9, "1234\020"), "1234\\020"));
assert_se(streq(cellescape(buf, 1, "\t\n"), ""));
assert_se(streq(cellescape(buf, 2, "\t\n"), "."));
assert_se(streq(cellescape(buf, 3, "\t\n"), ".."));
+#if 1 /// elogind supports systems with non-UTF-8 locales, the next would fail there
+ if (is_locale_utf8()) {
+#endif // 1
assert_se(streq(cellescape(buf, 4, "\t\n"), "…"));
+#if 1 /// elogind supports systems with non-UTF-8 locales, the previous would fail there
+ }
+#endif // 1
assert_se(streq(cellescape(buf, 5, "\t\n"), "\\t\\n"));
+#if 1 /// elogind supports systems with non-UTF-8 locales, the next would fail there
+ if (is_locale_utf8()) {
+#endif // 1
assert_se(streq(cellescape(buf, 5, "1234\t\n"), "1…"));
assert_se(streq(cellescape(buf, 6, "1234\t\n"), "12…"));
assert_se(streq(cellescape(buf, 7, "1234\t\n"), "123…"));
assert_se(streq(cellescape(buf, 8, "1234\t\n"), "1234…"));
+#if 1 /// elogind supports systems with non-UTF-8 locales, the previous would fail there
+ }
+#endif // 1
assert_se(streq(cellescape(buf, 9, "1234\t\n"), "1234\\t\\n"));
+#if 1 /// elogind supports systems with non-UTF-8 locales, the next would fail there
+ if (is_locale_utf8()) {
+#endif // 1
assert_se(streq(cellescape(buf, 4, "x\t\020\n"), "…"));
assert_se(streq(cellescape(buf, 5, "x\t\020\n"), "x…"));
assert_se(streq(cellescape(buf, 6, "x\t\020\n"), "x…"));
assert_se(streq(cellescape(buf, 7, "x\t\020\n"), "x\\t…"));
assert_se(streq(cellescape(buf, 8, "x\t\020\n"), "x\\t…"));
assert_se(streq(cellescape(buf, 9, "x\t\020\n"), "x\\t…"));
+#if 1 /// elogind supports systems with non-UTF-8 locales, the previous would fail there
+ }
+#endif // 1
assert_se(streq(cellescape(buf, 10, "x\t\020\n"), "x\\t\\020\\n"));
assert_se(streq(cellescape(buf, 6, "1\011"), "1\\t"));
@@ -497,11 +580,39 @@ static void test_memory_startswith(void) {
assert_se(streq(memory_startswith("xxx", 4, "x"), "xx"));
assert_se(streq(memory_startswith("xxx", 4, "xx"), "x"));
assert_se(streq(memory_startswith("xxx", 4, "xxx"), ""));
+#if 0 /// This is not used anywhere in elogind and thus masked.
assert_se(!memory_startswith("xxx", 4, "xxxx"));
+#endif // 0
}
+#if 0 /// This is not used anywhere in elogind and thus masked.
+static void test_memory_startswith_no_case(void) {
+ assert_se(streq(memory_startswith_no_case("", 0, ""), ""));
+ assert_se(streq(memory_startswith_no_case("", 1, ""), ""));
+ assert_se(streq(memory_startswith_no_case("x", 2, ""), "x"));
+ assert_se(streq(memory_startswith_no_case("X", 2, ""), "X"));
+ assert_se(!memory_startswith_no_case("", 1, "X"));
+ assert_se(!memory_startswith_no_case("", 1, "xxxxXXXX"));
+ assert_se(streq(memory_startswith_no_case("xxx", 4, "X"), "xx"));
+ assert_se(streq(memory_startswith_no_case("XXX", 4, "x"), "XX"));
+ assert_se(streq(memory_startswith_no_case("XXX", 4, "X"), "XX"));
+ assert_se(streq(memory_startswith_no_case("xxx", 4, "XX"), "x"));
+ assert_se(streq(memory_startswith_no_case("XXX", 4, "xx"), "X"));
+ assert_se(streq(memory_startswith_no_case("XXX", 4, "XX"), "X"));
+ assert_se(streq(memory_startswith_no_case("xxx", 4, "XXX"), ""));
+ assert_se(streq(memory_startswith_no_case("XXX", 4, "xxx"), ""));
+ assert_se(streq(memory_startswith_no_case("XXX", 4, "XXX"), ""));
+
+ assert_se(memory_startswith_no_case((char[2]){'x', 'x'}, 2, "xx"));
+ assert_se(memory_startswith_no_case((char[2]){'x', 'X'}, 2, "xX"));
+ assert_se(memory_startswith_no_case((char[2]){'X', 'x'}, 2, "Xx"));
+ assert_se(memory_startswith_no_case((char[2]){'X', 'X'}, 2, "XX"));
+}
+#endif // 0
+
int main(int argc, char *argv[]) {
test_string_erase();
+ test_free_and_strndup();
#if 0 /// UNNEEDED by elogind
test_ascii_strcasecmp_n();
test_ascii_strcasecmp_nn();
@@ -533,6 +644,9 @@ int main(int argc, char *argv[]) {
test_first_word();
test_strlen_ptr();
test_memory_startswith();
-
+#if 0 /// This is not used anywhere in elogind and thus masked.
+ test_memory_startswith_no_case();
+#endif // 0
+
return 0;
}
diff --git a/tools/gdb-sd_dump_hashmaps.py b/tools/gdb-sd_dump_hashmaps.py
index 27572be..0dcf684 100755
--- a/tools/gdb-sd_dump_hashmaps.py
+++ b/tools/gdb-sd_dump_hashmaps.py
@@ -2,6 +2,8 @@
# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
# SPDX-License-Identifier: LGPL-2.1+
+from __future__ import print_function
+
import gdb
class sd_dump_hashmaps(gdb.Command):
@@ -19,7 +21,7 @@ class sd_dump_hashmaps(gdb.Command):
ulong_t = gdb.lookup_type("unsigned long")
debug_offset = gdb.parse_and_eval("(unsigned long)&((HashmapBase*)0)->debug")
- print "type, hash, indirect, entries, max_entries, buckets, creator"
+ print("type, hash, indirect, entries, max_entries, buckets, creator")
while d:
h = gdb.parse_and_eval("(HashmapBase*)((char*)%d - %d)" % (int(d.cast(ulong_t)), debug_offset))
@@ -34,7 +36,7 @@ class sd_dump_hashmaps(gdb.Command):
t = ["plain", "ordered", "set"][int(h["type"])]
- print "{}, {}, {}, {}, {}, {}, {} ({}:{})".format(t, h["hash_ops"], bool(h["has_indirect"]), n_entries, d["max_entries"], n_buckets, d["func"], d["file"], d["line"])
+ print("{}, {}, {}, {}, {}, {}, {} ({}:{})".format(t, h["hash_ops"], bool(h["has_indirect"]), n_entries, d["max_entries"], n_buckets, d["func"], d["file"], d["line"]))
if arg != "" and n_entries > 0:
dib_raw_addr = storage_ptr + (all_entry_sizes[h["type"]] * n_buckets)
@@ -46,10 +48,10 @@ class sd_dump_hashmaps(gdb.Command):
for dib in sorted(iter(histogram)):
if dib != 255:
- print "{:>3} {:>8} {} of entries".format(dib, histogram[dib], 100.0*histogram[dib]/n_entries)
+ print("{:>3} {:>8} {} of entries".format(dib, histogram[dib], 100.0*histogram[dib]/n_entries))
else:
- print "{:>3} {:>8} {} of slots".format(dib, histogram[dib], 100.0*histogram[dib]/n_buckets)
- print "mean DIB of entries: {}".format(sum([dib*histogram[dib] for dib in iter(histogram) if dib != 255])*1.0/n_entries)
+ print("{:>3} {:>8} {} of slots".format(dib, histogram[dib], 100.0*histogram[dib]/n_buckets))
+ print("mean DIB of entries: {}".format(sum([dib*histogram[dib] for dib in iter(histogram) if dib != 255])*1.0/n_entries))
blocks = []
current_len = 1
@@ -70,9 +72,9 @@ class sd_dump_hashmaps(gdb.Command):
if len(blocks) > 1 and blocks[0][0] == blocks[0][1] and blocks[-1][0] == n_buckets - 1:
blocks[0][1] += blocks[-1][1]
blocks = blocks[0:-1]
- print "max block: {}".format(max(blocks, key=lambda a: a[1]))
- print "sum block lens: {}".format(sum(b[1] for b in blocks))
- print "mean block len: {}".format((1.0 * sum(b[1] for b in blocks) / len(blocks)))
+ print("max block: {}".format(max(blocks, key=lambda a: a[1])))
+ print("sum block lens: {}".format(sum(b[1] for b in blocks)))
+ print("mean block len: {}".format((1.0 * sum(b[1] for b in blocks) / len(blocks))))
d = d["debug_list_next"]