summaryrefslogtreecommitdiffstats
path: root/src/libsystemd/sd-bus
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:35:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:35:18 +0000
commitb750101eb236130cf056c675997decbac904cc49 (patch)
treea5df1a06754bdd014cb975c051c83b01c9a97532 /src/libsystemd/sd-bus
parentInitial commit. (diff)
downloadsystemd-b750101eb236130cf056c675997decbac904cc49.tar.xz
systemd-b750101eb236130cf056c675997decbac904cc49.zip
Adding upstream version 252.22.upstream/252.22
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/libsystemd/sd-bus/bus-common-errors.c149
-rw-r--r--src/libsystemd/sd-bus/bus-common-errors.h153
-rw-r--r--src/libsystemd/sd-bus/bus-container.c104
-rw-r--r--src/libsystemd/sd-bus/bus-container.h6
-rw-r--r--src/libsystemd/sd-bus/bus-control.c941
-rw-r--r--src/libsystemd/sd-bus/bus-control.h9
-rw-r--r--src/libsystemd/sd-bus/bus-convenience.c795
-rw-r--r--src/libsystemd/sd-bus/bus-creds.c1337
-rw-r--r--src/libsystemd/sd-bus/bus-creds.h72
-rw-r--r--src/libsystemd/sd-bus/bus-dump.c650
-rw-r--r--src/libsystemd/sd-bus/bus-dump.h12
-rw-r--r--src/libsystemd/sd-bus/bus-error.c629
-rw-r--r--src/libsystemd/sd-bus/bus-error.h57
-rw-r--r--src/libsystemd/sd-bus/bus-internal.c338
-rw-r--r--src/libsystemd/sd-bus/bus-internal.h414
-rw-r--r--src/libsystemd/sd-bus/bus-introspect.c284
-rw-r--r--src/libsystemd/sd-bus/bus-introspect.h26
-rw-r--r--src/libsystemd/sd-bus/bus-kernel.c41
-rw-r--r--src/libsystemd/sd-bus/bus-kernel.h24
-rw-r--r--src/libsystemd/sd-bus/bus-match.c1074
-rw-r--r--src/libsystemd/sd-bus/bus-match.h82
-rw-r--r--src/libsystemd/sd-bus/bus-message.c4669
-rw-r--r--src/libsystemd/sd-bus/bus-message.h189
-rw-r--r--src/libsystemd/sd-bus/bus-objects.c3033
-rw-r--r--src/libsystemd/sd-bus/bus-objects.h20
-rw-r--r--src/libsystemd/sd-bus/bus-protocol.h88
-rw-r--r--src/libsystemd/sd-bus/bus-signature.c148
-rw-r--r--src/libsystemd/sd-bus/bus-signature.h10
-rw-r--r--src/libsystemd/sd-bus/bus-slot.c311
-rw-r--r--src/libsystemd/sd-bus/bus-slot.h10
-rw-r--r--src/libsystemd/sd-bus/bus-socket.c1374
-rw-r--r--src/libsystemd/sd-bus/bus-socket.h20
-rw-r--r--src/libsystemd/sd-bus/bus-track.c495
-rw-r--r--src/libsystemd/sd-bus/bus-track.h5
-rw-r--r--src/libsystemd/sd-bus/bus-type.c162
-rw-r--r--src/libsystemd/sd-bus/bus-type.h16
-rw-r--r--src/libsystemd/sd-bus/fuzz-bus-match.c89
-rw-r--r--src/libsystemd/sd-bus/fuzz-bus-match.options2
-rw-r--r--src/libsystemd/sd-bus/fuzz-bus-message.c45
-rw-r--r--src/libsystemd/sd-bus/sd-bus.c4405
-rw-r--r--src/libsystemd/sd-bus/test-bus-address.c61
-rw-r--r--src/libsystemd/sd-bus/test-bus-benchmark.c324
-rw-r--r--src/libsystemd/sd-bus/test-bus-chat.c560
-rw-r--r--src/libsystemd/sd-bus/test-bus-cleanup.c78
-rw-r--r--src/libsystemd/sd-bus/test-bus-creds.c34
-rw-r--r--src/libsystemd/sd-bus/test-bus-error.c294
-rw-r--r--src/libsystemd/sd-bus/test-bus-introspect.c33
-rw-r--r--src/libsystemd/sd-bus/test-bus-marshal.c424
-rw-r--r--src/libsystemd/sd-bus/test-bus-match.c145
-rw-r--r--src/libsystemd/sd-bus/test-bus-objects.c675
-rw-r--r--src/libsystemd/sd-bus/test-bus-queue-ref-cycle.c56
-rw-r--r--src/libsystemd/sd-bus/test-bus-server.c198
-rw-r--r--src/libsystemd/sd-bus/test-bus-signature.c147
-rw-r--r--src/libsystemd/sd-bus/test-bus-track.c151
l---------src/libsystemd/sd-bus/test-bus-vtable-cc.cc1
-rw-r--r--src/libsystemd/sd-bus/test-bus-vtable.c76
-rw-r--r--src/libsystemd/sd-bus/test-bus-watch-bind.c223
-rw-r--r--src/libsystemd/sd-bus/test-vtable-data.h132
58 files changed, 25900 insertions, 0 deletions
diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c
new file mode 100644
index 0000000..0eaedec
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-common-errors.c
@@ -0,0 +1,149 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "sd-bus.h"
+
+#include "bus-common-errors.h"
+#include "bus-error.h"
+
+BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_UNIT, ENOENT),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_UNIT_FOR_PID, ESRCH),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_UNIT_FOR_INVOCATION_ID, ENOENT),
+ SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_EXISTS, EEXIST),
+ SD_BUS_ERROR_MAP(BUS_ERROR_LOAD_FAILED, EIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_BAD_UNIT_SETTING, ENOEXEC),
+ SD_BUS_ERROR_MAP(BUS_ERROR_JOB_FAILED, EREMOTEIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_JOB, ENOENT),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NOT_SUBSCRIBED, EINVAL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_ALREADY_SUBSCRIBED, EINVAL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_ONLY_BY_DEPENDENCY, EINVAL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, EDEADLK),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, EDEADLK),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, EDEADLK),
+ SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_MASKED, ERFKILL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_GENERATED, EADDRNOTAVAIL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_LINKED, ELOOP),
+ SD_BUS_ERROR_MAP(BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, EBADR),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_ISOLATION, EPERM),
+ SD_BUS_ERROR_MAP(BUS_ERROR_SHUTTING_DOWN, ECANCELED),
+ SD_BUS_ERROR_MAP(BUS_ERROR_SCOPE_NOT_RUNNING, EHOSTDOWN),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DYNAMIC_USER, ESRCH),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NOT_REFERENCED, EUNATCH),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DISK_FULL, ENOSPC),
+
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_MACHINE, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_MACHINE_FOR_PID, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_MACHINE_EXISTS, EEXIST),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRIVATE_NETWORKING, ENOSYS),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_USER_MAPPING, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_GROUP_MAPPING, ENXIO),
+
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_PORTABLE_IMAGE, ENOENT),
+ SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE, EMEDIUMTYPE),
+
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SESSION, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SESSION_FOR_PID, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_USER, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_USER_FOR_PID, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SEAT, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_SESSION_NOT_ON_SEAT, EINVAL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NOT_IN_CONTROL, EINVAL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DEVICE_IS_TAKEN, EINVAL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DEVICE_NOT_TAKEN, EINVAL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_OPERATION_IN_PROGRESS, EINPROGRESS),
+ SD_BUS_ERROR_MAP(BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, EOPNOTSUPP),
+ SD_BUS_ERROR_MAP(BUS_ERROR_SESSION_BUSY, EBUSY),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NOT_YOUR_DEVICE, EPERM),
+
+ SD_BUS_ERROR_MAP(BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, EALREADY),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_NTP_SUPPORT, EOPNOTSUPP),
+
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_PROCESS, ESRCH),
+
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_NAME_SERVERS, ESRCH),
+ SD_BUS_ERROR_MAP(BUS_ERROR_INVALID_REPLY, EINVAL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_RR, ENOENT),
+ SD_BUS_ERROR_MAP(BUS_ERROR_CNAME_LOOP, EDEADLK),
+ SD_BUS_ERROR_MAP(BUS_ERROR_ABORTED, ECANCELED),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SERVICE, EUNATCH),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNSSEC_FAILED, EHOSTUNREACH),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_TRUST_ANCHOR, EHOSTUNREACH),
+ SD_BUS_ERROR_MAP(BUS_ERROR_RR_TYPE_UNSUPPORTED, EOPNOTSUPP),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SOURCE, ESRCH),
+ SD_BUS_ERROR_MAP(BUS_ERROR_STUB_LOOP, ELOOP),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DNSSD_SERVICE, ENOENT),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNSSD_SERVICE_EXISTS, EEXIST),
+
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_FORMERR, EBADMSG),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_SERVFAIL, EHOSTDOWN),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_NXDOMAIN, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_NOTIMP, ENOSYS),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_REFUSED, EACCES),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_YXDOMAIN, EEXIST),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_YRRSET, EEXIST),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_NXRRSET, ENOENT),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_NOTAUTH, EACCES),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_NOTZONE, EREMOTE),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_BADVERS, EBADMSG),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_BADKEY, EKEYREJECTED),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_BADTIME, EBADMSG),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_BADMODE, EBADMSG),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_BADNAME, EBADMSG),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_BADALG, EBADMSG),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_BADTRUNC, EBADMSG),
+ SD_BUS_ERROR_MAP(BUS_ERROR_DNS_BADCOOKIE, EBADR),
+
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY),
+
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRODUCT_UUID, EOPNOTSUPP),
+ SD_BUS_ERROR_MAP(BUS_ERROR_FILE_IS_PROTECTED, EACCES),
+ SD_BUS_ERROR_MAP(BUS_ERROR_READ_ONLY_FILESYSTEM, EROFS),
+
+ SD_BUS_ERROR_MAP(BUS_ERROR_SPEED_METER_INACTIVE, EOPNOTSUPP),
+ SD_BUS_ERROR_MAP(BUS_ERROR_UNMANAGED_INTERFACE, EOPNOTSUPP),
+
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_HOME, EEXIST),
+ SD_BUS_ERROR_MAP(BUS_ERROR_UID_IN_USE, EEXIST),
+ SD_BUS_ERROR_MAP(BUS_ERROR_USER_NAME_EXISTS, EEXIST),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_EXISTS, EEXIST),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ALREADY_ACTIVE, EALREADY),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ALREADY_FIXATED, EALREADY),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_UNFIXATED, EADDRNOTAVAIL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_ACTIVE, EALREADY),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ABSENT, EREMOTE),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_BUSY, EBUSY),
+ SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PASSWORD, ENOKEY),
+ SD_BUS_ERROR_MAP(BUS_ERROR_LOW_PASSWORD_QUALITY, EUCLEAN),
+ SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN, EBADSLT),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_NEEDED, ENOANO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, ERFKILL),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED, EMEDIUMTYPE),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_USER_VERIFICATION_NEEDED, ENOCSI),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_ACTION_TIMEOUT, ENOSTR),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_LOCKED, EOWNERDEAD),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN, ENOLCK),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT, ETOOMANYREFS),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT, EUCLEAN),
+ SD_BUS_ERROR_MAP(BUS_ERROR_BAD_SIGNATURE, EKEYREJECTED),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_MISMATCH, EUCLEAN),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_DOWNGRADE, ESTALE),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_SIGNED, EROFS),
+ SD_BUS_ERROR_MAP(BUS_ERROR_BAD_HOME_SIZE, ERANGE),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRIVATE_KEY, ENOPKG),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_LOCKED, ENOEXEC),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_LOCKED, ENOEXEC),
+ SD_BUS_ERROR_MAP(BUS_ERROR_TOO_MANY_OPERATIONS, ENOBUFS),
+ SD_BUS_ERROR_MAP(BUS_ERROR_AUTHENTICATION_LIMIT_HIT, ETOOMANYREFS),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_CANT_AUTHENTICATE, EKEYREVOKED),
+ SD_BUS_ERROR_MAP(BUS_ERROR_HOME_IN_USE, EADDRINUSE),
+ SD_BUS_ERROR_MAP(BUS_ERROR_REBALANCE_NOT_NEEDED, EALREADY),
+
+ SD_BUS_ERROR_MAP_END
+};
diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h
new file mode 100644
index 0000000..b6c2e93
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-common-errors.h
@@ -0,0 +1,153 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "bus-error.h"
+
+#define BUS_ERROR_NO_SUCH_UNIT "org.freedesktop.systemd1.NoSuchUnit"
+#define BUS_ERROR_NO_UNIT_FOR_PID "org.freedesktop.systemd1.NoUnitForPID"
+#define BUS_ERROR_NO_UNIT_FOR_INVOCATION_ID "org.freedesktop.systemd1.NoUnitForInvocationID"
+#define BUS_ERROR_UNIT_EXISTS "org.freedesktop.systemd1.UnitExists"
+#define BUS_ERROR_LOAD_FAILED "org.freedesktop.systemd1.LoadFailed"
+#define BUS_ERROR_BAD_UNIT_SETTING "org.freedesktop.systemd1.BadUnitSetting"
+#define BUS_ERROR_JOB_FAILED "org.freedesktop.systemd1.JobFailed"
+#define BUS_ERROR_NO_SUCH_JOB "org.freedesktop.systemd1.NoSuchJob"
+#define BUS_ERROR_NOT_SUBSCRIBED "org.freedesktop.systemd1.NotSubscribed"
+#define BUS_ERROR_ALREADY_SUBSCRIBED "org.freedesktop.systemd1.AlreadySubscribed"
+#define BUS_ERROR_ONLY_BY_DEPENDENCY "org.freedesktop.systemd1.OnlyByDependency"
+#define BUS_ERROR_TRANSACTION_JOBS_CONFLICTING "org.freedesktop.systemd1.TransactionJobsConflicting"
+#define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic"
+#define BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE "org.freedesktop.systemd1.TransactionIsDestructive"
+#define BUS_ERROR_UNIT_MASKED "org.freedesktop.systemd1.UnitMasked"
+#define BUS_ERROR_UNIT_GENERATED "org.freedesktop.systemd1.UnitGenerated"
+#define BUS_ERROR_UNIT_LINKED "org.freedesktop.systemd1.UnitLinked"
+#define BUS_ERROR_UNIT_BAD_PATH "org.freedesktop.systemd1.UnitBadPath"
+#define BUS_ERROR_JOB_TYPE_NOT_APPLICABLE "org.freedesktop.systemd1.JobTypeNotApplicable"
+#define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation"
+#define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown"
+#define BUS_ERROR_SCOPE_NOT_RUNNING "org.freedesktop.systemd1.ScopeNotRunning"
+#define BUS_ERROR_NO_SUCH_DYNAMIC_USER "org.freedesktop.systemd1.NoSuchDynamicUser"
+#define BUS_ERROR_NOT_REFERENCED "org.freedesktop.systemd1.NotReferenced"
+#define BUS_ERROR_DISK_FULL "org.freedesktop.systemd1.DiskFull"
+#define BUS_ERROR_NOTHING_TO_CLEAN "org.freedesktop.systemd1.NothingToClean"
+#define BUS_ERROR_UNIT_BUSY "org.freedesktop.systemd1.UnitBusy"
+#define BUS_ERROR_UNIT_INACTIVE "org.freedesktop.systemd1.UnitInactive"
+#define BUS_ERROR_FREEZE_CANCELLED "org.freedesktop.systemd1.FreezeCancelled"
+
+#define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine"
+#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage"
+#define BUS_ERROR_NO_MACHINE_FOR_PID "org.freedesktop.machine1.NoMachineForPID"
+#define BUS_ERROR_MACHINE_EXISTS "org.freedesktop.machine1.MachineExists"
+#define BUS_ERROR_NO_PRIVATE_NETWORKING "org.freedesktop.machine1.NoPrivateNetworking"
+#define BUS_ERROR_NO_SUCH_USER_MAPPING "org.freedesktop.machine1.NoSuchUserMapping"
+#define BUS_ERROR_NO_SUCH_GROUP_MAPPING "org.freedesktop.machine1.NoSuchGroupMapping"
+
+#define BUS_ERROR_NO_SUCH_PORTABLE_IMAGE "org.freedesktop.portable1.NoSuchImage"
+#define BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE "org.freedesktop.portable1.BadImageType"
+
+#define BUS_ERROR_NO_SUCH_SESSION "org.freedesktop.login1.NoSuchSession"
+#define BUS_ERROR_NO_SESSION_FOR_PID "org.freedesktop.login1.NoSessionForPID"
+#define BUS_ERROR_NO_SUCH_USER "org.freedesktop.login1.NoSuchUser"
+#define BUS_ERROR_NO_USER_FOR_PID "org.freedesktop.login1.NoUserForPID"
+#define BUS_ERROR_NO_SUCH_SEAT "org.freedesktop.login1.NoSuchSeat"
+#define BUS_ERROR_SESSION_NOT_ON_SEAT "org.freedesktop.login1.SessionNotOnSeat"
+#define BUS_ERROR_NOT_IN_CONTROL "org.freedesktop.login1.NotInControl"
+#define BUS_ERROR_DEVICE_IS_TAKEN "org.freedesktop.login1.DeviceIsTaken"
+#define BUS_ERROR_DEVICE_NOT_TAKEN "org.freedesktop.login1.DeviceNotTaken"
+#define BUS_ERROR_OPERATION_IN_PROGRESS "org.freedesktop.login1.OperationInProgress"
+#define BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED "org.freedesktop.login1.SleepVerbNotSupported"
+#define BUS_ERROR_SESSION_BUSY "org.freedesktop.login1.SessionBusy"
+#define BUS_ERROR_NOT_YOUR_DEVICE "org.freedesktop.login1.NotYourDevice"
+
+#define BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED "org.freedesktop.timedate1.AutomaticTimeSyncEnabled"
+#define BUS_ERROR_NO_NTP_SUPPORT "org.freedesktop.timedate1.NoNTPSupport"
+
+#define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess"
+
+#define BUS_ERROR_NO_NAME_SERVERS "org.freedesktop.resolve1.NoNameServers"
+#define BUS_ERROR_INVALID_REPLY "org.freedesktop.resolve1.InvalidReply"
+#define BUS_ERROR_NO_SUCH_RR "org.freedesktop.resolve1.NoSuchRR"
+#define BUS_ERROR_CNAME_LOOP "org.freedesktop.resolve1.CNameLoop"
+#define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted"
+#define BUS_ERROR_NO_SUCH_SERVICE "org.freedesktop.resolve1.NoSuchService"
+#define BUS_ERROR_DNSSEC_FAILED "org.freedesktop.resolve1.DnssecFailed"
+#define BUS_ERROR_NO_TRUST_ANCHOR "org.freedesktop.resolve1.NoTrustAnchor"
+#define BUS_ERROR_RR_TYPE_UNSUPPORTED "org.freedesktop.resolve1.ResourceRecordTypeUnsupported"
+#define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink"
+#define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy"
+#define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown"
+#define BUS_ERROR_NO_SOURCE "org.freedesktop.resolve1.NoSource"
+#define BUS_ERROR_STUB_LOOP "org.freedesktop.resolve1.StubLoop"
+#define BUS_ERROR_NO_SUCH_DNSSD_SERVICE "org.freedesktop.resolve1.NoSuchDnssdService"
+#define BUS_ERROR_DNSSD_SERVICE_EXISTS "org.freedesktop.resolve1.DnssdServiceExists"
+
+#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError."
+#define BUS_ERROR_DNS_FORMERR _BUS_ERROR_DNS "FORMERR"
+#define BUS_ERROR_DNS_SERVFAIL _BUS_ERROR_DNS "SERVFAIL"
+#define BUS_ERROR_DNS_NXDOMAIN _BUS_ERROR_DNS "NXDOMAIN"
+#define BUS_ERROR_DNS_NOTIMP _BUS_ERROR_DNS "NOTIMP"
+#define BUS_ERROR_DNS_REFUSED _BUS_ERROR_DNS "REFUSED"
+#define BUS_ERROR_DNS_YXDOMAIN _BUS_ERROR_DNS "YXDOMAIN"
+#define BUS_ERROR_DNS_YRRSET _BUS_ERROR_DNS "YRRSET"
+#define BUS_ERROR_DNS_NXRRSET _BUS_ERROR_DNS "NXRRSET"
+#define BUS_ERROR_DNS_NOTAUTH _BUS_ERROR_DNS "NOTAUTH"
+#define BUS_ERROR_DNS_NOTZONE _BUS_ERROR_DNS "NOTZONE"
+#define BUS_ERROR_DNS_BADVERS _BUS_ERROR_DNS "BADVERS"
+#define BUS_ERROR_DNS_BADKEY _BUS_ERROR_DNS "BADKEY"
+#define BUS_ERROR_DNS_BADTIME _BUS_ERROR_DNS "BADTIME"
+#define BUS_ERROR_DNS_BADMODE _BUS_ERROR_DNS "BADMODE"
+#define BUS_ERROR_DNS_BADNAME _BUS_ERROR_DNS "BADNAME"
+#define BUS_ERROR_DNS_BADALG _BUS_ERROR_DNS "BADALG"
+#define BUS_ERROR_DNS_BADTRUNC _BUS_ERROR_DNS "BADTRUNC"
+#define BUS_ERROR_DNS_BADCOOKIE _BUS_ERROR_DNS "BADCOOKIE"
+
+#define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer"
+#define BUS_ERROR_TRANSFER_IN_PROGRESS "org.freedesktop.import1.TransferInProgress"
+
+#define BUS_ERROR_NO_PRODUCT_UUID "org.freedesktop.hostname1.NoProductUUID"
+#define BUS_ERROR_FILE_IS_PROTECTED "org.freedesktop.hostname1.FileIsProtected"
+#define BUS_ERROR_READ_ONLY_FILESYSTEM "org.freedesktop.hostname1.ReadOnlyFilesystem"
+
+#define BUS_ERROR_SPEED_METER_INACTIVE "org.freedesktop.network1.SpeedMeterInactive"
+#define BUS_ERROR_UNMANAGED_INTERFACE "org.freedesktop.network1.UnmanagedInterface"
+
+#define BUS_ERROR_NO_SUCH_HOME "org.freedesktop.home1.NoSuchHome"
+#define BUS_ERROR_UID_IN_USE "org.freedesktop.home1.UIDInUse"
+#define BUS_ERROR_USER_NAME_EXISTS "org.freedesktop.home1.UserNameExists"
+#define BUS_ERROR_HOME_EXISTS "org.freedesktop.home1.HomeExists"
+#define BUS_ERROR_HOME_ALREADY_ACTIVE "org.freedesktop.home1.HomeAlreadyActive"
+#define BUS_ERROR_HOME_ALREADY_FIXATED "org.freedesktop.home1.HomeAlreadyFixated"
+#define BUS_ERROR_HOME_UNFIXATED "org.freedesktop.home1.HomeUnfixated"
+#define BUS_ERROR_HOME_NOT_ACTIVE "org.freedesktop.home1.HomeNotActive"
+#define BUS_ERROR_HOME_ABSENT "org.freedesktop.home1.HomeAbsent"
+#define BUS_ERROR_HOME_BUSY "org.freedesktop.home1.HomeBusy"
+#define BUS_ERROR_BAD_PASSWORD "org.freedesktop.home1.BadPassword"
+#define BUS_ERROR_BAD_RECOVERY_KEY "org.freedesktop.home1.BadRecoveryKey"
+#define BUS_ERROR_LOW_PASSWORD_QUALITY "org.freedesktop.home1.LowPasswordQuality"
+#define BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN "org.freedesktop.home1.BadPasswordAndNoToken"
+#define BUS_ERROR_TOKEN_PIN_NEEDED "org.freedesktop.home1.TokenPinNeeded"
+#define BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED \
+ "org.freedesktop.home1.TokenProtectedAuthenticationPathNeeded"
+#define BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED "org.freedesktop.home1.TokenUserPresenceNeeded"
+#define BUS_ERROR_TOKEN_USER_VERIFICATION_NEEDED \
+ "org.freedesktop.home1.TokenUserVerificationNeeded"
+#define BUS_ERROR_TOKEN_ACTION_TIMEOUT "org.freedesktop.home1.TokenActionTimeout"
+#define BUS_ERROR_TOKEN_PIN_LOCKED "org.freedesktop.home1.TokenPinLocked"
+#define BUS_ERROR_TOKEN_BAD_PIN "org.freedesktop.home1.BadPin"
+#define BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT "org.freedesktop.home1.BadPinFewTriesLeft"
+#define BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT "org.freedesktop.home1.BadPinOneTryLeft"
+#define BUS_ERROR_BAD_SIGNATURE "org.freedesktop.home1.BadSignature"
+#define BUS_ERROR_HOME_RECORD_MISMATCH "org.freedesktop.home1.RecordMismatch"
+#define BUS_ERROR_HOME_RECORD_DOWNGRADE "org.freedesktop.home1.RecordDowngrade"
+#define BUS_ERROR_HOME_RECORD_SIGNED "org.freedesktop.home1.RecordSigned"
+#define BUS_ERROR_BAD_HOME_SIZE "org.freedesktop.home1.BadHomeSize"
+#define BUS_ERROR_NO_PRIVATE_KEY "org.freedesktop.home1.NoPrivateKey"
+#define BUS_ERROR_HOME_LOCKED "org.freedesktop.home1.HomeLocked"
+#define BUS_ERROR_HOME_NOT_LOCKED "org.freedesktop.home1.HomeNotLocked"
+#define BUS_ERROR_NO_DISK_SPACE "org.freedesktop.home1.NoDiskSpace"
+#define BUS_ERROR_TOO_MANY_OPERATIONS "org.freedesktop.home1.TooManyOperations"
+#define BUS_ERROR_AUTHENTICATION_LIMIT_HIT "org.freedesktop.home1.AuthenticationLimitHit"
+#define BUS_ERROR_HOME_CANT_AUTHENTICATE "org.freedesktop.home1.HomeCantAuthenticate"
+#define BUS_ERROR_HOME_IN_USE "org.freedesktop.home1.HomeInUse"
+#define BUS_ERROR_REBALANCE_NOT_NEEDED "org.freedesktop.home1.RebalanceNotNeeded"
+
+BUS_ERROR_MAP_ELF_USE(bus_common_errors);
diff --git a/src/libsystemd/sd-bus/bus-container.c b/src/libsystemd/sd-bus/bus-container.c
new file mode 100644
index 0000000..b3c0279
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-container.c
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "bus-container.h"
+#include "bus-internal.h"
+#include "bus-socket.h"
+#include "fd-util.h"
+#include "namespace-util.h"
+#include "process-util.h"
+#include "string-util.h"
+#include "util.h"
+
+int bus_container_connect_socket(sd_bus *b) {
+ _cleanup_close_pair_ int pair[2] = { -1, -1 };
+ _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1;
+ int r, error_buf = 0;
+ pid_t child;
+ ssize_t n;
+
+ assert(b);
+ assert(b->input_fd < 0);
+ assert(b->output_fd < 0);
+ assert(b->nspid > 0 || b->machine);
+
+ if (b->nspid <= 0) {
+ log_debug("sd-bus: connecting bus%s%s to machine %s...",
+ b->description ? " " : "", strempty(b->description), b->machine);
+
+ r = container_get_leader(b->machine, &b->nspid);
+ if (r < 0)
+ return r;
+ } else
+ log_debug("sd-bus: connecting bus%s%s to namespace of PID "PID_FMT"...",
+ b->description ? " " : "", strempty(b->description), b->nspid);
+
+ r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to open namespace of PID "PID_FMT": %m", b->nspid);
+
+ b->input_fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (b->input_fd < 0)
+ return log_debug_errno(errno, "Failed to create a socket: %m");
+
+ b->input_fd = fd_move_above_stdio(b->input_fd);
+
+ b->output_fd = b->input_fd;
+
+ bus_socket_setup(b);
+
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0)
+ return log_debug_errno(errno, "Failed to create a socket pair: %m");
+
+ r = namespace_fork("(sd-buscntrns)", "(sd-buscntr)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG,
+ pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to create namespace for (sd-buscntr): %m");
+ if (r == 0) {
+ pair[0] = safe_close(pair[0]);
+
+ r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size);
+ if (r < 0) {
+ /* Try to send error up */
+ error_buf = errno;
+ (void) write(pair[1], &error_buf, sizeof(error_buf));
+ _exit(EXIT_FAILURE);
+ }
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ pair[1] = safe_close(pair[1]);
+
+ r = wait_for_terminate_and_check("(sd-buscntrns)", child, 0);
+ if (r < 0)
+ return r;
+ bool nonzero_exit_status = r != EXIT_SUCCESS;
+
+ n = read(pair[0], &error_buf, sizeof(error_buf));
+ if (n < 0)
+ return log_debug_errno(errno, "Failed to read error status from (sd-buscntr): %m");
+
+ if (n > 0) {
+ if (n != sizeof(error_buf))
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Read error status of unexpected length %zd from (sd-buscntr): %m", n);
+
+ if (error_buf < 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+ "Got unexpected error status from (sd-buscntr): %m");
+
+ if (error_buf == EINPROGRESS)
+ return 1;
+
+ if (error_buf > 0)
+ return log_debug_errno(error_buf, "(sd-buscntr) failed to connect to D-Bus socket: %m");
+ }
+
+ if (nonzero_exit_status)
+ return -EPROTO;
+
+ return bus_socket_start_auth(b);
+}
diff --git a/src/libsystemd/sd-bus/bus-container.h b/src/libsystemd/sd-bus/bus-container.h
new file mode 100644
index 0000000..cb503a5
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-container.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-bus.h"
+
+int bus_container_connect_socket(sd_bus *b);
diff --git a/src/libsystemd/sd-bus/bus-control.c b/src/libsystemd/sd-bus/bus-control.c
new file mode 100644
index 0000000..d96b725
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-control.c
@@ -0,0 +1,941 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#if HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include <errno.h>
+#include <stddef.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-control.h"
+#include "bus-internal.h"
+#include "bus-message.h"
+#include "capability-util.h"
+#include "process-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "user-util.h"
+
+_public_ int sd_bus_get_unique_name(sd_bus *bus, const char **unique) {
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(unique, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!bus->bus_client)
+ return -EINVAL;
+
+ r = bus_ensure_running(bus);
+ if (r < 0)
+ return r;
+
+ *unique = bus->unique_name;
+ return 0;
+}
+
+static int validate_request_name_parameters(
+ sd_bus *bus,
+ const char *name,
+ uint64_t flags,
+ uint32_t *ret_param) {
+
+ uint32_t param = 0;
+
+ assert(bus);
+ assert(name);
+ assert(ret_param);
+
+ assert_return(!(flags & ~(SD_BUS_NAME_ALLOW_REPLACEMENT|SD_BUS_NAME_REPLACE_EXISTING|SD_BUS_NAME_QUEUE)), -EINVAL);
+ assert_return(service_name_is_valid(name), -EINVAL);
+ assert_return(name[0] != ':', -EINVAL);
+
+ if (!bus->bus_client)
+ return -EINVAL;
+
+ /* Don't allow requesting the special driver and local names */
+ if (STR_IN_SET(name, "org.freedesktop.DBus", "org.freedesktop.DBus.Local"))
+ return -EINVAL;
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ if (flags & SD_BUS_NAME_ALLOW_REPLACEMENT)
+ param |= BUS_NAME_ALLOW_REPLACEMENT;
+ if (flags & SD_BUS_NAME_REPLACE_EXISTING)
+ param |= BUS_NAME_REPLACE_EXISTING;
+ if (!(flags & SD_BUS_NAME_QUEUE))
+ param |= BUS_NAME_DO_NOT_QUEUE;
+
+ *ret_param = param;
+
+ return 0;
+}
+
+_public_ int sd_bus_request_name(
+ sd_bus *bus,
+ const char *name,
+ uint64_t flags) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ uint32_t ret, param = 0;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(name, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ r = validate_request_name_parameters(bus, name, flags, &param);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "RequestName",
+ NULL,
+ &reply,
+ "su",
+ name,
+ param);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(reply, "u", &ret);
+ if (r < 0)
+ return r;
+
+ switch (ret) {
+
+ case BUS_NAME_ALREADY_OWNER:
+ return -EALREADY;
+
+ case BUS_NAME_EXISTS:
+ return -EEXIST;
+
+ case BUS_NAME_IN_QUEUE:
+ return 0;
+
+ case BUS_NAME_PRIMARY_OWNER:
+ return 1;
+ }
+
+ return -EIO;
+}
+
+static int default_request_name_handler(
+ sd_bus_message *m,
+ void *userdata,
+ sd_bus_error *ret_error) {
+
+ uint32_t ret;
+ int r;
+
+ assert(m);
+
+ if (sd_bus_message_is_method_error(m, NULL)) {
+ log_debug_errno(sd_bus_message_get_errno(m),
+ "Unable to request name, failing connection: %s",
+ sd_bus_message_get_error(m)->message);
+
+ bus_enter_closing(sd_bus_message_get_bus(m));
+ return 1;
+ }
+
+ r = sd_bus_message_read(m, "u", &ret);
+ if (r < 0)
+ return r;
+
+ switch (ret) {
+
+ case BUS_NAME_ALREADY_OWNER:
+ log_debug("Already owner of requested service name, ignoring.");
+ return 1;
+
+ case BUS_NAME_IN_QUEUE:
+ log_debug("In queue for requested service name.");
+ return 1;
+
+ case BUS_NAME_PRIMARY_OWNER:
+ log_debug("Successfully acquired requested service name.");
+ return 1;
+
+ case BUS_NAME_EXISTS:
+ log_debug("Requested service name already owned, failing connection.");
+ bus_enter_closing(sd_bus_message_get_bus(m));
+ return 1;
+ }
+
+ log_debug("Unexpected response from RequestName(), failing connection.");
+ bus_enter_closing(sd_bus_message_get_bus(m));
+ return 1;
+}
+
+_public_ int sd_bus_request_name_async(
+ sd_bus *bus,
+ sd_bus_slot **ret_slot,
+ const char *name,
+ uint64_t flags,
+ sd_bus_message_handler_t callback,
+ void *userdata) {
+
+ uint32_t param = 0;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(name, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ r = validate_request_name_parameters(bus, name, flags, &param);
+ if (r < 0)
+ return r;
+
+ return sd_bus_call_method_async(
+ bus,
+ ret_slot,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "RequestName",
+ callback ?: default_request_name_handler,
+ userdata,
+ "su",
+ name,
+ param);
+}
+
+static int validate_release_name_parameters(
+ sd_bus *bus,
+ const char *name) {
+
+ assert(bus);
+ assert(name);
+
+ assert_return(service_name_is_valid(name), -EINVAL);
+ assert_return(name[0] != ':', -EINVAL);
+
+ if (!bus->bus_client)
+ return -EINVAL;
+
+ /* Don't allow releasing the special driver and local names */
+ if (STR_IN_SET(name, "org.freedesktop.DBus", "org.freedesktop.DBus.Local"))
+ return -EINVAL;
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ return 0;
+}
+
+_public_ int sd_bus_release_name(
+ sd_bus *bus,
+ const char *name) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ uint32_t ret;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(name, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ r = validate_release_name_parameters(bus, name);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "ReleaseName",
+ NULL,
+ &reply,
+ "s",
+ name);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(reply, "u", &ret);
+ if (r < 0)
+ return r;
+
+ switch (ret) {
+
+ case BUS_NAME_NON_EXISTENT:
+ return -ESRCH;
+
+ case BUS_NAME_NOT_OWNER:
+ return -EADDRINUSE;
+
+ case BUS_NAME_RELEASED:
+ return 0;
+ }
+
+ return -EIO;
+}
+
+static int default_release_name_handler(
+ sd_bus_message *m,
+ void *userdata,
+ sd_bus_error *ret_error) {
+
+ uint32_t ret;
+ int r;
+
+ assert(m);
+
+ if (sd_bus_message_is_method_error(m, NULL)) {
+ log_debug_errno(sd_bus_message_get_errno(m),
+ "Unable to release name, failing connection: %s",
+ sd_bus_message_get_error(m)->message);
+
+ bus_enter_closing(sd_bus_message_get_bus(m));
+ return 1;
+ }
+
+ r = sd_bus_message_read(m, "u", &ret);
+ if (r < 0)
+ return r;
+
+ switch (ret) {
+
+ case BUS_NAME_NON_EXISTENT:
+ log_debug("Name asked to release is not taken currently, ignoring.");
+ return 1;
+
+ case BUS_NAME_NOT_OWNER:
+ log_debug("Name asked to release is owned by somebody else, ignoring.");
+ return 1;
+
+ case BUS_NAME_RELEASED:
+ log_debug("Name successfully released.");
+ return 1;
+ }
+
+ log_debug("Unexpected response from ReleaseName(), failing connection.");
+ bus_enter_closing(sd_bus_message_get_bus(m));
+ return 1;
+}
+
+_public_ int sd_bus_release_name_async(
+ sd_bus *bus,
+ sd_bus_slot **ret_slot,
+ const char *name,
+ sd_bus_message_handler_t callback,
+ void *userdata) {
+
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(name, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ r = validate_release_name_parameters(bus, name);
+ if (r < 0)
+ return r;
+
+ return sd_bus_call_method_async(
+ bus,
+ ret_slot,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "ReleaseName",
+ callback ?: default_release_name_handler,
+ userdata,
+ "s",
+ name);
+}
+
+_public_ int sd_bus_list_names(sd_bus *bus, char ***acquired, char ***activatable) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_strv_free_ char **x = NULL, **y = NULL;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(acquired || activatable, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!bus->bus_client)
+ return -EINVAL;
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ if (acquired) {
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "ListNames",
+ NULL,
+ &reply,
+ NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read_strv(reply, &x);
+ if (r < 0)
+ return r;
+
+ reply = sd_bus_message_unref(reply);
+ }
+
+ if (activatable) {
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "ListActivatableNames",
+ NULL,
+ &reply,
+ NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read_strv(reply, &y);
+ if (r < 0)
+ return r;
+
+ *activatable = TAKE_PTR(y);
+ }
+
+ if (acquired)
+ *acquired = TAKE_PTR(x);
+
+ return 0;
+}
+
+_public_ int sd_bus_get_name_creds(
+ sd_bus *bus,
+ const char *name,
+ uint64_t mask,
+ sd_bus_creds **creds) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_unique = NULL, *reply = NULL;
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL;
+ const char *unique;
+ pid_t pid = 0;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(name, -EINVAL);
+ assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP);
+ assert_return(mask == 0 || creds, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(service_name_is_valid(name), -EINVAL);
+
+ if (!bus->bus_client)
+ return -EINVAL;
+
+ /* Turn off augmenting if this isn't a local connection. If the connection is not local, then /proc is not
+ * going to match. */
+ if (!bus->is_local)
+ mask &= ~SD_BUS_CREDS_AUGMENT;
+
+ if (streq(name, "org.freedesktop.DBus.Local"))
+ return -EINVAL;
+
+ if (streq(name, "org.freedesktop.DBus"))
+ return sd_bus_get_owner_creds(bus, mask, creds);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ /* If the name is unique anyway, we can use it directly */
+ unique = name[0] == ':' ? name : NULL;
+
+ /* Only query the owner if the caller wants to know it and the name is not unique anyway, or if the caller just
+ * wants to check whether a name exists */
+ if ((FLAGS_SET(mask, SD_BUS_CREDS_UNIQUE_NAME) && !unique) || mask == 0) {
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetNameOwner",
+ NULL,
+ &reply_unique,
+ "s",
+ name);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(reply_unique, "s", &unique);
+ if (r < 0)
+ return r;
+ }
+
+ if (mask != 0) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ bool need_pid, need_uid, need_selinux, need_separate_calls;
+
+ c = bus_creds_new();
+ if (!c)
+ return -ENOMEM;
+
+ if ((mask & SD_BUS_CREDS_UNIQUE_NAME) && unique) {
+ c->unique_name = strdup(unique);
+ if (!c->unique_name)
+ return -ENOMEM;
+
+ c->mask |= SD_BUS_CREDS_UNIQUE_NAME;
+ }
+
+ need_pid = (mask & SD_BUS_CREDS_PID) ||
+ ((mask & SD_BUS_CREDS_AUGMENT) &&
+ (mask & (SD_BUS_CREDS_UID|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|
+ SD_BUS_CREDS_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE|
+ SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID|
+ SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS|
+ SD_BUS_CREDS_SELINUX_CONTEXT|
+ SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID)));
+ need_uid = mask & SD_BUS_CREDS_EUID;
+ need_selinux = mask & SD_BUS_CREDS_SELINUX_CONTEXT;
+
+ if (need_pid + need_uid + need_selinux > 1) {
+
+ /* If we need more than one of the credentials, then use GetConnectionCredentials() */
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetConnectionCredentials",
+ &error,
+ &reply,
+ "s",
+ unique ?: name);
+
+ if (r < 0) {
+
+ if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD))
+ return r;
+
+ /* If we got an unknown method error, fall back to the individual calls... */
+ need_separate_calls = true;
+ sd_bus_error_free(&error);
+
+ } else {
+ need_separate_calls = false;
+
+ r = sd_bus_message_enter_container(reply, 'a', "{sv}");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ const char *m;
+
+ r = sd_bus_message_enter_container(reply, 'e', "sv");
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ r = sd_bus_message_read(reply, "s", &m);
+ if (r < 0)
+ return r;
+
+ if (need_uid && streq(m, "UnixUserID")) {
+ uint32_t u;
+
+ r = sd_bus_message_read(reply, "v", "u", &u);
+ if (r < 0)
+ return r;
+
+ c->euid = u;
+ c->mask |= SD_BUS_CREDS_EUID;
+
+ } else if (need_pid && streq(m, "ProcessID")) {
+ uint32_t p;
+
+ r = sd_bus_message_read(reply, "v", "u", &p);
+ if (r < 0)
+ return r;
+
+ pid = p;
+ if (mask & SD_BUS_CREDS_PID) {
+ c->pid = p;
+ c->mask |= SD_BUS_CREDS_PID;
+ }
+
+ } else if (need_selinux && streq(m, "LinuxSecurityLabel")) {
+ const void *p = NULL;
+ size_t sz = 0;
+
+ r = sd_bus_message_enter_container(reply, 'v', "ay");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read_array(reply, 'y', &p, &sz);
+ if (r < 0)
+ return r;
+
+ free(c->label);
+ c->label = strndup(p, sz);
+ if (!c->label)
+ return -ENOMEM;
+
+ c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT;
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return r;
+ } else {
+ r = sd_bus_message_skip(reply, "v");
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return r;
+
+ if (need_pid && pid == 0)
+ return -EPROTO;
+ }
+
+ } else /* When we only need a single field, then let's use separate calls */
+ need_separate_calls = true;
+
+ if (need_separate_calls) {
+ if (need_pid) {
+ uint32_t u;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetConnectionUnixProcessID",
+ NULL,
+ &reply,
+ "s",
+ unique ?: name);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(reply, "u", &u);
+ if (r < 0)
+ return r;
+
+ pid = u;
+ if (mask & SD_BUS_CREDS_PID) {
+ c->pid = u;
+ c->mask |= SD_BUS_CREDS_PID;
+ }
+
+ reply = sd_bus_message_unref(reply);
+ }
+
+ if (need_uid) {
+ uint32_t u;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetConnectionUnixUser",
+ NULL,
+ &reply,
+ "s",
+ unique ?: name);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(reply, "u", &u);
+ if (r < 0)
+ return r;
+
+ c->euid = u;
+ c->mask |= SD_BUS_CREDS_EUID;
+
+ reply = sd_bus_message_unref(reply);
+ }
+
+ if (need_selinux) {
+ const void *p = NULL;
+ size_t sz = 0;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetConnectionSELinuxSecurityContext",
+ &error,
+ &reply,
+ "s",
+ unique ?: name);
+ if (r < 0) {
+ if (!sd_bus_error_has_name(&error, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"))
+ return r;
+
+ /* no data is fine */
+ } else {
+ r = sd_bus_message_read_array(reply, 'y', &p, &sz);
+ if (r < 0)
+ return r;
+
+ c->label = memdup_suffix0(p, sz);
+ if (!c->label)
+ return -ENOMEM;
+
+ c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT;
+ }
+ }
+ }
+
+ r = bus_creds_add_more(c, mask, pid, 0);
+ if (r < 0 && r != -ESRCH) /* Return the error, but ignore ESRCH which just means the process is already gone */
+ return r;
+ }
+
+ if (creds)
+ *creds = TAKE_PTR(c);
+
+ return 0;
+}
+
+_public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL;
+ bool do_label, do_groups;
+ pid_t pid = 0;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP);
+ assert_return(ret, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ if (!bus->is_local)
+ mask &= ~SD_BUS_CREDS_AUGMENT;
+
+ do_label = bus->label && (mask & SD_BUS_CREDS_SELINUX_CONTEXT);
+ do_groups = bus->n_groups != SIZE_MAX && (mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS);
+
+ /* Avoid allocating anything if we have no chance of returning useful data */
+ if (!bus->ucred_valid && !do_label && !do_groups)
+ return -ENODATA;
+
+ c = bus_creds_new();
+ if (!c)
+ return -ENOMEM;
+
+ if (bus->ucred_valid) {
+ if (pid_is_valid(bus->ucred.pid)) {
+ pid = c->pid = bus->ucred.pid;
+ c->mask |= SD_BUS_CREDS_PID & mask;
+ }
+
+ if (uid_is_valid(bus->ucred.uid)) {
+ c->euid = bus->ucred.uid;
+ c->mask |= SD_BUS_CREDS_EUID & mask;
+ }
+
+ if (gid_is_valid(bus->ucred.gid)) {
+ c->egid = bus->ucred.gid;
+ c->mask |= SD_BUS_CREDS_EGID & mask;
+ }
+ }
+
+ if (do_label) {
+ c->label = strdup(bus->label);
+ if (!c->label)
+ return -ENOMEM;
+
+ c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT;
+ }
+
+ if (do_groups) {
+ c->supplementary_gids = newdup(gid_t, bus->groups, bus->n_groups);
+ if (!c->supplementary_gids)
+ return -ENOMEM;
+
+ c->n_supplementary_gids = bus->n_groups;
+
+ c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS;
+ }
+
+ r = bus_creds_add_more(c, mask, pid, 0);
+ if (r < 0 && r != -ESRCH) /* If the process vanished, then don't complain, just return what we got */
+ return r;
+
+ *ret = TAKE_PTR(c);
+
+ return 0;
+}
+
+#define append_eavesdrop(bus, m) \
+ ((bus)->is_monitor \
+ ? (isempty(m) ? "eavesdrop='true'" : strjoina((m), ",eavesdrop='true'")) \
+ : (m))
+
+int bus_add_match_internal(
+ sd_bus *bus,
+ const char *match,
+ uint64_t *ret_counter) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ const char *e;
+ int r;
+
+ assert(bus);
+
+ if (!bus->bus_client)
+ return -EINVAL;
+
+ e = append_eavesdrop(bus, match);
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "AddMatch",
+ NULL,
+ &reply,
+ "s",
+ e);
+ if (r < 0)
+ return r;
+
+ /* If the caller asked for it, return the read counter of the reply */
+ if (ret_counter)
+ *ret_counter = reply->read_counter;
+
+ return r;
+}
+
+int bus_add_match_internal_async(
+ sd_bus *bus,
+ sd_bus_slot **ret_slot,
+ const char *match,
+ sd_bus_message_handler_t callback,
+ void *userdata) {
+
+ const char *e;
+
+ assert(bus);
+
+ if (!bus->bus_client)
+ return -EINVAL;
+
+ e = append_eavesdrop(bus, match);
+
+ return sd_bus_call_method_async(
+ bus,
+ ret_slot,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "AddMatch",
+ callback,
+ userdata,
+ "s",
+ e);
+}
+
+int bus_remove_match_internal(
+ sd_bus *bus,
+ const char *match) {
+
+ const char *e;
+
+ assert(bus);
+ assert(match);
+
+ if (!bus->bus_client)
+ return -EINVAL;
+
+ e = append_eavesdrop(bus, match);
+
+ /* Fire and forget */
+
+ return sd_bus_call_method_async(
+ bus,
+ NULL,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "RemoveMatch",
+ NULL,
+ NULL,
+ "s",
+ e);
+}
+
+_public_ int sd_bus_get_name_machine_id(sd_bus *bus, const char *name, sd_id128_t *machine) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
+ const char *mid;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(name, -EINVAL);
+ assert_return(machine, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(service_name_is_valid(name), -EINVAL);
+
+ if (!bus->bus_client)
+ return -EINVAL;
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ if (streq_ptr(name, bus->unique_name))
+ return sd_id128_get_machine(machine);
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &m,
+ name,
+ "/",
+ "org.freedesktop.DBus.Peer",
+ "GetMachineId");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_set_auto_start(m, false);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call(bus, m, 0, NULL, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(reply, "s", &mid);
+ if (r < 0)
+ return r;
+
+ return sd_id128_from_string(mid, machine);
+}
diff --git a/src/libsystemd/sd-bus/bus-control.h b/src/libsystemd/sd-bus/bus-control.h
new file mode 100644
index 0000000..8182b9c
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-control.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-bus.h"
+
+int bus_add_match_internal(sd_bus *bus, const char *match, uint64_t *ret_counter);
+int bus_add_match_internal_async(sd_bus *bus, sd_bus_slot **ret, const char *match, sd_bus_message_handler_t callback, void *userdata);
+
+int bus_remove_match_internal(sd_bus *bus, const char *match);
diff --git a/src/libsystemd/sd-bus/bus-convenience.c b/src/libsystemd/sd-bus/bus-convenience.c
new file mode 100644
index 0000000..26a756f
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-convenience.c
@@ -0,0 +1,795 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "bus-internal.h"
+#include "bus-message.h"
+#include "bus-signature.h"
+#include "bus-type.h"
+#include "string-util.h"
+
+_public_ int sd_bus_message_send(sd_bus_message *reply) {
+ assert_return(reply, -EINVAL);
+ assert_return(reply->bus, -EINVAL);
+ assert_return(!bus_pid_changed(reply->bus), -ECHILD);
+
+ return sd_bus_send(reply->bus, reply, NULL);
+}
+
+_public_ int sd_bus_emit_signalv(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *member,
+ const char *types, va_list ap) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ r = sd_bus_message_new_signal(bus, &m, path, interface, member);
+ if (r < 0)
+ return r;
+
+ if (!isempty(types)) {
+ r = sd_bus_message_appendv(m, types, ap);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_send(bus, m, NULL);
+}
+
+_public_ int sd_bus_emit_signal(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *member,
+ const char *types, ...) {
+
+ va_list ap;
+ int r;
+
+ va_start(ap, types);
+ r = sd_bus_emit_signalv(bus, path, interface, member, types, ap);
+ va_end(ap);
+
+ return r;
+}
+
+_public_ int sd_bus_call_method_asyncv(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_message_handler_t callback,
+ void *userdata,
+ const char *types, va_list ap) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ r = sd_bus_message_new_method_call(bus, &m, destination, path, interface, member);
+ if (r < 0)
+ return r;
+
+ if (!isempty(types)) {
+ r = sd_bus_message_appendv(m, types, ap);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_call_async(bus, slot, m, callback, userdata, 0);
+}
+
+_public_ int sd_bus_call_method_async(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_message_handler_t callback,
+ void *userdata,
+ const char *types, ...) {
+
+ va_list ap;
+ int r;
+
+ va_start(ap, types);
+ r = sd_bus_call_method_asyncv(bus, slot, destination, path, interface, member, callback, userdata, types, ap);
+ va_end(ap);
+
+ return r;
+}
+
+_public_ int sd_bus_call_methodv(
+ sd_bus *bus,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_error *error,
+ sd_bus_message **reply,
+ const char *types, va_list ap) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
+
+ bus_assert_return(bus, -EINVAL, error);
+ bus_assert_return(bus = bus_resolve(bus), -ENOPKG, error);
+ bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+
+ if (!BUS_IS_OPEN(bus->state)) {
+ r = -ENOTCONN;
+ goto fail;
+ }
+
+ r = sd_bus_message_new_method_call(bus, &m, destination, path, interface, member);
+ if (r < 0)
+ goto fail;
+
+ if (!isempty(types)) {
+ r = sd_bus_message_appendv(m, types, ap);
+ if (r < 0)
+ goto fail;
+ }
+
+ return sd_bus_call(bus, m, 0, error, reply);
+
+fail:
+ return sd_bus_error_set_errno(error, r);
+}
+
+_public_ int sd_bus_call_method(
+ sd_bus *bus,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_error *error,
+ sd_bus_message **reply,
+ const char *types, ...) {
+
+ va_list ap;
+ int r;
+
+ va_start(ap, types);
+ r = sd_bus_call_methodv(bus, destination, path, interface, member, error, reply, types, ap);
+ va_end(ap);
+
+ return r;
+}
+
+_public_ int sd_bus_reply_method_returnv(
+ sd_bus_message *call,
+ const char *types, va_list ap) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
+
+ assert_return(call, -EINVAL);
+ assert_return(call->sealed, -EPERM);
+ assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
+ assert_return(call->bus, -EINVAL);
+ assert_return(!bus_pid_changed(call->bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(call->bus->state))
+ return -ENOTCONN;
+
+ if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)
+ return 0;
+
+ r = sd_bus_message_new_method_return(call, &m);
+ if (r < 0)
+ return r;
+
+ if (!isempty(types)) {
+ r = sd_bus_message_appendv(m, types, ap);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_send(m);
+}
+
+_public_ int sd_bus_reply_method_return(
+ sd_bus_message *call,
+ const char *types, ...) {
+
+ va_list ap;
+ int r;
+
+ va_start(ap, types);
+ r = sd_bus_reply_method_returnv(call, types, ap);
+ va_end(ap);
+
+ return r;
+}
+
+_public_ int sd_bus_reply_method_error(
+ sd_bus_message *call,
+ const sd_bus_error *e) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
+
+ assert_return(call, -EINVAL);
+ assert_return(call->sealed, -EPERM);
+ assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
+ assert_return(sd_bus_error_is_set(e), -EINVAL);
+ assert_return(call->bus, -EINVAL);
+ assert_return(!bus_pid_changed(call->bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(call->bus->state))
+ return -ENOTCONN;
+
+ if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)
+ return 0;
+
+ r = sd_bus_message_new_method_error(call, &m, e);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_send(m);
+}
+
+_public_ int sd_bus_reply_method_errorfv(
+ sd_bus_message *call,
+ const char *name,
+ const char *format,
+ va_list ap) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ assert_return(call, -EINVAL);
+ assert_return(call->sealed, -EPERM);
+ assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
+ assert_return(call->bus, -EINVAL);
+ assert_return(!bus_pid_changed(call->bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(call->bus->state))
+ return -ENOTCONN;
+
+ if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)
+ return 0;
+
+ sd_bus_error_setfv(&error, name, format, ap);
+
+ return sd_bus_reply_method_error(call, &error);
+}
+
+_public_ int sd_bus_reply_method_errorf(
+ sd_bus_message *call,
+ const char *name,
+ const char *format,
+ ...) {
+
+ va_list ap;
+ int r;
+
+ va_start(ap, format);
+ r = sd_bus_reply_method_errorfv(call, name, format, ap);
+ va_end(ap);
+
+ return r;
+}
+
+_public_ int sd_bus_reply_method_errno(
+ sd_bus_message *call,
+ int error,
+ const sd_bus_error *p) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL;
+
+ assert_return(call, -EINVAL);
+ assert_return(call->sealed, -EPERM);
+ assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
+ assert_return(call->bus, -EINVAL);
+ assert_return(!bus_pid_changed(call->bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(call->bus->state))
+ return -ENOTCONN;
+
+ if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)
+ return 0;
+
+ if (sd_bus_error_is_set(p))
+ return sd_bus_reply_method_error(call, p);
+
+ sd_bus_error_set_errno(&berror, error);
+
+ return sd_bus_reply_method_error(call, &berror);
+}
+
+_public_ int sd_bus_reply_method_errnofv(
+ sd_bus_message *call,
+ int error,
+ const char *format,
+ va_list ap) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL;
+
+ assert_return(call, -EINVAL);
+ assert_return(call->sealed, -EPERM);
+ assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
+ assert_return(call->bus, -EINVAL);
+ assert_return(!bus_pid_changed(call->bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(call->bus->state))
+ return -ENOTCONN;
+
+ if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)
+ return 0;
+
+ sd_bus_error_set_errnofv(&berror, error, format, ap);
+
+ return sd_bus_reply_method_error(call, &berror);
+}
+
+_public_ int sd_bus_reply_method_errnof(
+ sd_bus_message *call,
+ int error,
+ const char *format,
+ ...) {
+
+ va_list ap;
+ int r;
+
+ va_start(ap, format);
+ r = sd_bus_reply_method_errnofv(call, error, format, ap);
+ va_end(ap);
+
+ return r;
+}
+
+_public_ int sd_bus_get_property(
+ sd_bus *bus,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_error *error,
+ sd_bus_message **reply,
+ const char *type) {
+
+ sd_bus_message *rep = NULL;
+ int r;
+
+ bus_assert_return(bus, -EINVAL, error);
+ bus_assert_return(bus = bus_resolve(bus), -ENOPKG, error);
+ bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error);
+ bus_assert_return(member_name_is_valid(member), -EINVAL, error);
+ bus_assert_return(reply, -EINVAL, error);
+ bus_assert_return(signature_is_single(type, false), -EINVAL, error);
+ bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+
+ if (!BUS_IS_OPEN(bus->state)) {
+ r = -ENOTCONN;
+ goto fail;
+ }
+
+ r = sd_bus_call_method(bus, destination, path,
+ "org.freedesktop.DBus.Properties", "Get",
+ error, &rep,
+ "ss", strempty(interface), member);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(rep, 'v', type);
+ if (r < 0) {
+ sd_bus_message_unref(rep);
+ goto fail;
+ }
+
+ *reply = rep;
+ return 0;
+
+fail:
+ return sd_bus_error_set_errno(error, r);
+}
+
+_public_ int sd_bus_get_property_trivial(
+ sd_bus *bus,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_error *error,
+ char type, void *ptr) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+
+ bus_assert_return(bus, -EINVAL, error);
+ bus_assert_return(bus = bus_resolve(bus), -ENOPKG, error);
+ bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error);
+ bus_assert_return(member_name_is_valid(member), -EINVAL, error);
+ bus_assert_return(bus_type_is_trivial(type), -EINVAL, error);
+ bus_assert_return(ptr, -EINVAL, error);
+ bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+
+ if (!BUS_IS_OPEN(bus->state)) {
+ r = -ENOTCONN;
+ goto fail;
+ }
+
+ r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(reply, 'v', CHAR_TO_STR(type));
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_message_read_basic(reply, type, ptr);
+ if (r < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ return sd_bus_error_set_errno(error, r);
+}
+
+_public_ int sd_bus_get_property_string(
+ sd_bus *bus,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_error *error,
+ char **ret) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ const char *s;
+ char *n;
+ int r;
+
+ bus_assert_return(bus, -EINVAL, error);
+ bus_assert_return(bus = bus_resolve(bus), -ENOPKG, error);
+ bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error);
+ bus_assert_return(member_name_is_valid(member), -EINVAL, error);
+ bus_assert_return(ret, -EINVAL, error);
+ bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+
+ if (!BUS_IS_OPEN(bus->state)) {
+ r = -ENOTCONN;
+ goto fail;
+ }
+
+ r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(reply, 'v', "s");
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_message_read_basic(reply, 's', &s);
+ if (r < 0)
+ goto fail;
+
+ n = strdup(s);
+ if (!n) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ *ret = n;
+ return 0;
+
+fail:
+ return sd_bus_error_set_errno(error, r);
+}
+
+_public_ int sd_bus_get_property_strv(
+ sd_bus *bus,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_error *error,
+ char ***ret) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+
+ bus_assert_return(bus, -EINVAL, error);
+ bus_assert_return(bus = bus_resolve(bus), -ENOPKG, error);
+ bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error);
+ bus_assert_return(member_name_is_valid(member), -EINVAL, error);
+ bus_assert_return(ret, -EINVAL, error);
+ bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+
+ if (!BUS_IS_OPEN(bus->state)) {
+ r = -ENOTCONN;
+ goto fail;
+ }
+
+ r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(reply, 'v', NULL);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_message_read_strv(reply, ret);
+ if (r < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ return sd_bus_error_set_errno(error, r);
+}
+
+_public_ int sd_bus_set_propertyv(
+ sd_bus *bus,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_error *error,
+ const char *type, va_list ap) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
+
+ bus_assert_return(bus, -EINVAL, error);
+ bus_assert_return(bus = bus_resolve(bus), -ENOPKG, error);
+ bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error);
+ bus_assert_return(member_name_is_valid(member), -EINVAL, error);
+ bus_assert_return(signature_is_single(type, false), -EINVAL, error);
+ bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+
+ if (!BUS_IS_OPEN(bus->state)) {
+ r = -ENOTCONN;
+ goto fail;
+ }
+
+ r = sd_bus_message_new_method_call(bus, &m, destination, path, "org.freedesktop.DBus.Properties", "Set");
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_message_append(m, "ss", strempty(interface), member);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_message_open_container(m, 'v', type);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_message_appendv(m, type, ap);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ goto fail;
+
+ return sd_bus_call(bus, m, 0, error, NULL);
+
+fail:
+ return sd_bus_error_set_errno(error, r);
+}
+
+_public_ int sd_bus_set_property(
+ sd_bus *bus,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_error *error,
+ const char *type, ...) {
+
+ va_list ap;
+ int r;
+
+ va_start(ap, type);
+ r = sd_bus_set_propertyv(bus, destination, path, interface, member, error, type, ap);
+ va_end(ap);
+
+ return r;
+}
+
+_public_ int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_bus_creds **ret) {
+ sd_bus_creds *c;
+ int r;
+
+ assert_return(call, -EINVAL);
+ assert_return(call->sealed, -EPERM);
+ assert_return(call->bus, -EINVAL);
+ assert_return(!bus_pid_changed(call->bus), -ECHILD);
+ assert_return(ret, -EINVAL);
+
+ if (!BUS_IS_OPEN(call->bus->state))
+ return -ENOTCONN;
+
+ c = sd_bus_message_get_creds(call);
+
+ /* All data we need? */
+ if (c && (mask & ~SD_BUS_CREDS_AUGMENT & ~c->mask) == 0) {
+ *ret = sd_bus_creds_ref(c);
+ return 0;
+ }
+
+ /* No data passed? Or not enough data passed to retrieve the missing bits? */
+ if (!c || !(c->mask & SD_BUS_CREDS_PID)) {
+ /* We couldn't read anything from the call, let's try
+ * to get it from the sender or peer. */
+
+ if (call->sender)
+ /* There's a sender, but the creds are missing. */
+ return sd_bus_get_name_creds(call->bus, call->sender, mask, ret);
+ else
+ /* There's no sender. For direct connections
+ * the credentials of the AF_UNIX peer matter,
+ * which may be queried via sd_bus_get_owner_creds(). */
+ return sd_bus_get_owner_creds(call->bus, mask, ret);
+ }
+
+ r = bus_creds_extend_by_pid(c, mask, ret);
+ if (r == -ESRCH) {
+ /* Process doesn't exist anymore? propagate the few things we have */
+ *ret = sd_bus_creds_ref(c);
+ return 0;
+ }
+
+ return r;
+}
+
+_public_ int sd_bus_query_sender_privilege(sd_bus_message *call, int capability) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ uid_t our_uid;
+ bool know_caps = false;
+ int r;
+
+ assert_return(call, -EINVAL);
+ assert_return(call->sealed, -EPERM);
+ assert_return(call->bus, -EINVAL);
+ assert_return(!bus_pid_changed(call->bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(call->bus->state))
+ return -ENOTCONN;
+
+ if (capability >= 0) {
+
+ r = sd_bus_query_sender_creds(call, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS, &creds);
+ if (r < 0)
+ return r;
+
+ /* We cannot use augmented caps for authorization,
+ * since then data is acquired raceful from
+ * /proc. This can never actually happen, but let's
+ * better be safe than sorry, and do an extra check
+ * here. */
+ assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EFFECTIVE_CAPS) == 0, -EPERM);
+
+ r = sd_bus_creds_has_effective_cap(creds, capability);
+ if (r > 0)
+ return 1;
+ if (r == 0)
+ know_caps = true;
+ } else {
+ r = sd_bus_query_sender_creds(call, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID, &creds);
+ if (r < 0)
+ return r;
+ }
+
+ /* Now, check the UID, but only if the capability check wasn't
+ * sufficient */
+ our_uid = getuid();
+ if (our_uid != 0 || !know_caps || capability < 0) {
+ uid_t sender_uid;
+
+ /* We cannot use augmented uid/euid for authorization,
+ * since then data is acquired raceful from
+ * /proc. This can never actually happen, but let's
+ * better be safe than sorry, and do an extra check
+ * here. */
+ assert_return((sd_bus_creds_get_augmented_mask(creds) & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID)) == 0, -EPERM);
+
+ /* Try to use the EUID, if we have it. */
+ r = sd_bus_creds_get_euid(creds, &sender_uid);
+ if (r < 0)
+ r = sd_bus_creds_get_uid(creds, &sender_uid);
+
+ if (r >= 0) {
+ /* Sender has same UID as us, then let's grant access */
+ if (sender_uid == our_uid)
+ return 1;
+
+ /* Sender is root, we are not root. */
+ if (our_uid != 0 && sender_uid == 0)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+#define make_expression(sender, path, interface, member) \
+ strjoina( \
+ "type='signal'", \
+ sender ? ",sender='" : "", \
+ sender ?: "", \
+ sender ? "'" : "", \
+ path ? ",path='" : "", \
+ path ?: "", \
+ path ? "'" : "", \
+ interface ? ",interface='" : "", \
+ interface ?: "", \
+ interface ? "'" : "", \
+ member ? ",member='" : "", \
+ member ?: "", \
+ member ? "'" : "" \
+ )
+
+_public_ int sd_bus_match_signal(
+ sd_bus *bus,
+ sd_bus_slot **ret,
+ const char *sender,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_message_handler_t callback,
+ void *userdata) {
+
+ const char *expression;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!sender || service_name_is_valid(sender), -EINVAL);
+ assert_return(!path || object_path_is_valid(path), -EINVAL);
+ assert_return(!interface || interface_name_is_valid(interface), -EINVAL);
+ assert_return(!member || member_name_is_valid(member), -EINVAL);
+
+ expression = make_expression(sender, path, interface, member);
+
+ return sd_bus_add_match(bus, ret, expression, callback, userdata);
+}
+
+_public_ int sd_bus_match_signal_async(
+ sd_bus *bus,
+ sd_bus_slot **ret,
+ const char *sender,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_message_handler_t callback,
+ sd_bus_message_handler_t install_callback,
+ void *userdata) {
+
+ const char *expression;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!sender || service_name_is_valid(sender), -EINVAL);
+ assert_return(!path || object_path_is_valid(path), -EINVAL);
+ assert_return(!interface || interface_name_is_valid(interface), -EINVAL);
+ assert_return(!member || member_name_is_valid(member), -EINVAL);
+
+ expression = make_expression(sender, path, interface, member);
+
+ return sd_bus_add_match_async(bus, ret, expression, callback, install_callback, userdata);
+}
diff --git a/src/libsystemd/sd-bus/bus-creds.c b/src/libsystemd/sd-bus/bus-creds.c
new file mode 100644
index 0000000..5951e08
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-creds.c
@@ -0,0 +1,1337 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/capability.h>
+#include <stdlib.h>
+
+#include "alloc-util.h"
+#include "audit-util.h"
+#include "bus-creds.h"
+#include "bus-label.h"
+#include "bus-message.h"
+#include "capability-util.h"
+#include "cgroup-util.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "hexdecoct.h"
+#include "parse-util.h"
+#include "process-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "user-util.h"
+#include "util.h"
+
+enum {
+ CAP_OFFSET_INHERITABLE = 0,
+ CAP_OFFSET_PERMITTED = 1,
+ CAP_OFFSET_EFFECTIVE = 2,
+ CAP_OFFSET_BOUNDING = 3
+};
+
+void bus_creds_done(sd_bus_creds *c) {
+ assert(c);
+
+ /* For internal bus cred structures that are allocated by
+ * something else */
+
+ free(c->session);
+ free(c->unit);
+ free(c->user_unit);
+ free(c->slice);
+ free(c->user_slice);
+ free(c->unescaped_description);
+ free(c->supplementary_gids);
+ free(c->tty);
+
+ free(c->well_known_names); /* note that this is an strv, but
+ * we only free the array, not the
+ * strings the array points to. The
+ * full strv we only free if
+ * c->allocated is set, see
+ * below. */
+
+ strv_free(c->cmdline_array);
+}
+
+_public_ sd_bus_creds *sd_bus_creds_ref(sd_bus_creds *c) {
+
+ if (!c)
+ return NULL;
+
+ if (c->allocated) {
+ assert(c->n_ref > 0);
+ c->n_ref++;
+ } else {
+ sd_bus_message *m;
+
+ /* If this is an embedded creds structure, then
+ * forward ref counting to the message */
+ m = container_of(c, sd_bus_message, creds);
+ sd_bus_message_ref(m);
+ }
+
+ return c;
+}
+
+_public_ sd_bus_creds *sd_bus_creds_unref(sd_bus_creds *c) {
+
+ if (!c)
+ return NULL;
+
+ if (c->allocated) {
+ assert(c->n_ref > 0);
+ c->n_ref--;
+
+ if (c->n_ref == 0) {
+ free(c->comm);
+ free(c->tid_comm);
+ free(c->exe);
+ free(c->cmdline);
+ free(c->cgroup);
+ free(c->capability);
+ free(c->label);
+ free(c->unique_name);
+ free(c->cgroup_root);
+ free(c->description);
+
+ c->supplementary_gids = mfree(c->supplementary_gids);
+
+ c->well_known_names = strv_free(c->well_known_names);
+
+ bus_creds_done(c);
+
+ free(c);
+ }
+ } else {
+ sd_bus_message *m;
+
+ m = container_of(c, sd_bus_message, creds);
+ sd_bus_message_unref(m);
+ }
+
+ return NULL;
+}
+
+_public_ uint64_t sd_bus_creds_get_mask(const sd_bus_creds *c) {
+ assert_return(c, 0);
+
+ return c->mask;
+}
+
+_public_ uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c) {
+ assert_return(c, 0);
+
+ return c->augmented;
+}
+
+sd_bus_creds* bus_creds_new(void) {
+ sd_bus_creds *c;
+
+ c = new0(sd_bus_creds, 1);
+ if (!c)
+ return NULL;
+
+ c->allocated = true;
+ c->n_ref = 1;
+ return c;
+}
+
+_public_ int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t mask) {
+ sd_bus_creds *c;
+ int r;
+
+ assert_return(pid >= 0, -EINVAL);
+ assert_return(mask <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP);
+ assert_return(ret, -EINVAL);
+
+ if (pid == 0)
+ pid = getpid_cached();
+
+ c = bus_creds_new();
+ if (!c)
+ return -ENOMEM;
+
+ r = bus_creds_add_more(c, mask | SD_BUS_CREDS_AUGMENT, pid, 0);
+ if (r < 0) {
+ sd_bus_creds_unref(c);
+ return r;
+ }
+
+ /* Check if the process existed at all, in case we haven't
+ * figured that out already */
+ if (!pid_is_alive(pid)) {
+ sd_bus_creds_unref(c);
+ return -ESRCH;
+ }
+
+ *ret = c;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid) {
+ assert_return(c, -EINVAL);
+ assert_return(uid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_UID))
+ return -ENODATA;
+
+ *uid = c->uid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *euid) {
+ assert_return(c, -EINVAL);
+ assert_return(euid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_EUID))
+ return -ENODATA;
+
+ *euid = c->euid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_suid(sd_bus_creds *c, uid_t *suid) {
+ assert_return(c, -EINVAL);
+ assert_return(suid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_SUID))
+ return -ENODATA;
+
+ *suid = c->suid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_fsuid(sd_bus_creds *c, uid_t *fsuid) {
+ assert_return(c, -EINVAL);
+ assert_return(fsuid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_FSUID))
+ return -ENODATA;
+
+ *fsuid = c->fsuid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid) {
+ assert_return(c, -EINVAL);
+ assert_return(gid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_GID))
+ return -ENODATA;
+
+ *gid = c->gid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_egid(sd_bus_creds *c, gid_t *egid) {
+ assert_return(c, -EINVAL);
+ assert_return(egid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_EGID))
+ return -ENODATA;
+
+ *egid = c->egid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_sgid(sd_bus_creds *c, gid_t *sgid) {
+ assert_return(c, -EINVAL);
+ assert_return(sgid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_SGID))
+ return -ENODATA;
+
+ *sgid = c->sgid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_fsgid(sd_bus_creds *c, gid_t *fsgid) {
+ assert_return(c, -EINVAL);
+ assert_return(fsgid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_FSGID))
+ return -ENODATA;
+
+ *fsgid = c->fsgid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids) {
+ assert_return(c, -EINVAL);
+ assert_return(gids, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS))
+ return -ENODATA;
+
+ *gids = c->supplementary_gids;
+ return (int) c->n_supplementary_gids;
+}
+
+_public_ int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid) {
+ assert_return(c, -EINVAL);
+ assert_return(pid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_PID))
+ return -ENODATA;
+
+ assert(c->pid > 0);
+ *pid = c->pid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid) {
+ assert_return(c, -EINVAL);
+ assert_return(ppid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_PPID))
+ return -ENODATA;
+
+ /* PID 1 has no parent process. Let's distinguish the case of
+ * not knowing and not having a parent process by the returned
+ * error code. */
+ if (c->ppid == 0)
+ return -ENXIO;
+
+ *ppid = c->ppid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_tid(sd_bus_creds *c, pid_t *tid) {
+ assert_return(c, -EINVAL);
+ assert_return(tid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_TID))
+ return -ENODATA;
+
+ assert(c->tid > 0);
+ *tid = c->tid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **ret) {
+ assert_return(c, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_SELINUX_CONTEXT))
+ return -ENODATA;
+
+ assert(c->label);
+ *ret = c->label;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_comm(sd_bus_creds *c, const char **ret) {
+ assert_return(c, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_COMM))
+ return -ENODATA;
+
+ assert(c->comm);
+ *ret = c->comm;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_tid_comm(sd_bus_creds *c, const char **ret) {
+ assert_return(c, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_TID_COMM))
+ return -ENODATA;
+
+ assert(c->tid_comm);
+ *ret = c->tid_comm;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_exe(sd_bus_creds *c, const char **ret) {
+ assert_return(c, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_EXE))
+ return -ENODATA;
+
+ if (!c->exe)
+ return -ENXIO;
+
+ *ret = c->exe;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_cgroup(sd_bus_creds *c, const char **ret) {
+ assert_return(c, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_CGROUP))
+ return -ENODATA;
+
+ assert(c->cgroup);
+ *ret = c->cgroup;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_unit(sd_bus_creds *c, const char **ret) {
+ int r;
+
+ assert_return(c, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_UNIT))
+ return -ENODATA;
+
+ assert(c->cgroup);
+
+ if (!c->unit) {
+ const char *shifted;
+
+ r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted);
+ if (r < 0)
+ return r;
+
+ r = cg_path_get_unit(shifted, (char**) &c->unit);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = c->unit;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_user_unit(sd_bus_creds *c, const char **ret) {
+ int r;
+
+ assert_return(c, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_USER_UNIT))
+ return -ENODATA;
+
+ assert(c->cgroup);
+
+ if (!c->user_unit) {
+ const char *shifted;
+
+ r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted);
+ if (r < 0)
+ return r;
+
+ r = cg_path_get_user_unit(shifted, (char**) &c->user_unit);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = c->user_unit;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_slice(sd_bus_creds *c, const char **ret) {
+ int r;
+
+ assert_return(c, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_SLICE))
+ return -ENODATA;
+
+ assert(c->cgroup);
+
+ if (!c->slice) {
+ const char *shifted;
+
+ r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted);
+ if (r < 0)
+ return r;
+
+ r = cg_path_get_slice(shifted, (char**) &c->slice);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = c->slice;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_user_slice(sd_bus_creds *c, const char **ret) {
+ int r;
+
+ assert_return(c, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_USER_SLICE))
+ return -ENODATA;
+
+ assert(c->cgroup);
+
+ if (!c->user_slice) {
+ const char *shifted;
+
+ r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted);
+ if (r < 0)
+ return r;
+
+ r = cg_path_get_user_slice(shifted, (char**) &c->user_slice);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = c->user_slice;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_session(sd_bus_creds *c, const char **ret) {
+ int r;
+
+ assert_return(c, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_SESSION))
+ return -ENODATA;
+
+ assert(c->cgroup);
+
+ if (!c->session) {
+ const char *shifted;
+
+ r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted);
+ if (r < 0)
+ return r;
+
+ r = cg_path_get_session(shifted, (char**) &c->session);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = c->session;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_owner_uid(sd_bus_creds *c, uid_t *uid) {
+ const char *shifted;
+ int r;
+
+ assert_return(c, -EINVAL);
+ assert_return(uid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_OWNER_UID))
+ return -ENODATA;
+
+ assert(c->cgroup);
+
+ r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted);
+ if (r < 0)
+ return r;
+
+ return cg_path_get_owner_uid(shifted, uid);
+}
+
+_public_ int sd_bus_creds_get_cmdline(sd_bus_creds *c, char ***cmdline) {
+ assert_return(c, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_CMDLINE))
+ return -ENODATA;
+
+ if (!c->cmdline)
+ return -ENXIO;
+
+ if (!c->cmdline_array) {
+ c->cmdline_array = strv_parse_nulstr(c->cmdline, c->cmdline_size);
+ if (!c->cmdline_array)
+ return -ENOMEM;
+ }
+
+ *cmdline = c->cmdline_array;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_audit_session_id(sd_bus_creds *c, uint32_t *sessionid) {
+ assert_return(c, -EINVAL);
+ assert_return(sessionid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_AUDIT_SESSION_ID))
+ return -ENODATA;
+
+ if (!audit_session_is_valid(c->audit_session_id))
+ return -ENXIO;
+
+ *sessionid = c->audit_session_id;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_audit_login_uid(sd_bus_creds *c, uid_t *uid) {
+ assert_return(c, -EINVAL);
+ assert_return(uid, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_AUDIT_LOGIN_UID))
+ return -ENODATA;
+
+ if (!uid_is_valid(c->audit_login_uid))
+ return -ENXIO;
+
+ *uid = c->audit_login_uid;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_tty(sd_bus_creds *c, const char **ret) {
+ assert_return(c, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_TTY))
+ return -ENODATA;
+
+ if (!c->tty)
+ return -ENXIO;
+
+ *ret = c->tty;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_unique_name(sd_bus_creds *c, const char **unique_name) {
+ assert_return(c, -EINVAL);
+ assert_return(unique_name, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_UNIQUE_NAME))
+ return -ENODATA;
+
+ *unique_name = c->unique_name;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_well_known_names(sd_bus_creds *c, char ***well_known_names) {
+ assert_return(c, -EINVAL);
+ assert_return(well_known_names, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_WELL_KNOWN_NAMES))
+ return -ENODATA;
+
+ /* As a special hack we return the bus driver as well-known
+ * names list when this is requested. */
+ if (c->well_known_names_driver) {
+ static const char* const wkn[] = {
+ "org.freedesktop.DBus",
+ NULL
+ };
+
+ *well_known_names = (char**) wkn;
+ return 0;
+ }
+
+ if (c->well_known_names_local) {
+ static const char* const wkn[] = {
+ "org.freedesktop.DBus.Local",
+ NULL
+ };
+
+ *well_known_names = (char**) wkn;
+ return 0;
+ }
+
+ *well_known_names = c->well_known_names;
+ return 0;
+}
+
+_public_ int sd_bus_creds_get_description(sd_bus_creds *c, const char **ret) {
+ assert_return(c, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_DESCRIPTION))
+ return -ENODATA;
+
+ assert(c->description);
+
+ if (!c->unescaped_description) {
+ c->unescaped_description = bus_label_unescape(c->description);
+ if (!c->unescaped_description)
+ return -ENOMEM;
+ }
+
+ *ret = c->unescaped_description;
+ return 0;
+}
+
+static int has_cap(sd_bus_creds *c, size_t offset, int capability) {
+ size_t sz;
+
+ assert(c);
+ assert(capability >= 0);
+ assert(c->capability);
+
+ unsigned lc = cap_last_cap();
+
+ if ((unsigned) capability > lc)
+ return 0;
+
+ /* If the last cap is 63, then there are 64 caps defined, and we need 2 entries à 32bit hence. *
+ * If the last cap is 64, then there are 65 caps defined, and we need 3 entries à 32bit hence. */
+ sz = DIV_ROUND_UP(lc+1, 32LU);
+
+ return !!(c->capability[offset * sz + CAP_TO_INDEX((uint32_t) capability)] & CAP_TO_MASK_CORRECTED((uint32_t) capability));
+}
+
+_public_ int sd_bus_creds_has_effective_cap(sd_bus_creds *c, int capability) {
+ assert_return(c, -EINVAL);
+ assert_return(capability >= 0, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_EFFECTIVE_CAPS))
+ return -ENODATA;
+
+ return has_cap(c, CAP_OFFSET_EFFECTIVE, capability);
+}
+
+_public_ int sd_bus_creds_has_permitted_cap(sd_bus_creds *c, int capability) {
+ assert_return(c, -EINVAL);
+ assert_return(capability >= 0, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_PERMITTED_CAPS))
+ return -ENODATA;
+
+ return has_cap(c, CAP_OFFSET_PERMITTED, capability);
+}
+
+_public_ int sd_bus_creds_has_inheritable_cap(sd_bus_creds *c, int capability) {
+ assert_return(c, -EINVAL);
+ assert_return(capability >= 0, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_INHERITABLE_CAPS))
+ return -ENODATA;
+
+ return has_cap(c, CAP_OFFSET_INHERITABLE, capability);
+}
+
+_public_ int sd_bus_creds_has_bounding_cap(sd_bus_creds *c, int capability) {
+ assert_return(c, -EINVAL);
+ assert_return(capability >= 0, -EINVAL);
+
+ if (!(c->mask & SD_BUS_CREDS_BOUNDING_CAPS))
+ return -ENODATA;
+
+ return has_cap(c, CAP_OFFSET_BOUNDING, capability);
+}
+
+static int parse_caps(sd_bus_creds *c, unsigned offset, const char *p) {
+ size_t sz, max;
+ unsigned i, j;
+
+ assert(c);
+ assert(p);
+
+ max = DIV_ROUND_UP(cap_last_cap()+1, 32U);
+ p += strspn(p, WHITESPACE);
+
+ sz = strlen(p);
+ if (sz % 8 != 0)
+ return -EINVAL;
+
+ sz /= 8;
+ if (sz > max)
+ return -EINVAL;
+
+ if (!c->capability) {
+ c->capability = new0(uint32_t, max * 4);
+ if (!c->capability)
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < sz; i ++) {
+ uint32_t v = 0;
+
+ for (j = 0; j < 8; ++j) {
+ int t;
+
+ t = unhexchar(*p++);
+ if (t < 0)
+ return -EINVAL;
+
+ v = (v << 4) | t;
+ }
+
+ c->capability[offset * max + (sz - i - 1)] = v;
+ }
+
+ return 0;
+}
+
+int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) {
+ uint64_t missing;
+ int r;
+
+ assert(c);
+ assert(c->allocated);
+
+ if (!(mask & SD_BUS_CREDS_AUGMENT))
+ return 0;
+
+ /* Try to retrieve PID from creds if it wasn't passed to us */
+ if (pid > 0) {
+ c->pid = pid;
+ c->mask |= SD_BUS_CREDS_PID;
+ } else if (c->mask & SD_BUS_CREDS_PID)
+ pid = c->pid;
+ else
+ /* Without pid we cannot do much... */
+ return 0;
+
+ /* Try to retrieve TID from creds if it wasn't passed to us */
+ if (tid <= 0 && (c->mask & SD_BUS_CREDS_TID))
+ tid = c->tid;
+
+ /* Calculate what we shall and can add */
+ missing = mask & ~(c->mask|SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_DESCRIPTION|SD_BUS_CREDS_AUGMENT);
+ if (missing == 0)
+ return 0;
+
+ if (tid > 0) {
+ c->tid = tid;
+ c->mask |= SD_BUS_CREDS_TID;
+ }
+
+ if (missing & (SD_BUS_CREDS_PPID |
+ 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 |
+ SD_BUS_CREDS_EFFECTIVE_CAPS | SD_BUS_CREDS_INHERITABLE_CAPS |
+ SD_BUS_CREDS_PERMITTED_CAPS | SD_BUS_CREDS_BOUNDING_CAPS)) {
+
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *p;
+
+ p = procfs_file_alloca(pid, "status");
+
+ f = fopen(p, "re");
+ if (!f) {
+ if (errno == ENOENT)
+ return -ESRCH;
+ else if (!ERRNO_IS_PRIVILEGE(errno))
+ return -errno;
+ } else {
+
+ for (;;) {
+ _cleanup_free_ char *line = NULL;
+
+ r = read_line(f, LONG_LINE_MAX, &line);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (missing & SD_BUS_CREDS_PPID) {
+ p = startswith(line, "PPid:");
+ if (p) {
+ p += strspn(p, WHITESPACE);
+
+ /* Explicitly check for PPID 0 (which is the case for PID 1) */
+ if (!streq(p, "0")) {
+ r = parse_pid(p, &c->ppid);
+ if (r < 0)
+ return r;
+
+ } else
+ c->ppid = 0;
+
+ c->mask |= SD_BUS_CREDS_PPID;
+ continue;
+ }
+ }
+
+ if (missing & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID)) {
+ p = startswith(line, "Uid:");
+ if (p) {
+ unsigned long uid, euid, suid, fsuid;
+
+ p += strspn(p, WHITESPACE);
+ if (sscanf(p, "%lu %lu %lu %lu", &uid, &euid, &suid, &fsuid) != 4)
+ return -EIO;
+
+ if (missing & SD_BUS_CREDS_UID)
+ c->uid = (uid_t) uid;
+ if (missing & SD_BUS_CREDS_EUID)
+ c->euid = (uid_t) euid;
+ if (missing & SD_BUS_CREDS_SUID)
+ c->suid = (uid_t) suid;
+ if (missing & SD_BUS_CREDS_FSUID)
+ c->fsuid = (uid_t) fsuid;
+
+ c->mask |= missing & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID);
+ continue;
+ }
+ }
+
+ if (missing & (SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID)) {
+ p = startswith(line, "Gid:");
+ if (p) {
+ unsigned long gid, egid, sgid, fsgid;
+
+ p += strspn(p, WHITESPACE);
+ if (sscanf(p, "%lu %lu %lu %lu", &gid, &egid, &sgid, &fsgid) != 4)
+ return -EIO;
+
+ if (missing & SD_BUS_CREDS_GID)
+ c->gid = (gid_t) gid;
+ if (missing & SD_BUS_CREDS_EGID)
+ c->egid = (gid_t) egid;
+ if (missing & SD_BUS_CREDS_SGID)
+ c->sgid = (gid_t) sgid;
+ if (missing & SD_BUS_CREDS_FSGID)
+ c->fsgid = (gid_t) fsgid;
+
+ c->mask |= missing & (SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID);
+ continue;
+ }
+ }
+
+ if (missing & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) {
+ p = startswith(line, "Groups:");
+ if (p) {
+ for (;;) {
+ unsigned long g;
+ int n = 0;
+
+ p += strspn(p, WHITESPACE);
+ if (*p == 0)
+ break;
+
+ if (sscanf(p, "%lu%n", &g, &n) != 1)
+ return -EIO;
+
+ if (!GREEDY_REALLOC(c->supplementary_gids, c->n_supplementary_gids+1))
+ return -ENOMEM;
+
+ c->supplementary_gids[c->n_supplementary_gids++] = (gid_t) g;
+ p += n;
+ }
+
+ c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS;
+ continue;
+ }
+ }
+
+ if (missing & SD_BUS_CREDS_EFFECTIVE_CAPS) {
+ p = startswith(line, "CapEff:");
+ if (p) {
+ r = parse_caps(c, CAP_OFFSET_EFFECTIVE, p);
+ if (r < 0)
+ return r;
+
+ c->mask |= SD_BUS_CREDS_EFFECTIVE_CAPS;
+ continue;
+ }
+ }
+
+ if (missing & SD_BUS_CREDS_PERMITTED_CAPS) {
+ p = startswith(line, "CapPrm:");
+ if (p) {
+ r = parse_caps(c, CAP_OFFSET_PERMITTED, p);
+ if (r < 0)
+ return r;
+
+ c->mask |= SD_BUS_CREDS_PERMITTED_CAPS;
+ continue;
+ }
+ }
+
+ if (missing & SD_BUS_CREDS_INHERITABLE_CAPS) {
+ p = startswith(line, "CapInh:");
+ if (p) {
+ r = parse_caps(c, CAP_OFFSET_INHERITABLE, p);
+ if (r < 0)
+ return r;
+
+ c->mask |= SD_BUS_CREDS_INHERITABLE_CAPS;
+ continue;
+ }
+ }
+
+ if (missing & SD_BUS_CREDS_BOUNDING_CAPS) {
+ p = startswith(line, "CapBnd:");
+ if (p) {
+ r = parse_caps(c, CAP_OFFSET_BOUNDING, p);
+ if (r < 0)
+ return r;
+
+ c->mask |= SD_BUS_CREDS_BOUNDING_CAPS;
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+ if (missing & SD_BUS_CREDS_SELINUX_CONTEXT) {
+ const char *p;
+
+ p = procfs_file_alloca(pid, "attr/current");
+ r = read_one_line_file(p, &c->label);
+ if (r < 0) {
+ if (!IN_SET(r, -ENOENT, -EINVAL, -EPERM, -EACCES))
+ return r;
+ } else
+ c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT;
+ }
+
+ if (missing & SD_BUS_CREDS_COMM) {
+ r = get_process_comm(pid, &c->comm);
+ if (r < 0) {
+ if (!ERRNO_IS_PRIVILEGE(r))
+ return r;
+ } else
+ c->mask |= SD_BUS_CREDS_COMM;
+ }
+
+ if (missing & SD_BUS_CREDS_EXE) {
+ r = get_process_exe(pid, &c->exe);
+ if (r == -ESRCH) {
+ /* Unfortunately we cannot really distinguish
+ * the case here where the process does not
+ * exist, and /proc/$PID/exe being unreadable
+ * because $PID is a kernel thread. Hence,
+ * assume it is a kernel thread, and rely on
+ * that this case is caught with a later
+ * call. */
+ c->exe = NULL;
+ c->mask |= SD_BUS_CREDS_EXE;
+ } else if (r < 0) {
+ if (!ERRNO_IS_PRIVILEGE(r))
+ return r;
+ } else
+ c->mask |= SD_BUS_CREDS_EXE;
+ }
+
+ if (missing & SD_BUS_CREDS_CMDLINE) {
+ const char *p;
+
+ p = procfs_file_alloca(pid, "cmdline");
+ r = read_full_virtual_file(p, &c->cmdline, &c->cmdline_size);
+ if (r == -ENOENT)
+ return -ESRCH;
+ if (r < 0) {
+ if (!ERRNO_IS_PRIVILEGE(r))
+ return r;
+ } else {
+ if (c->cmdline_size == 0)
+ c->cmdline = mfree(c->cmdline);
+
+ c->mask |= SD_BUS_CREDS_CMDLINE;
+ }
+ }
+
+ if (tid > 0 && (missing & SD_BUS_CREDS_TID_COMM)) {
+ _cleanup_free_ char *p = NULL;
+
+ if (asprintf(&p, "/proc/"PID_FMT"/task/"PID_FMT"/comm", pid, tid) < 0)
+ return -ENOMEM;
+
+ r = read_one_line_file(p, &c->tid_comm);
+ if (r == -ENOENT)
+ return -ESRCH;
+ if (r < 0) {
+ if (!ERRNO_IS_PRIVILEGE(r))
+ return r;
+ } else
+ c->mask |= SD_BUS_CREDS_TID_COMM;
+ }
+
+ if (missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID)) {
+
+ if (!c->cgroup) {
+ r = cg_pid_get_path(NULL, pid, &c->cgroup);
+ if (r < 0) {
+ if (!ERRNO_IS_PRIVILEGE(r))
+ return r;
+ }
+ }
+
+ if (!c->cgroup_root) {
+ r = cg_get_root_path(&c->cgroup_root);
+ if (r < 0)
+ return r;
+ }
+
+ if (c->cgroup)
+ c->mask |= missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID);
+ }
+
+ if (missing & SD_BUS_CREDS_AUDIT_SESSION_ID) {
+ r = audit_session_from_pid(pid, &c->audit_session_id);
+ if (r == -ENODATA) {
+ /* ENODATA means: no audit session id assigned */
+ c->audit_session_id = AUDIT_SESSION_INVALID;
+ c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID;
+ } else if (r < 0) {
+ if (!IN_SET(r, -EOPNOTSUPP, -ENOENT, -EPERM, -EACCES))
+ return r;
+ } else
+ c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID;
+ }
+
+ if (missing & SD_BUS_CREDS_AUDIT_LOGIN_UID) {
+ r = audit_loginuid_from_pid(pid, &c->audit_login_uid);
+ if (r == -ENODATA) {
+ /* ENODATA means: no audit login uid assigned */
+ c->audit_login_uid = UID_INVALID;
+ c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID;
+ } else if (r < 0) {
+ if (!IN_SET(r, -EOPNOTSUPP, -ENOENT, -EPERM, -EACCES))
+ return r;
+ } else
+ c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID;
+ }
+
+ if (missing & SD_BUS_CREDS_TTY) {
+ r = get_ctty(pid, NULL, &c->tty);
+ if (r == -ENXIO) {
+ /* ENXIO means: process has no controlling TTY */
+ c->tty = NULL;
+ c->mask |= SD_BUS_CREDS_TTY;
+ } else if (r < 0) {
+ if (!IN_SET(r, -EPERM, -EACCES, -ENOENT))
+ return r;
+ } else
+ c->mask |= SD_BUS_CREDS_TTY;
+ }
+
+ /* In case only the exe path was to be read we cannot
+ * distinguish the case where the exe path was unreadable
+ * because the process was a kernel thread, or when the
+ * process didn't exist at all. Hence, let's do a final check,
+ * to be sure. */
+ if (!pid_is_alive(pid))
+ return -ESRCH;
+
+ if (tid > 0 && tid != pid && !pid_is_unwaited(tid))
+ return -ESRCH;
+
+ c->augmented = missing & c->mask;
+
+ return 0;
+}
+
+int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *n = NULL;
+ int r;
+
+ assert(c);
+ assert(ret);
+
+ if ((mask & ~c->mask) == 0 || (!(mask & SD_BUS_CREDS_AUGMENT))) {
+ /* There's already all data we need, or augmentation
+ * wasn't turned on. */
+
+ *ret = sd_bus_creds_ref(c);
+ return 0;
+ }
+
+ n = bus_creds_new();
+ if (!n)
+ return -ENOMEM;
+
+ /* Copy the original data over */
+
+ if (c->mask & mask & SD_BUS_CREDS_PID) {
+ n->pid = c->pid;
+ n->mask |= SD_BUS_CREDS_PID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_TID) {
+ n->tid = c->tid;
+ n->mask |= SD_BUS_CREDS_TID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_PPID) {
+ n->ppid = c->ppid;
+ n->mask |= SD_BUS_CREDS_PPID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_UID) {
+ n->uid = c->uid;
+ n->mask |= SD_BUS_CREDS_UID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_EUID) {
+ n->euid = c->euid;
+ n->mask |= SD_BUS_CREDS_EUID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_SUID) {
+ n->suid = c->suid;
+ n->mask |= SD_BUS_CREDS_SUID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_FSUID) {
+ n->fsuid = c->fsuid;
+ n->mask |= SD_BUS_CREDS_FSUID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_GID) {
+ n->gid = c->gid;
+ n->mask |= SD_BUS_CREDS_GID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_EGID) {
+ n->egid = c->egid;
+ n->mask |= SD_BUS_CREDS_EGID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_SGID) {
+ n->sgid = c->sgid;
+ n->mask |= SD_BUS_CREDS_SGID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_FSGID) {
+ n->fsgid = c->fsgid;
+ n->mask |= SD_BUS_CREDS_FSGID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) {
+ if (c->supplementary_gids) {
+ n->supplementary_gids = newdup(gid_t, c->supplementary_gids, c->n_supplementary_gids);
+ if (!n->supplementary_gids)
+ return -ENOMEM;
+ n->n_supplementary_gids = c->n_supplementary_gids;
+ } else {
+ n->supplementary_gids = NULL;
+ n->n_supplementary_gids = 0;
+ }
+
+ n->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_COMM) {
+ assert(c->comm);
+
+ n->comm = strdup(c->comm);
+ if (!n->comm)
+ return -ENOMEM;
+
+ n->mask |= SD_BUS_CREDS_COMM;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_TID_COMM) {
+ assert(c->tid_comm);
+
+ n->tid_comm = strdup(c->tid_comm);
+ if (!n->tid_comm)
+ return -ENOMEM;
+
+ n->mask |= SD_BUS_CREDS_TID_COMM;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_EXE) {
+ if (c->exe) {
+ n->exe = strdup(c->exe);
+ if (!n->exe)
+ return -ENOMEM;
+ } else
+ n->exe = NULL;
+
+ n->mask |= SD_BUS_CREDS_EXE;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_CMDLINE) {
+ if (c->cmdline) {
+ n->cmdline = memdup(c->cmdline, c->cmdline_size);
+ if (!n->cmdline)
+ return -ENOMEM;
+
+ n->cmdline_size = c->cmdline_size;
+ } else {
+ n->cmdline = NULL;
+ n->cmdline_size = 0;
+ }
+
+ n->mask |= SD_BUS_CREDS_CMDLINE;
+ }
+
+ if (c->mask & mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_OWNER_UID)) {
+ assert(c->cgroup);
+
+ n->cgroup = strdup(c->cgroup);
+ if (!n->cgroup)
+ return -ENOMEM;
+
+ n->cgroup_root = strdup(c->cgroup_root);
+ if (!n->cgroup_root)
+ return -ENOMEM;
+
+ n->mask |= mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_OWNER_UID);
+ }
+
+ if (c->mask & mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS)) {
+ assert(c->capability);
+
+ n->capability = memdup(c->capability, DIV_ROUND_UP(cap_last_cap()+1, 32U) * 4 * 4);
+ if (!n->capability)
+ return -ENOMEM;
+
+ n->mask |= c->mask & mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS);
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_SELINUX_CONTEXT) {
+ assert(c->label);
+
+ n->label = strdup(c->label);
+ if (!n->label)
+ return -ENOMEM;
+ n->mask |= SD_BUS_CREDS_SELINUX_CONTEXT;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_AUDIT_SESSION_ID) {
+ n->audit_session_id = c->audit_session_id;
+ n->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID;
+ }
+ if (c->mask & mask & SD_BUS_CREDS_AUDIT_LOGIN_UID) {
+ n->audit_login_uid = c->audit_login_uid;
+ n->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_TTY) {
+ if (c->tty) {
+ n->tty = strdup(c->tty);
+ if (!n->tty)
+ return -ENOMEM;
+ } else
+ n->tty = NULL;
+ n->mask |= SD_BUS_CREDS_TTY;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_UNIQUE_NAME) {
+ assert(c->unique_name);
+
+ n->unique_name = strdup(c->unique_name);
+ if (!n->unique_name)
+ return -ENOMEM;
+ n->mask |= SD_BUS_CREDS_UNIQUE_NAME;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) {
+ if (strv_isempty(c->well_known_names))
+ n->well_known_names = NULL;
+ else {
+ n->well_known_names = strv_copy(c->well_known_names);
+ if (!n->well_known_names)
+ return -ENOMEM;
+ }
+ n->well_known_names_driver = c->well_known_names_driver;
+ n->well_known_names_local = c->well_known_names_local;
+ n->mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES;
+ }
+
+ if (c->mask & mask & SD_BUS_CREDS_DESCRIPTION) {
+ assert(c->description);
+ n->description = strdup(c->description);
+ if (!n->description)
+ return -ENOMEM;
+ n->mask |= SD_BUS_CREDS_DESCRIPTION;
+ }
+
+ n->augmented = c->augmented & n->mask;
+
+ /* Get more data */
+
+ r = bus_creds_add_more(n, mask, 0, 0);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(n);
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/bus-creds.h b/src/libsystemd/sd-bus/bus-creds.h
new file mode 100644
index 0000000..7806d9e
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-creds.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+#include "sd-bus.h"
+
+struct sd_bus_creds {
+ bool allocated;
+ unsigned n_ref;
+
+ uint64_t mask;
+ uint64_t augmented;
+
+ uid_t uid;
+ uid_t euid;
+ uid_t suid;
+ uid_t fsuid;
+ gid_t gid;
+ gid_t egid;
+ gid_t sgid;
+ gid_t fsgid;
+
+ gid_t *supplementary_gids;
+ unsigned n_supplementary_gids;
+
+ pid_t ppid;
+ pid_t pid;
+ pid_t tid;
+
+ char *comm;
+ char *tid_comm;
+ char *exe;
+
+ char *cmdline;
+ size_t cmdline_size;
+ char **cmdline_array;
+
+ char *cgroup;
+ char *session;
+ char *unit;
+ char *user_unit;
+ char *slice;
+ char *user_slice;
+
+ char *tty;
+
+ uint32_t *capability;
+
+ uint32_t audit_session_id;
+ uid_t audit_login_uid;
+
+ char *label;
+
+ char *unique_name;
+
+ char **well_known_names;
+ bool well_known_names_driver:1;
+ bool well_known_names_local:1;
+
+ char *cgroup_root;
+
+ char *description, *unescaped_description;
+};
+
+sd_bus_creds* bus_creds_new(void);
+
+void bus_creds_done(sd_bus_creds *c);
+
+int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid);
+
+int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret);
diff --git a/src/libsystemd/sd-bus/bus-dump.c b/src/libsystemd/sd-bus/bus-dump.c
new file mode 100644
index 0000000..73939d8
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-dump.c
@@ -0,0 +1,650 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/time.h>
+
+#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 "glyph-util.h"
+#include "macro.h"
+#include "pcapng.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "util.h"
+
+static char *indent(unsigned level, uint64_t flags) {
+ char *p;
+ unsigned n, i = 0;
+
+ n = 0;
+
+ if (flags & SD_BUS_MESSAGE_DUMP_SUBTREE_ONLY && level > 0)
+ level -= 1;
+
+ if (flags & SD_BUS_MESSAGE_DUMP_WITH_HEADER)
+ n += 2;
+
+ p = new(char, n + level*8 + 1);
+ if (!p)
+ return NULL;
+
+ if (flags & SD_BUS_MESSAGE_DUMP_WITH_HEADER) {
+ p[i++] = ' ';
+ p[i++] = ' ';
+ }
+
+ memset(p + i, ' ', level*8);
+ p[i + level*8] = 0;
+
+ return p;
+}
+
+_public_ int sd_bus_message_dump(sd_bus_message *m, FILE *f, uint64_t flags) {
+ unsigned level = 1;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return((flags & ~_SD_BUS_MESSAGE_DUMP_KNOWN_FLAGS) == 0, -EINVAL);
+
+ if (!f)
+ f = stdout;
+
+ if (flags & SD_BUS_MESSAGE_DUMP_WITH_HEADER) {
+ usec_t ts = m->realtime;
+
+ if (ts == 0)
+ ts = now(CLOCK_REALTIME);
+
+ fprintf(f,
+ "%s%s%s Type=%s%s%s Endian=%c Flags=%u Version=%u",
+ 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(SPECIAL_GLYPH_TRIANGULAR_BULLET),
+ ansi_normal(),
+
+ ansi_highlight(),
+ bus_message_type_to_string(m->header->type) ?: "(unknown)",
+ ansi_normal(),
+
+ m->header->endian,
+ m->header->flags,
+ m->header->version);
+
+ /* Display synthetic message serial number in a more readable
+ * format than UINT32_MAX */
+ if (BUS_MESSAGE_COOKIE(m) == UINT32_MAX)
+ 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);
+
+ fprintf(f, " Timestamp=\"%s\"\n", strna(FORMAT_TIMESTAMP_STYLE(ts, TIMESTAMP_US_UTC)));
+
+ 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 & SD_BUS_MESSAGE_DUMP_SUBTREE_ONLY));
+ if (r < 0)
+ return log_error_errno(r, "Failed to rewind: %m");
+
+ if (!(flags & SD_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();
+ }
+ }
+
+ if (!(flags & SD_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) {
+ fprintf(f, "%sSupplementaryGIDs=%s", prefix, color);
+ for (unsigned 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) {
+ 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;
+ r = sd_bus_creds_get_unit(c, &s);
+ if (r != -ENODATA)
+ fprintf(f, "%sUnit=%s%s%s", prefix, color, strna(s), suffix);
+ s = NULL;
+ v = sd_bus_creds_get_slice(c, &s);
+ if (v != -ENODATA)
+ fprintf(f, "%sSlice=%s%s%s", prefix, color, strna(s), suffix);
+ s = NULL;
+ 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;
+ 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 (terse && ((c->mask & SD_BUS_CREDS_CGROUP) || r != -ENODATA || q != -ENODATA || v != -ENODATA || w != -ENODATA || z != -ENODATA))
+ 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) {
+ 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;
+}
+
+static uint16_t pcapng_optlen(size_t len) {
+ return ALIGN4(len + sizeof(struct pcapng_option));
+}
+
+static void pcapng_putopt(FILE *f, uint16_t code, const void *data, size_t len) {
+ struct pcapng_option opt = {
+ .code = code,
+ .length = len,
+ };
+
+ assert(f);
+ assert((uint16_t) len == len);
+ assert(data || len == 0);
+
+ fwrite(&opt, 1, sizeof(opt), f);
+ if (len > 0) {
+ size_t pad = ALIGN4(len) - len;
+
+ fwrite(data, 1, len, f);
+
+ assert(pad < sizeof(uint32_t));
+ while (pad-- > 0)
+ fputc('\0', f);
+ }
+}
+
+static void pcapng_section_header(FILE *f, const char *os, const char *app) {
+ uint32_t len;
+
+ assert(f);
+
+ /* determine length of section header and options */
+ len = sizeof(struct pcapng_section);
+ if (os)
+ len += pcapng_optlen(strlen(os));
+ if (app)
+ len += pcapng_optlen(strlen(app));
+ len += pcapng_optlen(0); /* OPT_END */
+ len += sizeof(uint32_t); /* trailer length */
+
+ struct pcapng_section hdr = {
+ .block_type = PCAPNG_SECTION_BLOCK,
+ .block_length = len,
+ .byte_order_magic = PCAPNG_BYTE_ORDER_MAGIC,
+ .major_version = PCAPNG_MAJOR_VERS,
+ .minor_version = PCAPNG_MINOR_VERS,
+ .section_length = UINT64_MAX,
+ };
+
+ fwrite(&hdr, 1, sizeof(hdr), f);
+ if (os)
+ pcapng_putopt(f, PCAPNG_SHB_OS, os, strlen(os));
+ if (app)
+ pcapng_putopt(f, PCAPNG_SHB_USERAPPL, app, strlen(app));
+ pcapng_putopt(f, PCAPNG_OPT_END, NULL, 0);
+ fwrite(&len, 1, sizeof(uint32_t), f);
+}
+
+/* Only have a single instance of dbus pseudo interface */
+static void pcapng_interface_header(FILE *f, size_t snaplen) {
+ uint32_t len;
+
+ assert(f);
+ assert(snaplen > 0);
+ assert((size_t) (uint32_t) snaplen == snaplen);
+
+ /* no options (yet) */
+ len = sizeof(struct pcapng_interface_block) + sizeof(uint32_t);
+ struct pcapng_interface_block hdr = {
+ .block_type = PCAPNG_INTERFACE_BLOCK,
+ .block_length = len,
+ .link_type = 231, /* D-Bus */
+ .snap_len = snaplen,
+ };
+
+ fwrite(&hdr, 1, sizeof(hdr), f);
+ fwrite(&len, 1, sizeof(uint32_t), f);
+}
+
+int bus_pcap_header(size_t snaplen, const char *os, const char *info, FILE *f) {
+ if (!f)
+ f = stdout;
+
+ pcapng_section_header(f, os, info);
+ pcapng_interface_header(f, snaplen);
+ return fflush_and_check(f);
+}
+
+int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f) {
+ struct bus_body_part *part;
+ size_t msglen, caplen, pad;
+ uint32_t length;
+ uint64_t ts;
+ unsigned i;
+ size_t w;
+
+ if (!f)
+ f = stdout;
+
+ assert(m);
+ assert(snaplen > 0);
+ assert((size_t) (uint32_t) snaplen == snaplen);
+
+ ts = m->realtime ?: now(CLOCK_REALTIME);
+ msglen = BUS_MESSAGE_SIZE(m);
+ caplen = MIN(msglen, snaplen);
+ pad = ALIGN4(caplen) - caplen;
+
+ /* packet block has no options */
+ length = sizeof(struct pcapng_enhance_packet_block)
+ + caplen + pad + sizeof(uint32_t);
+
+ struct pcapng_enhance_packet_block epb = {
+ .block_type = PCAPNG_ENHANCED_PACKET_BLOCK,
+ .block_length = length,
+ .interface_id = 0,
+ .timestamp_hi = (uint32_t)(ts >> 32),
+ .timestamp_lo = (uint32_t)ts,
+ .original_length = msglen,
+ .capture_length = caplen,
+ };
+
+ /* write the pcapng enhanced packet block header */
+ fwrite(&epb, 1, sizeof(epb), 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;
+ }
+
+ while (pad-- > 0)
+ fputc('\0', f);
+
+ /* trailing block length */
+ fwrite(&length, 1, sizeof(uint32_t), f);
+
+ return fflush_and_check(f);
+}
diff --git a/src/libsystemd/sd-bus/bus-dump.h b/src/libsystemd/sd-bus/bus-dump.h
new file mode 100644
index 0000000..e7470ba
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-dump.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "sd-bus.h"
+
+int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse);
+
+int bus_pcap_header(size_t snaplen, const char *os, const char *app, FILE *f);
+int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f);
diff --git a/src/libsystemd/sd-bus/bus-error.c b/src/libsystemd/sd-bus/bus-error.c
new file mode 100644
index 0000000..4d687cf
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-error.c
@@ -0,0 +1,629 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-error.h"
+#include "errno-list.h"
+#include "errno-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "util.h"
+
+BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_standard_errors[] = {
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Failed", EACCES),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoMemory", ENOMEM),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ServiceUnknown", EHOSTUNREACH),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NameHasNoOwner", ENXIO),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoReply", ETIMEDOUT),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.IOError", EIO),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.BadAddress", EADDRNOTAVAIL),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NotSupported", EOPNOTSUPP),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.LimitsExceeded", ENOBUFS),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AccessDenied", EACCES),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AuthFailed", EACCES),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InteractiveAuthorizationRequired", EACCES),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoServer", EHOSTDOWN),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Timeout", ETIMEDOUT),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoNetwork", ENONET),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AddressInUse", EADDRINUSE),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Disconnected", ECONNRESET),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidArgs", EINVAL),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileNotFound", ENOENT),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileExists", EEXIST),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownMethod", EBADR),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownObject", EBADR),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownInterface", EBADR),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownProperty", EBADR),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.PropertyReadOnly", EROFS),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnixProcessIdUnknown", ESRCH),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidSignature", EINVAL),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InconsistentMessage", EBADMSG),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.TimedOut", ETIMEDOUT),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleInvalid", EINVAL),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidFileContent", EINVAL),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleNotFound", ENOENT),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown", ESRCH),
+ SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ObjectPathInUse", EBUSY),
+ SD_BUS_ERROR_MAP_END
+};
+
+/* GCC maps this magically to the beginning and end of the BUS_ERROR_MAP section */
+extern const sd_bus_error_map __start_SYSTEMD_BUS_ERROR_MAP[];
+extern const sd_bus_error_map __stop_SYSTEMD_BUS_ERROR_MAP[];
+
+/* Additional maps registered with sd_bus_error_add_map() are in this
+ * NULL terminated array */
+static const sd_bus_error_map **additional_error_maps = NULL;
+
+static int bus_error_name_to_errno(const char *name) {
+ const sd_bus_error_map **map, *m;
+ const char *p;
+ int r;
+
+ if (!name)
+ return EINVAL;
+
+ p = startswith(name, "System.Error.");
+ if (p) {
+ r = errno_from_name(p);
+ if (r < 0)
+ return EIO;
+
+ return r;
+ }
+
+ if (additional_error_maps)
+ for (map = additional_error_maps; *map; map++)
+ for (m = *map;; m++) {
+ /* For additional error maps the end marker is actually the end marker */
+ if (m->code == BUS_ERROR_MAP_END_MARKER)
+ break;
+
+ if (streq(m->name, name)) {
+ assert(m->code > 0);
+ return m->code;
+ }
+ }
+
+ m = ALIGN_PTR(__start_SYSTEMD_BUS_ERROR_MAP);
+ while (m < __stop_SYSTEMD_BUS_ERROR_MAP) {
+ /* For magic ELF error maps, the end marker might
+ * appear in the middle of things, since multiple maps
+ * might appear in the same section. Hence, let's skip
+ * over it, but realign the pointer to the next 8 byte
+ * boundary, which is the selected alignment for the
+ * arrays. */
+ if (m->code == BUS_ERROR_MAP_END_MARKER) {
+ m = ALIGN_PTR(m + 1);
+ continue;
+ }
+
+ if (streq(m->name, name)) {
+ assert(m->code > 0);
+ return m->code;
+ }
+
+ m++;
+ }
+
+ return EIO;
+}
+
+static sd_bus_error errno_to_bus_error_const(int error) {
+
+ if (error < 0)
+ error = -error;
+
+ switch (error) {
+
+ case ENOMEM:
+ return BUS_ERROR_OOM;
+
+ case EPERM:
+ case EACCES:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_ACCESS_DENIED, "Access denied");
+
+ case EINVAL:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid argument");
+
+ case ESRCH:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, "No such process");
+
+ case ENOENT:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_NOT_FOUND, "File not found");
+
+ case EEXIST:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_EXISTS, "File exists");
+
+ case ETIMEDOUT:
+ case ETIME:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_TIMEOUT, "Timed out");
+
+ case EIO:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_IO_ERROR, "Input/output error");
+
+ case ENETRESET:
+ case ECONNABORTED:
+ case ECONNRESET:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_DISCONNECTED, "Disconnected");
+
+ case EOPNOTSUPP:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NOT_SUPPORTED, "Not supported");
+
+ case EADDRNOTAVAIL:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_BAD_ADDRESS, "Address not available");
+
+ case ENOBUFS:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_LIMITS_EXCEEDED, "Limits exceeded");
+
+ case EADDRINUSE:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_ADDRESS_IN_USE, "Address in use");
+
+ case EBADMSG:
+ return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Inconsistent message");
+ }
+
+ return SD_BUS_ERROR_NULL;
+}
+
+static int errno_to_bus_error_name_new(int error, char **ret) {
+ const char *name;
+ char *n;
+
+ if (error < 0)
+ error = -error;
+
+ name = errno_to_name(error);
+ if (!name)
+ return 0;
+
+ n = strjoin("System.Error.", name);
+ if (!n)
+ return -ENOMEM;
+
+ *ret = n;
+ return 1;
+}
+
+bool bus_error_is_dirty(sd_bus_error *e) {
+ if (!e)
+ return false;
+
+ return e->name || e->message || e->_need_free != 0;
+}
+
+_public_ void sd_bus_error_free(sd_bus_error *e) {
+ if (!e)
+ return;
+
+ if (e->_need_free > 0) {
+ free((void*) e->name);
+ free((void*) e->message);
+ }
+
+ *e = SD_BUS_ERROR_NULL;
+}
+
+_public_ int sd_bus_error_set(sd_bus_error *e, const char *name, const char *message) {
+ int r;
+
+ if (!name)
+ return 0;
+
+ if (e) {
+ assert_return(!bus_error_is_dirty(e), -EINVAL);
+
+ e->name = strdup(name);
+ if (!e->name) {
+ *e = BUS_ERROR_OOM;
+ return -ENOMEM;
+ }
+
+ if (message)
+ e->message = strdup(message);
+
+ e->_need_free = 1;
+ }
+
+ r = bus_error_name_to_errno(name);
+ assert(r > 0);
+ return -r;
+}
+
+_public_ int sd_bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_list ap) {
+ int r;
+
+ if (!name)
+ return 0;
+
+ if (e) {
+ assert_return(!bus_error_is_dirty(e), -EINVAL);
+
+ e->name = strdup(name);
+ if (!e->name) {
+ *e = BUS_ERROR_OOM;
+ return -ENOMEM;
+ }
+
+ if (format) {
+ _cleanup_free_ char *mesg = NULL;
+
+ /* If we hit OOM on formatting the pretty message, we ignore
+ * this, since we at least managed to write the error name */
+
+ if (vasprintf(&mesg, format, ap) >= 0)
+ e->message = TAKE_PTR(mesg);
+ }
+
+ e->_need_free = 1;
+ }
+
+ r = bus_error_name_to_errno(name);
+ assert(r > 0);
+ return -r;
+}
+
+_public_ int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *format, ...) {
+ int r;
+
+ if (format) {
+ va_list ap;
+
+ va_start(ap, format);
+ r = sd_bus_error_setfv(e, name, format, ap);
+ assert(!name || r < 0);
+ va_end(ap);
+
+ return r;
+ }
+
+ r = sd_bus_error_set(e, name, NULL);
+ assert(!name || r < 0);
+ return r;
+}
+
+_public_ int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e) {
+
+ if (!sd_bus_error_is_set(e))
+ return 0;
+ if (!dest)
+ goto finish;
+
+ assert_return(!bus_error_is_dirty(dest), -EINVAL);
+
+ /*
+ * _need_free < 0 indicates that the error is temporarily const, needs deep copying
+ * _need_free == 0 indicates that the error is perpetually const, needs no deep copying
+ * _need_free > 0 indicates that the error is fully dynamic, needs deep copying
+ */
+
+ if (e->_need_free == 0)
+ *dest = *e;
+ else {
+ dest->name = strdup(e->name);
+ if (!dest->name) {
+ *dest = BUS_ERROR_OOM;
+ return -ENOMEM;
+ }
+
+ if (e->message)
+ dest->message = strdup(e->message);
+
+ dest->_need_free = 1;
+ }
+
+finish:
+ return -bus_error_name_to_errno(e->name);
+}
+
+_public_ 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;
+}
+
+_public_ int sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message) {
+ if (!name)
+ return 0;
+ if (!e)
+ goto finish;
+
+ assert_return(!bus_error_is_dirty(e), -EINVAL);
+
+ *e = SD_BUS_ERROR_MAKE_CONST(name, message);
+
+finish:
+ return -bus_error_name_to_errno(name);
+}
+
+_public_ int sd_bus_error_is_set(const sd_bus_error *e) {
+ if (!e)
+ return 0;
+
+ return !!e->name;
+}
+
+_public_ int sd_bus_error_has_name(const sd_bus_error *e, const char *name) {
+ if (!e)
+ return 0;
+
+ return streq_ptr(e->name, name);
+}
+
+_public_ int sd_bus_error_has_names_sentinel(const sd_bus_error *e, ...) {
+ if (!e || !e->name)
+ return 0;
+
+ va_list ap;
+ const char *p;
+
+ va_start(ap, e);
+ while ((p = va_arg(ap, const char *)))
+ if (streq(p, e->name))
+ break;
+ va_end(ap);
+ return !!p;
+}
+
+_public_ int sd_bus_error_get_errno(const sd_bus_error* e) {
+ if (!e || !e->name)
+ return 0;
+
+ return bus_error_name_to_errno(e->name);
+}
+
+static void bus_error_strerror(sd_bus_error *e, int error) {
+ size_t k = 64;
+ char *m;
+
+ assert(e);
+
+ for (;;) {
+ char *x;
+
+ m = new(char, k);
+ if (!m)
+ return;
+
+ errno = 0;
+ x = strerror_r(error, m, k);
+ if (errno == ERANGE || strlen(x) >= k - 1) {
+ free(m);
+ k *= 2;
+ continue;
+ }
+
+ if (errno) {
+ free(m);
+ return;
+ }
+
+ if (x == m) {
+ if (e->_need_free > 0) {
+ /* Error is already dynamic, let's just update the message */
+ free((char*) e->message);
+ e->message = x;
+
+ } else {
+ char *t;
+ /* Error was const so far, let's make it dynamic, if we can */
+
+ t = strdup(e->name);
+ if (!t) {
+ free(m);
+ return;
+ }
+
+ e->_need_free = 1;
+ e->name = t;
+ e->message = x;
+ }
+ } else {
+ free(m);
+
+ if (e->_need_free > 0) {
+ char *t;
+
+ /* Error is dynamic, let's hence make the message also dynamic */
+ t = strdup(x);
+ if (!t)
+ return;
+
+ free((char*) e->message);
+ e->message = t;
+ } else {
+ /* Error is const, hence we can just override */
+ e->message = x;
+ }
+ }
+
+ return;
+ }
+}
+
+_public_ int sd_bus_error_set_errno(sd_bus_error *e, int error) {
+
+ if (error < 0)
+ error = -error;
+
+ if (!e)
+ return -error;
+ if (error == 0)
+ return 0;
+
+ assert_return(!bus_error_is_dirty(e), -EINVAL);
+
+ /* First, try a const translation */
+ *e = errno_to_bus_error_const(error);
+
+ if (!sd_bus_error_is_set(e)) {
+ int k;
+
+ /* If that didn't work, try a dynamic one. */
+
+ k = errno_to_bus_error_name_new(error, (char**) &e->name);
+ if (k > 0)
+ e->_need_free = 1;
+ else if (k < 0) {
+ *e = BUS_ERROR_OOM;
+ return -error;
+ } else
+ *e = BUS_ERROR_FAILED;
+ }
+
+ /* Now, fill in the message from strerror_r() if we can */
+ bus_error_strerror(e, error);
+ return -error;
+}
+
+_public_ int sd_bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) {
+ PROTECT_ERRNO;
+
+ if (error < 0)
+ error = -error;
+
+ if (!e)
+ return -error;
+ if (error == 0)
+ return 0;
+
+ assert_return(!bus_error_is_dirty(e), -EINVAL);
+
+ /* First, try a const translation */
+ *e = errno_to_bus_error_const(error);
+
+ if (!sd_bus_error_is_set(e)) {
+ int k;
+
+ /* If that didn't work, try a dynamic one */
+
+ k = errno_to_bus_error_name_new(error, (char**) &e->name);
+ if (k > 0)
+ e->_need_free = 1;
+ else if (k < 0) {
+ *e = BUS_ERROR_OOM;
+ return -ENOMEM;
+ } else
+ *e = BUS_ERROR_FAILED;
+ }
+
+ if (format) {
+ _cleanup_free_ char *m = NULL;
+
+ /* Then, let's try to fill in the supplied message */
+
+ errno = error; /* Make sure that %m resolves to the specified error */
+ if (vasprintf(&m, format, ap) < 0)
+ goto fail;
+
+ if (e->_need_free <= 0) {
+ char *t;
+
+ t = strdup(e->name);
+ if (!t)
+ goto fail;
+
+ e->_need_free = 1;
+ e->name = t;
+ }
+
+ e->message = TAKE_PTR(m);
+ return -error;
+ }
+
+fail:
+ /* If that didn't work, use strerror_r() for the message */
+ bus_error_strerror(e, error);
+ return -error;
+}
+
+_public_ int sd_bus_error_set_errnof(sd_bus_error *e, int error, const char *format, ...) {
+ int r;
+
+ if (error < 0)
+ error = -error;
+
+ if (!e)
+ return -error;
+ if (error == 0)
+ return 0;
+
+ assert_return(!bus_error_is_dirty(e), -EINVAL);
+
+ if (format) {
+ va_list ap;
+
+ va_start(ap, format);
+ r = sd_bus_error_set_errnofv(e, error, format, ap);
+ va_end(ap);
+
+ return r;
+ }
+
+ return sd_bus_error_set_errno(e, error);
+}
+
+const char* _bus_error_message(const sd_bus_error *e, int error, char buf[static ERRNO_BUF_LEN]) {
+ /* Sometimes, the D-Bus server is a little bit too verbose with
+ * its error messages, so let's override them here */
+ if (sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED))
+ return "Access denied";
+
+ if (e && e->message)
+ return e->message;
+
+ return strerror_r(abs(error), buf, ERRNO_BUF_LEN);
+}
+
+static bool map_ok(const sd_bus_error_map *map) {
+ for (; map->code != BUS_ERROR_MAP_END_MARKER; map++)
+ if (!map->name || map->code <=0)
+ return false;
+ return true;
+}
+
+_public_ int sd_bus_error_add_map(const sd_bus_error_map *map) {
+ const sd_bus_error_map **maps = NULL;
+ unsigned n = 0;
+
+ assert_return(map, -EINVAL);
+ assert_return(map_ok(map), -EINVAL);
+
+ if (additional_error_maps)
+ for (; additional_error_maps[n] != NULL; n++)
+ if (additional_error_maps[n] == map)
+ return 0;
+
+ maps = reallocarray(additional_error_maps, n + 2, sizeof(struct sd_bus_error_map*));
+ if (!maps)
+ return -ENOMEM;
+
+ maps[n] = map;
+ maps[n+1] = NULL;
+
+ additional_error_maps = maps;
+ return 1;
+}
diff --git a/src/libsystemd/sd-bus/bus-error.h b/src/libsystemd/sd-bus/bus-error.h
new file mode 100644
index 0000000..c8768c9
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-error.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+#include "sd-bus.h"
+
+#include "errno-util.h"
+#include "macro.h"
+
+bool bus_error_is_dirty(sd_bus_error *e);
+
+const char* _bus_error_message(const sd_bus_error *e, int error, char buf[static ERRNO_BUF_LEN]);
+
+/* Note: the lifetime of the compound literal is the immediately surrounding block,
+ * see C11 §6.5.2.5, and
+ * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */
+#define bus_error_message(e, error) _bus_error_message(e, error, (char[ERRNO_BUF_LEN]){})
+
+#define BUS_ERROR_OOM SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_MEMORY, "Out of memory")
+#define BUS_ERROR_FAILED SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FAILED, "Operation failed")
+
+/*
+ * There are two ways to register error maps with the error translation
+ * logic: by using BUS_ERROR_MAP_ELF_REGISTER, which however only
+ * works when linked into the same ELF module, or via
+ * sd_bus_error_add_map() which is the official, external API, that
+ * works from any module.
+ *
+ * Note that BUS_ERROR_MAP_ELF_REGISTER has to be used as decorator in
+ * the bus error table, and BUS_ERROR_MAP_ELF_USE has to be used at
+ * least once per compilation unit (i.e. per library), to ensure that
+ * the error map is really added to the final binary.
+ *
+ * In addition, set the retain attribute so that the section cannot be
+ * discarded by ld --gc-sections -z start-stop-gc. Older compilers would
+ * warn for the unknown attribute, so just disable -Wattributes.
+ */
+
+#define BUS_ERROR_MAP_ELF_REGISTER \
+ _Pragma("GCC diagnostic ignored \"-Wattributes\"") \
+ _section_("SYSTEMD_BUS_ERROR_MAP") \
+ _used_ \
+ _retain_ \
+ _alignptr_ \
+ _variable_no_sanitize_address_
+
+#define BUS_ERROR_MAP_ELF_USE(errors) \
+ extern const sd_bus_error_map errors[]; \
+ _used_ \
+ static const sd_bus_error_map * const CONCATENATE(errors ## _copy_, __COUNTER__) = errors;
+
+/* We use something exotic as end marker, to ensure people build the
+ * maps using the macsd-ros. */
+#define BUS_ERROR_MAP_END_MARKER -'x'
+
+BUS_ERROR_MAP_ELF_USE(bus_standard_errors);
diff --git a/src/libsystemd/sd-bus/bus-internal.c b/src/libsystemd/sd-bus/bus-internal.c
new file mode 100644
index 0000000..a249b84
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-internal.c
@@ -0,0 +1,338 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "bus-internal.h"
+#include "bus-message.h"
+#include "escape.h"
+#include "hexdecoct.h"
+#include "string-util.h"
+
+bool object_path_is_valid(const char *p) {
+ const char *q;
+ bool slash;
+
+ if (!p)
+ return false;
+
+ if (p[0] != '/')
+ return false;
+
+ if (p[1] == 0)
+ return true;
+
+ for (slash = true, q = p+1; *q; q++)
+ if (*q == '/') {
+ if (slash)
+ return false;
+
+ slash = true;
+ } else {
+ bool good;
+
+ good = ascii_isalpha(*q) ||
+ ascii_isdigit(*q) ||
+ *q == '_';
+
+ if (!good)
+ return false;
+
+ slash = false;
+ }
+
+ if (slash)
+ return false;
+
+ return (q - p) <= BUS_PATH_SIZE_MAX;
+}
+
+char* object_path_startswith(const char *a, const char *b) {
+ const char *p;
+
+ if (!object_path_is_valid(a) ||
+ !object_path_is_valid(b))
+ return NULL;
+
+ if (streq(b, "/"))
+ return (char*) a + 1;
+
+ p = startswith(a, b);
+ if (!p)
+ return NULL;
+
+ if (*p == 0)
+ return (char*) p;
+
+ if (*p == '/')
+ return (char*) p + 1;
+
+ return NULL;
+}
+
+bool interface_name_is_valid(const char *p) {
+ const char *q;
+ bool dot, found_dot = false;
+
+ if (isempty(p))
+ return false;
+
+ for (dot = true, q = p; *q; q++)
+ if (*q == '.') {
+ if (dot)
+ return false;
+
+ found_dot = dot = true;
+ } else {
+ bool good;
+
+ good =
+ ascii_isalpha(*q) ||
+ (!dot && ascii_isdigit(*q)) ||
+ *q == '_';
+
+ if (!good) {
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *iface = cescape(p);
+ log_debug("The interface %s is invalid as it contains special character", strnull(iface));
+ }
+ return false;
+ }
+
+ dot = false;
+ }
+
+ if (q - p > SD_BUS_MAXIMUM_NAME_LENGTH)
+ return false;
+
+ if (dot)
+ return false;
+
+ if (!found_dot)
+ return false;
+
+ return true;
+}
+
+bool service_name_is_valid(const char *p) {
+ const char *q;
+ bool dot, found_dot = false, unique;
+
+ if (isempty(p))
+ return false;
+
+ unique = p[0] == ':';
+
+ for (dot = true, q = unique ? p+1 : p; *q; q++)
+ if (*q == '.') {
+ if (dot)
+ return false;
+
+ found_dot = dot = true;
+ } else {
+ bool good;
+
+ good =
+ ascii_isalpha(*q) ||
+ ((!dot || unique) && ascii_isdigit(*q)) ||
+ IN_SET(*q, '_', '-');
+
+ if (!good)
+ return false;
+
+ dot = false;
+ }
+
+ if (q - p > SD_BUS_MAXIMUM_NAME_LENGTH)
+ return false;
+
+ if (dot)
+ return false;
+
+ if (!found_dot)
+ return false;
+
+ return true;
+}
+
+bool member_name_is_valid(const char *p) {
+ const char *q;
+
+ if (isempty(p))
+ return false;
+
+ for (q = p; *q; q++) {
+ bool good;
+
+ good =
+ ascii_isalpha(*q) ||
+ ascii_isdigit(*q) ||
+ *q == '_';
+
+ if (!good)
+ return false;
+ }
+
+ if (q - p > SD_BUS_MAXIMUM_NAME_LENGTH)
+ return false;
+
+ return true;
+}
+
+/*
+ * Complex pattern match
+ * This checks whether @a is a 'complex-prefix' of @b, or @b is a
+ * 'complex-prefix' of @a, based on strings that consist of labels with @c as
+ * separator. This function returns true if:
+ * - both strings are equal
+ * - either is a prefix of the other and ends with @c
+ * The second rule makes sure that either string needs to be fully included in
+ * the other, and the string which is considered the prefix needs to end with a
+ * separator.
+ */
+static bool complex_pattern_check(char c, const char *a, const char *b) {
+ bool separator = false;
+
+ if (!a && !b)
+ return true;
+
+ if (!a || !b)
+ return false;
+
+ for (;;) {
+ if (*a != *b)
+ return (separator && (*a == 0 || *b == 0));
+
+ if (*a == 0)
+ return true;
+
+ separator = *a == c;
+
+ a++, b++;
+ }
+}
+
+bool namespace_complex_pattern(const char *pattern, const char *value) {
+ return complex_pattern_check('.', pattern, value);
+}
+
+bool path_complex_pattern(const char *pattern, const char *value) {
+ return complex_pattern_check('/', pattern, value);
+}
+
+/*
+ * Simple pattern match
+ * This checks whether @a is a 'simple-prefix' of @b, based on strings that
+ * consist of labels with @c as separator. This function returns true, if:
+ * - if @a and @b are equal
+ * - if @a is a prefix of @b, and the first following character in @b (or the
+ * last character in @a) is @c
+ * The second rule basically makes sure that if @a is a prefix of @b, then @b
+ * must follow with a new label separated by @c. It cannot extend the label.
+ */
+static bool simple_pattern_check(char c, const char *a, const char *b) {
+ bool separator = false;
+
+ if (!a && !b)
+ return true;
+
+ if (!a || !b)
+ return false;
+
+ for (;;) {
+ if (*a != *b)
+ return *a == 0 && (*b == c || separator);
+
+ if (*a == 0)
+ return true;
+
+ separator = *a == c;
+
+ a++, b++;
+ }
+}
+
+bool namespace_simple_pattern(const char *pattern, const char *value) {
+ return simple_pattern_check('.', pattern, value);
+}
+
+bool path_simple_pattern(const char *pattern, const char *value) {
+ return simple_pattern_check('/', pattern, value);
+}
+
+int bus_message_type_from_string(const char *s, uint8_t *u) {
+ if (streq(s, "signal"))
+ *u = SD_BUS_MESSAGE_SIGNAL;
+ else if (streq(s, "method_call"))
+ *u = SD_BUS_MESSAGE_METHOD_CALL;
+ else if (streq(s, "error"))
+ *u = SD_BUS_MESSAGE_METHOD_ERROR;
+ else if (streq(s, "method_return"))
+ *u = SD_BUS_MESSAGE_METHOD_RETURN;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+const char *bus_message_type_to_string(uint8_t u) {
+ if (u == SD_BUS_MESSAGE_SIGNAL)
+ return "signal";
+ else if (u == SD_BUS_MESSAGE_METHOD_CALL)
+ return "method_call";
+ else if (u == SD_BUS_MESSAGE_METHOD_ERROR)
+ return "error";
+ else if (u == SD_BUS_MESSAGE_METHOD_RETURN)
+ return "method_return";
+ else
+ return NULL;
+}
+
+char *bus_address_escape(const char *v) {
+ const char *a;
+ char *r, *b;
+
+ r = new(char, strlen(v)*3+1);
+ if (!r)
+ return NULL;
+
+ for (a = v, b = r; *a; a++) {
+
+ if (ascii_isdigit(*a) ||
+ ascii_isalpha(*a) ||
+ strchr("_-/.", *a))
+ *(b++) = *a;
+ else {
+ *(b++) = '%';
+ *(b++) = hexchar(*a >> 4);
+ *(b++) = hexchar(*a & 0xF);
+ }
+ }
+
+ *b = 0;
+ return r;
+}
+
+int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error) {
+ assert(m);
+
+ if (sd_bus_error_is_set(error) || r < 0) {
+ if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL)
+ sd_bus_reply_method_errno(m, r, error);
+ } else
+ return r;
+
+ log_debug("Failed to process message type=%s sender=%s destination=%s path=%s interface=%s member=%s cookie=%" PRIu64 " reply_cookie=%" PRIu64 " signature=%s error-name=%s error-message=%s: %s",
+ bus_message_type_to_string(m->header->type),
+ strna(sd_bus_message_get_sender(m)),
+ strna(sd_bus_message_get_destination(m)),
+ strna(sd_bus_message_get_path(m)),
+ strna(sd_bus_message_get_interface(m)),
+ strna(sd_bus_message_get_member(m)),
+ BUS_MESSAGE_COOKIE(m),
+ m->reply_cookie,
+ strna(m->root_container.signature),
+ strna(m->error.name),
+ strna(m->error.message),
+ bus_error_message(error, r));
+
+ return 1;
+}
diff --git a/src/libsystemd/sd-bus/bus-internal.h b/src/libsystemd/sd-bus/bus-internal.h
new file mode 100644
index 0000000..d486e17
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-internal.h
@@ -0,0 +1,414 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <pthread.h>
+
+#include "sd-bus.h"
+
+#include "bus-error.h"
+#include "bus-kernel.h"
+#include "bus-match.h"
+#include "def.h"
+#include "hashmap.h"
+#include "list.h"
+#include "prioq.h"
+#include "socket-util.h"
+#include "time-util.h"
+
+/* Note that we use the new /run prefix here (instead of /var/run) since we require them to be aliases and
+ * that way we become independent of /var being mounted */
+#define DEFAULT_SYSTEM_BUS_ADDRESS "unix:path=/run/dbus/system_bus_socket"
+#define DEFAULT_USER_BUS_ADDRESS_FMT "unix:path=%s/bus"
+
+struct reply_callback {
+ sd_bus_message_handler_t callback;
+ usec_t timeout_usec; /* this is a relative timeout until we reach the BUS_HELLO state, and an absolute one right after */
+ uint64_t cookie;
+ unsigned prioq_idx;
+};
+
+struct filter_callback {
+ sd_bus_message_handler_t callback;
+
+ unsigned last_iteration;
+
+ LIST_FIELDS(struct filter_callback, callbacks);
+};
+
+struct match_callback {
+ sd_bus_message_handler_t callback;
+ sd_bus_message_handler_t install_callback;
+
+ sd_bus_slot *install_slot; /* The AddMatch() call */
+
+ unsigned last_iteration;
+
+ /* Don't dispatch this slot with messages that arrived in any iteration before or at the this
+ * one. We use this to ensure that matches don't apply "retroactively" and confuse the caller:
+ * only messages received after the match was installed will be considered. */
+ uint64_t after;
+
+ char *match_string;
+
+ struct bus_match_node *match_node;
+};
+
+struct node {
+ char *path;
+ struct node *parent;
+ LIST_HEAD(struct node, child);
+ LIST_FIELDS(struct node, siblings);
+
+ LIST_HEAD(struct node_callback, callbacks);
+ LIST_HEAD(struct node_vtable, vtables);
+ LIST_HEAD(struct node_enumerator, enumerators);
+ LIST_HEAD(struct node_object_manager, object_managers);
+};
+
+struct node_callback {
+ struct node *node;
+
+ bool is_fallback:1;
+ unsigned last_iteration;
+
+ sd_bus_message_handler_t callback;
+
+ LIST_FIELDS(struct node_callback, callbacks);
+};
+
+struct node_enumerator {
+ struct node *node;
+
+ sd_bus_node_enumerator_t callback;
+
+ unsigned last_iteration;
+
+ LIST_FIELDS(struct node_enumerator, enumerators);
+};
+
+struct node_object_manager {
+ struct node *node;
+
+ LIST_FIELDS(struct node_object_manager, object_managers);
+};
+
+struct node_vtable {
+ struct node *node;
+
+ bool is_fallback:1;
+ unsigned last_iteration;
+
+ char *interface;
+ const sd_bus_vtable *vtable;
+ sd_bus_object_find_t find;
+
+ LIST_FIELDS(struct node_vtable, vtables);
+};
+
+struct vtable_member {
+ const char *path;
+ const char *interface;
+ const char *member;
+ struct node_vtable *parent;
+ unsigned last_iteration;
+ const sd_bus_vtable *vtable;
+};
+
+typedef enum BusSlotType {
+ BUS_REPLY_CALLBACK,
+ BUS_FILTER_CALLBACK,
+ BUS_MATCH_CALLBACK,
+ BUS_NODE_CALLBACK,
+ BUS_NODE_ENUMERATOR,
+ BUS_NODE_VTABLE,
+ BUS_NODE_OBJECT_MANAGER,
+ _BUS_SLOT_INVALID = -EINVAL,
+} BusSlotType;
+
+struct sd_bus_slot {
+ unsigned n_ref;
+ BusSlotType type:8;
+
+ /* Slots can be "floating" or not. If they are not floating (the usual case) then they reference the
+ * bus object they are associated with. This means the bus object stays allocated at least as long as
+ * there is a slot around associated with it. If it is floating, then the slot's lifecycle is bound
+ * to the lifecycle of the bus: it will be disconnected from the bus when the bus is destroyed, and
+ * it keeping the slot reffed hence won't mean the bus stays reffed too. Internally this means the
+ * reference direction is reversed: floating slots objects are referenced by the bus object, and not
+ * vice versa. */
+ bool floating;
+ bool match_added;
+
+ sd_bus *bus;
+ void *userdata;
+ sd_bus_destroy_t destroy_callback;
+
+ char *description;
+
+ LIST_FIELDS(sd_bus_slot, slots);
+
+ union {
+ struct reply_callback reply_callback;
+ struct filter_callback filter_callback;
+ struct match_callback match_callback;
+ struct node_callback node_callback;
+ struct node_enumerator node_enumerator;
+ struct node_object_manager node_object_manager;
+ struct node_vtable node_vtable;
+ };
+};
+
+enum bus_state {
+ BUS_UNSET,
+ BUS_WATCH_BIND, /* waiting for the socket to appear via inotify */
+ BUS_OPENING, /* the kernel's connect() is still not ready */
+ BUS_AUTHENTICATING, /* we are currently in the "SASL" authorization phase of dbus */
+ BUS_HELLO, /* we are waiting for the Hello() response */
+ BUS_RUNNING,
+ BUS_CLOSING,
+ BUS_CLOSED,
+ _BUS_STATE_MAX,
+};
+
+static inline bool BUS_IS_OPEN(enum bus_state state) {
+ return state > BUS_UNSET && state < BUS_CLOSING;
+}
+
+enum bus_auth {
+ _BUS_AUTH_INVALID,
+ BUS_AUTH_EXTERNAL,
+ BUS_AUTH_ANONYMOUS
+};
+
+struct sd_bus {
+ unsigned n_ref;
+
+ enum bus_state state;
+ int input_fd, output_fd;
+ int inotify_fd;
+ int message_version;
+ int message_endian;
+
+ bool can_fds:1;
+ bool bus_client:1;
+ bool ucred_valid:1;
+ bool is_server:1;
+ bool anonymous_auth:1;
+ bool prefer_readv:1;
+ bool prefer_writev:1;
+ bool match_callbacks_modified:1;
+ bool filter_callbacks_modified:1;
+ bool nodes_modified:1;
+ bool trusted:1;
+ bool manual_peer_interface:1;
+ bool is_system:1;
+ bool is_user:1;
+ bool allow_interactive_authorization:1;
+ bool exit_on_disconnect:1;
+ bool exited:1;
+ bool exit_triggered:1;
+ bool is_local:1;
+ bool watch_bind:1;
+ bool is_monitor:1;
+ bool accept_fd:1;
+ bool attach_timestamp:1;
+ bool connected_signal:1;
+ bool close_on_exit:1;
+
+ signed int use_memfd:2;
+
+ void *rbuffer;
+ size_t rbuffer_size;
+
+ sd_bus_message **rqueue;
+ size_t rqueue_size;
+
+ sd_bus_message **wqueue;
+ size_t wqueue_size;
+ size_t windex;
+
+ uint64_t cookie;
+ uint64_t read_counter; /* A counter for each incoming msg */
+
+ char *unique_name;
+ uint64_t unique_id;
+
+ struct bus_match_node match_callbacks;
+ Prioq *reply_callbacks_prioq;
+ OrderedHashmap *reply_callbacks;
+ LIST_HEAD(struct filter_callback, filter_callbacks);
+
+ Hashmap *nodes;
+ Hashmap *vtable_methods;
+ Hashmap *vtable_properties;
+
+ union sockaddr_union sockaddr;
+ socklen_t sockaddr_size;
+
+ pid_t nspid;
+ char *machine;
+
+ sd_id128_t server_id;
+
+ char *address;
+ unsigned address_index;
+
+ int last_connect_error;
+
+ enum bus_auth auth;
+ unsigned auth_index;
+ struct iovec auth_iovec[3];
+ size_t auth_rbegin;
+ char *auth_buffer;
+ usec_t auth_timeout;
+
+ struct ucred ucred;
+ char *label;
+ gid_t *groups;
+ size_t n_groups;
+
+ uint64_t creds_mask;
+
+ int *fds;
+ size_t n_fds;
+
+ char *exec_path;
+ char **exec_argv;
+
+ /* We do locking around the memfd cache, since we want to
+ * allow people to process a sd_bus_message in a different
+ * thread then it was generated on and free it there. Since
+ * adding something to the memfd cache might happen when a
+ * message is released, we hence need to protect this bit with
+ * a mutex. */
+ pthread_mutex_t memfd_cache_mutex;
+ struct memfd_cache memfd_cache[MEMFD_CACHE_MAX];
+ unsigned n_memfd_cache;
+
+ pid_t original_pid;
+ pid_t busexec_pid;
+
+ unsigned iteration_counter;
+
+ sd_event_source *input_io_event_source;
+ sd_event_source *output_io_event_source;
+ sd_event_source *time_event_source;
+ sd_event_source *quit_event_source;
+ sd_event_source *inotify_event_source;
+ sd_event *event;
+ int event_priority;
+
+ pid_t tid;
+
+ sd_bus_message *current_message;
+ sd_bus_slot *current_slot;
+ sd_bus_message_handler_t current_handler;
+ void *current_userdata;
+
+ sd_bus **default_bus_ptr;
+
+ char *description;
+ char *patch_sender;
+
+ sd_bus_track *track_queue;
+
+ LIST_HEAD(sd_bus_slot, slots);
+ LIST_HEAD(sd_bus_track, tracks);
+
+ int *inotify_watches;
+ size_t n_inotify_watches;
+
+ /* zero means use value specified by $SYSTEMD_BUS_TIMEOUT= environment variable or built-in default */
+ usec_t method_call_timeout;
+};
+
+/* For method calls we timeout at 25s, like in the D-Bus reference implementation */
+#define BUS_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC))
+
+/* For the authentication phase we grant 90s, to provide extra room during boot, when RNGs and such are not filled up
+ * with enough entropy yet and might delay the boot */
+#define BUS_AUTH_TIMEOUT ((usec_t) DEFAULT_TIMEOUT_USEC)
+
+#define BUS_WQUEUE_MAX (384*1024)
+#define BUS_RQUEUE_MAX (384*1024)
+
+#define BUS_MESSAGE_SIZE_MAX (128*1024*1024)
+#define BUS_AUTH_SIZE_MAX (64*1024)
+/* Note that the D-Bus specification states that bus paths shall have no size limit. We enforce here one
+ * anyway, since truly unbounded strings are a security problem. The limit we pick is relatively large however,
+ * to not clash unnecessarily with real-life applications. */
+#define BUS_PATH_SIZE_MAX (64*1024)
+
+#define BUS_CONTAINER_DEPTH 128
+
+/* Defined by the specification as maximum size of an array in bytes */
+#define BUS_ARRAY_MAX_SIZE 67108864
+
+#define BUS_FDS_MAX 1024
+
+#define BUS_EXEC_ARGV_MAX 256
+
+bool interface_name_is_valid(const char *p) _pure_;
+bool service_name_is_valid(const char *p) _pure_;
+bool member_name_is_valid(const char *p) _pure_;
+bool object_path_is_valid(const char *p) _pure_;
+
+char *object_path_startswith(const char *a, const char *b) _pure_;
+
+bool namespace_complex_pattern(const char *pattern, const char *value) _pure_;
+bool path_complex_pattern(const char *pattern, const char *value) _pure_;
+
+bool namespace_simple_pattern(const char *pattern, const char *value) _pure_;
+bool path_simple_pattern(const char *pattern, const char *value) _pure_;
+
+int bus_message_type_from_string(const char *s, uint8_t *u);
+const char *bus_message_type_to_string(uint8_t u) _pure_;
+
+#define error_name_is_valid interface_name_is_valid
+
+sd_bus *bus_resolve(sd_bus *bus);
+
+int bus_ensure_running(sd_bus *bus);
+int bus_start_running(sd_bus *bus);
+int bus_next_address(sd_bus *bus);
+
+int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m);
+
+int bus_rqueue_make_room(sd_bus *bus);
+
+bool bus_pid_changed(sd_bus *bus);
+
+char *bus_address_escape(const char *v);
+
+int bus_attach_io_events(sd_bus *b);
+int bus_attach_inotify_event(sd_bus *b);
+
+void bus_close_inotify_fd(sd_bus *b);
+void bus_close_io_fds(sd_bus *b);
+
+#define OBJECT_PATH_FOREACH_PREFIX(prefix, path) \
+ for (char *_slash = ({ strcpy((prefix), (path)); streq((prefix), "/") ? NULL : strrchr((prefix), '/'); }) ; \
+ _slash && ((_slash[(_slash) == (prefix)] = 0), true); \
+ _slash = streq((prefix), "/") ? NULL : strrchr((prefix), '/'))
+
+/* If we are invoking callbacks of a bus object, ensure unreffing the
+ * bus from the callback doesn't destroy the object we are working on */
+#define BUS_DONT_DESTROY(bus) \
+ _cleanup_(sd_bus_unrefp) _unused_ sd_bus *_dont_destroy_##bus = sd_bus_ref(bus)
+
+int bus_set_address_system(sd_bus *bus);
+int bus_set_address_user(sd_bus *bus);
+int bus_set_address_system_remote(sd_bus *b, const char *host);
+int bus_set_address_machine(sd_bus *b, bool user, const char *machine);
+
+int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error);
+
+#define bus_assert_return(expr, r, error) \
+ do { \
+ if (!assert_log(expr, #expr)) \
+ return sd_bus_error_set_errno(error, r); \
+ } while (false)
+
+void bus_enter_closing(sd_bus *bus);
+
+void bus_set_state(sd_bus *bus, enum bus_state state);
diff --git a/src/libsystemd/sd-bus/bus-introspect.c b/src/libsystemd/sd-bus/bus-introspect.c
new file mode 100644
index 0000000..49236a8
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-introspect.c
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bus-internal.h"
+#include "bus-introspect.h"
+#include "bus-objects.h"
+#include "bus-protocol.h"
+#include "bus-signature.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "memory-util.h"
+#include "string-util.h"
+
+#define BUS_INTROSPECT_DOCTYPE \
+ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n" \
+ "\"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
+
+#define BUS_INTROSPECT_INTERFACE_PEER \
+ " <interface name=\"org.freedesktop.DBus.Peer\">\n" \
+ " <method name=\"Ping\"/>\n" \
+ " <method name=\"GetMachineId\">\n" \
+ " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " </interface>\n"
+
+#define BUS_INTROSPECT_INTERFACE_INTROSPECTABLE \
+ " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" \
+ " <method name=\"Introspect\">\n" \
+ " <arg name=\"xml_data\" type=\"s\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " </interface>\n"
+
+#define BUS_INTROSPECT_INTERFACE_PROPERTIES \
+ " <interface name=\"org.freedesktop.DBus.Properties\">\n" \
+ " <method name=\"Get\">\n" \
+ " <arg name=\"interface_name\" direction=\"in\" type=\"s\"/>\n" \
+ " <arg name=\"property_name\" direction=\"in\" type=\"s\"/>\n" \
+ " <arg name=\"value\" direction=\"out\" type=\"v\"/>\n" \
+ " </method>\n" \
+ " <method name=\"GetAll\">\n" \
+ " <arg name=\"interface_name\" direction=\"in\" type=\"s\"/>\n" \
+ " <arg name=\"props\" direction=\"out\" type=\"a{sv}\"/>\n" \
+ " </method>\n" \
+ " <method name=\"Set\">\n" \
+ " <arg name=\"interface_name\" direction=\"in\" type=\"s\"/>\n" \
+ " <arg name=\"property_name\" direction=\"in\" type=\"s\"/>\n" \
+ " <arg name=\"value\" direction=\"in\" type=\"v\"/>\n" \
+ " </method>\n" \
+ " <signal name=\"PropertiesChanged\">\n" \
+ " <arg type=\"s\" name=\"interface_name\"/>\n" \
+ " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" \
+ " <arg type=\"as\" name=\"invalidated_properties\"/>\n" \
+ " </signal>\n" \
+ " </interface>\n"
+
+#define BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER \
+ " <interface name=\"org.freedesktop.DBus.ObjectManager\">\n" \
+ " <method name=\"GetManagedObjects\">\n" \
+ " <arg type=\"a{oa{sa{sv}}}\" name=\"object_paths_interfaces_and_properties\" direction=\"out\"/>\n" \
+ " </method>\n" \
+ " <signal name=\"InterfacesAdded\">\n" \
+ " <arg type=\"o\" name=\"object_path\"/>\n" \
+ " <arg type=\"a{sa{sv}}\" name=\"interfaces_and_properties\"/>\n" \
+ " </signal>\n" \
+ " <signal name=\"InterfacesRemoved\">\n" \
+ " <arg type=\"o\" name=\"object_path\"/>\n" \
+ " <arg type=\"as\" name=\"interfaces\"/>\n" \
+ " </signal>\n" \
+ " </interface>\n"
+
+int introspect_begin(struct introspect *i, bool trusted) {
+ assert(i);
+
+ *i = (struct introspect) {
+ .trusted = trusted,
+ };
+
+ i->f = open_memstream_unlocked(&i->introspection, &i->size);
+ if (!i->f)
+ return -ENOMEM;
+
+ fputs(BUS_INTROSPECT_DOCTYPE
+ "<node>\n", i->f);
+
+ return 0;
+}
+
+int introspect_write_default_interfaces(struct introspect *i, bool object_manager) {
+ assert(i);
+
+ fputs(BUS_INTROSPECT_INTERFACE_PEER
+ BUS_INTROSPECT_INTERFACE_INTROSPECTABLE
+ BUS_INTROSPECT_INTERFACE_PROPERTIES, i->f);
+
+ if (object_manager)
+ fputs(BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER, i->f);
+
+ return 0;
+}
+
+static int set_interface_name(struct introspect *intro, const char *interface_name) {
+ if (streq_ptr(intro->interface_name, interface_name))
+ return 0;
+
+ if (intro->interface_name)
+ fputs(" </interface>\n", intro->f);
+
+ if (interface_name)
+ fprintf(intro->f, " <interface name=\"%s\">\n", interface_name);
+
+ return free_and_strdup(&intro->interface_name, interface_name);
+}
+
+int introspect_write_child_nodes(struct introspect *i, OrderedSet *s, const char *prefix) {
+ char *node;
+
+ assert(i);
+ assert(prefix);
+
+ assert_se(set_interface_name(i, NULL) >= 0);
+
+ while ((node = ordered_set_steal_first(s))) {
+ const char *e;
+
+ e = object_path_startswith(node, prefix);
+ if (e && e[0])
+ fprintf(i->f, " <node name=\"%s\"/>\n", e);
+
+ free(node);
+ }
+
+ return 0;
+}
+
+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);
+
+ if (type == _SD_BUS_VTABLE_METHOD && (flags & SD_BUS_VTABLE_METHOD_NO_REPLY))
+ fputs(" <annotation name=\"org.freedesktop.DBus.Method.NoReply\" value=\"true\"/>\n", i->f);
+
+ if (IN_SET(type, _SD_BUS_VTABLE_PROPERTY, _SD_BUS_VTABLE_WRITABLE_PROPERTY)) {
+ if (flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT)
+ fputs(" <annotation name=\"org.freedesktop.systemd1.Explicit\" value=\"true\"/>\n", i->f);
+
+ if (flags & SD_BUS_VTABLE_PROPERTY_CONST)
+ fputs(" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"const\"/>\n", i->f);
+ else if (flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)
+ fputs(" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"invalidates\"/>\n", i->f);
+ else if (!(flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE))
+ fputs(" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>\n", i->f);
+ }
+
+ if (!i->trusted &&
+ IN_SET(type, _SD_BUS_VTABLE_METHOD, _SD_BUS_VTABLE_WRITABLE_PROPERTY) &&
+ !(flags & SD_BUS_VTABLE_UNPRIVILEGED))
+ fputs(" <annotation name=\"org.freedesktop.systemd1.Privileged\" value=\"true\"/>\n", i->f);
+}
+
+/* Note that "names" is both an input and an output parameter. It initially points to the first argument name in a
+ NULL-separated list of strings, and is then advanced with each argument, and the resulting pointer is returned. */
+static int introspect_write_arguments(struct introspect *i, const char *signature, const char **names, const char *direction) {
+ int r;
+
+ for (;;) {
+ size_t l;
+
+ if (!*signature)
+ return 0;
+
+ r = signature_element_length(signature, &l);
+ if (r < 0)
+ return r;
+
+ fprintf(i->f, " <arg type=\"%.*s\"", (int) l, signature);
+
+ if (**names != '\0') {
+ fprintf(i->f, " name=\"%s\"", *names);
+ *names += strlen(*names) + 1;
+ }
+
+ if (direction)
+ fprintf(i->f, " direction=\"%s\"/>\n", direction);
+ else
+ fputs("/>\n", i->f);
+
+ signature += l;
+ }
+}
+
+int introspect_write_interface(
+ struct introspect *i,
+ const char *interface_name,
+ const sd_bus_vtable *v) {
+
+ const sd_bus_vtable *vtable = ASSERT_PTR(v);
+ const char *names = "";
+ int r;
+
+ assert(i);
+ assert(interface_name);
+
+ r = set_interface_name(i, interface_name);
+ if (r < 0)
+ return r;
+
+ for (; v->type != _SD_BUS_VTABLE_END; v = bus_vtable_next(vtable, v)) {
+
+ /* Ignore methods, signals and properties that are
+ * marked "hidden", but do show the interface
+ * itself */
+
+ if (v->type != _SD_BUS_VTABLE_START && (v->flags & SD_BUS_VTABLE_HIDDEN))
+ continue;
+
+ switch (v->type) {
+
+ case _SD_BUS_VTABLE_START:
+ if (v->flags & SD_BUS_VTABLE_DEPRECATED)
+ fputs(" <annotation name=\"org.freedesktop.DBus.Deprecated\" value=\"true\"/>\n", i->f);
+ break;
+
+ case _SD_BUS_VTABLE_METHOD:
+ fprintf(i->f, " <method name=\"%s\">\n", v->x.method.member);
+ if (bus_vtable_has_names(vtable))
+ names = strempty(v->x.method.names);
+ introspect_write_arguments(i, strempty(v->x.method.signature), &names, "in");
+ introspect_write_arguments(i, strempty(v->x.method.result), &names, "out");
+ introspect_write_flags(i, v->type, v->flags);
+ fputs(" </method>\n", i->f);
+ break;
+
+ case _SD_BUS_VTABLE_PROPERTY:
+ case _SD_BUS_VTABLE_WRITABLE_PROPERTY:
+ fprintf(i->f, " <property name=\"%s\" type=\"%s\" access=\"%s\">\n",
+ v->x.property.member,
+ v->x.property.signature,
+ v->type == _SD_BUS_VTABLE_WRITABLE_PROPERTY ? "readwrite" : "read");
+ introspect_write_flags(i, v->type, v->flags);
+ fputs(" </property>\n", i->f);
+ break;
+
+ case _SD_BUS_VTABLE_SIGNAL:
+ fprintf(i->f, " <signal name=\"%s\">\n", v->x.signal.member);
+ if (bus_vtable_has_names(vtable))
+ names = strempty(v->x.signal.names);
+ introspect_write_arguments(i, strempty(v->x.signal.signature), &names, NULL);
+ introspect_write_flags(i, v->type, v->flags);
+ fputs(" </signal>\n", i->f);
+ break;
+ }
+
+ }
+
+ return 0;
+}
+
+int introspect_finish(struct introspect *i, char **ret) {
+ int r;
+
+ assert(i);
+
+ assert_se(set_interface_name(i, NULL) >= 0);
+
+ fputs("</node>\n", i->f);
+
+ r = fflush_and_check(i->f);
+ if (r < 0)
+ return r;
+
+ i->f = safe_fclose(i->f);
+ *ret = TAKE_PTR(i->introspection);
+
+ return 0;
+}
+
+void introspect_free(struct introspect *i) {
+ assert(i);
+
+ /* Normally introspect_finish() does all the work, this is just a backup for error paths */
+
+ safe_fclose(i->f);
+ free(i->interface_name);
+ free(i->introspection);
+}
diff --git a/src/libsystemd/sd-bus/bus-introspect.h b/src/libsystemd/sd-bus/bus-introspect.h
new file mode 100644
index 0000000..19e3ef0
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-introspect.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdio.h>
+
+#include "sd-bus.h"
+
+#include "ordered-set.h"
+
+struct introspect {
+ FILE *f;
+ char *interface_name;
+ char *introspection;
+ size_t size;
+ bool trusted;
+};
+
+int introspect_begin(struct introspect *i, bool trusted);
+int introspect_write_default_interfaces(struct introspect *i, bool object_manager);
+int introspect_write_child_nodes(struct introspect *i, OrderedSet *s, const char *prefix);
+int introspect_write_interface(
+ struct introspect *i,
+ const char *interface_name,
+ const sd_bus_vtable *v);
+int introspect_finish(struct introspect *i, char **ret);
+void introspect_free(struct introspect *i);
diff --git a/src/libsystemd/sd-bus/bus-kernel.c b/src/libsystemd/sd-bus/bus-kernel.c
new file mode 100644
index 0000000..b553f15
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-kernel.c
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#if HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include <fcntl.h>
+#include <malloc.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+
+#include "alloc-util.h"
+#include "bus-internal.h"
+#include "bus-kernel.h"
+#include "bus-label.h"
+#include "bus-message.h"
+#include "capability-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "memfd-util.h"
+#include "parse-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "user-util.h"
+#include "memory-util.h"
+
+void close_and_munmap(int fd, void *address, size_t size) {
+ if (size > 0)
+ assert_se(munmap(address, PAGE_ALIGN(size)) >= 0);
+
+ safe_close(fd);
+}
+
+void bus_flush_memfd(sd_bus *b) {
+ assert(b);
+
+ for (unsigned 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);
+}
diff --git a/src/libsystemd/sd-bus/bus-kernel.h b/src/libsystemd/sd-bus/bus-kernel.h
new file mode 100644
index 0000000..be8e0ce
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-kernel.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-bus.h"
+
+#define MEMFD_CACHE_MAX 32
+
+/* When we cache a memfd block for reuse, we will truncate blocks
+ * longer than this in order not to keep too much data around. */
+#define MEMFD_CACHE_ITEM_SIZE_MAX (128*1024)
+
+/* This determines at which minimum size we prefer sending memfds over
+ * sending vectors */
+#define MEMFD_MIN_SIZE (512*1024)
+
+struct memfd_cache {
+ int fd;
+ void *address;
+ size_t mapped;
+ size_t allocated;
+};
+
+void close_and_munmap(int fd, void *address, size_t size);
+void bus_flush_memfd(sd_bus *bus);
diff --git a/src/libsystemd/sd-bus/bus-match.c b/src/libsystemd/sd-bus/bus-match.c
new file mode 100644
index 0000000..157c660
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-match.c
@@ -0,0 +1,1074 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "bus-internal.h"
+#include "bus-match.h"
+#include "bus-message.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "hexdecoct.h"
+#include "sort-util.h"
+#include "string-util.h"
+#include "strv.h"
+
+/* Example:
+ *
+ * A: type=signal,sender=foo,interface=bar
+ * B: type=signal,sender=quux,interface=fips
+ * C: type=signal,sender=quux,interface=waldo
+ * D: type=signal,member=test
+ * E: sender=miau
+ * F: type=signal
+ * G: type=signal
+ *
+ * results in this tree:
+ *
+ * BUS_MATCH_ROOT
+ * + BUS_MATCH_MESSAGE_TYPE
+ * | ` BUS_MATCH_VALUE: value == signal
+ * | + DBUS_MATCH_SENDER
+ * | | + BUS_MATCH_VALUE: value == foo
+ * | | | ` DBUS_MATCH_INTERFACE
+ * | | | ` BUS_MATCH_VALUE: value == bar
+ * | | | ` BUS_MATCH_LEAF: A
+ * | | ` BUS_MATCH_VALUE: value == quux
+ * | | ` DBUS_MATCH_INTERFACE
+ * | | | BUS_MATCH_VALUE: value == fips
+ * | | | ` BUS_MATCH_LEAF: B
+ * | | ` BUS_MATCH_VALUE: value == waldo
+ * | | ` BUS_MATCH_LEAF: C
+ * | + DBUS_MATCH_MEMBER
+ * | | ` BUS_MATCH_VALUE: value == test
+ * | | ` BUS_MATCH_LEAF: D
+ * | + BUS_MATCH_LEAF: F
+ * | ` BUS_MATCH_LEAF: G
+ * ` BUS_MATCH_SENDER
+ * ` BUS_MATCH_VALUE: value == miau
+ * ` BUS_MATCH_LEAF: E
+ */
+
+static bool BUS_MATCH_IS_COMPARE(enum bus_match_node_type t) {
+ return t >= BUS_MATCH_SENDER && t <= BUS_MATCH_ARG_HAS_LAST;
+}
+
+static bool BUS_MATCH_CAN_HASH(enum bus_match_node_type t) {
+ return (t >= BUS_MATCH_MESSAGE_TYPE && t <= BUS_MATCH_PATH) ||
+ (t >= BUS_MATCH_ARG && t <= BUS_MATCH_ARG_LAST) ||
+ (t >= BUS_MATCH_ARG_HAS && t <= BUS_MATCH_ARG_HAS_LAST);
+}
+
+static void bus_match_node_free(struct bus_match_node *node) {
+ assert(node);
+ assert(node->parent);
+ assert(!node->child);
+ assert(node->type != BUS_MATCH_ROOT);
+ assert(node->type < _BUS_MATCH_NODE_TYPE_MAX);
+
+ if (node->parent->child) {
+ /* We are apparently linked into the parent's child
+ * list. Let's remove us from there. */
+ if (node->prev) {
+ assert(node->prev->next == node);
+ node->prev->next = node->next;
+ } else {
+ assert(node->parent->child == node);
+ node->parent->child = node->next;
+ }
+
+ if (node->next)
+ node->next->prev = node->prev;
+ }
+
+ if (node->type == BUS_MATCH_VALUE) {
+ /* We might be in the parent's hash table, so clean
+ * this up */
+
+ if (node->parent->type == BUS_MATCH_MESSAGE_TYPE)
+ hashmap_remove(node->parent->compare.children, UINT_TO_PTR(node->value.u8));
+ else if (BUS_MATCH_CAN_HASH(node->parent->type) && node->value.str)
+ hashmap_remove(node->parent->compare.children, node->value.str);
+
+ free(node->value.str);
+ }
+
+ if (BUS_MATCH_IS_COMPARE(node->type)) {
+ assert(hashmap_isempty(node->compare.children));
+ hashmap_free(node->compare.children);
+ }
+
+ free(node);
+}
+
+static bool bus_match_node_maybe_free(struct bus_match_node *node) {
+ assert(node);
+
+ if (node->type == BUS_MATCH_ROOT)
+ return false;
+
+ if (node->child)
+ return false;
+
+ if (BUS_MATCH_IS_COMPARE(node->type) && !hashmap_isempty(node->compare.children))
+ return true;
+
+ bus_match_node_free(node);
+ return true;
+}
+
+static bool value_node_test(
+ struct bus_match_node *node,
+ enum bus_match_node_type parent_type,
+ uint8_t value_u8,
+ const char *value_str,
+ char **value_strv,
+ sd_bus_message *m) {
+
+ assert(node);
+ assert(node->type == BUS_MATCH_VALUE);
+
+ /* Tests parameters against this value node, doing prefix
+ * magic and stuff. */
+
+ switch (parent_type) {
+
+ case BUS_MATCH_MESSAGE_TYPE:
+ return node->value.u8 == value_u8;
+
+ case BUS_MATCH_SENDER:
+ if (streq_ptr(node->value.str, value_str))
+ return true;
+
+ if (m->creds.mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) {
+ /* on kdbus we have the well known names list
+ * in the credentials, let's make use of that
+ * for an accurate match */
+
+ STRV_FOREACH(i, m->creds.well_known_names)
+ if (streq_ptr(node->value.str, *i))
+ return true;
+
+ } else {
+
+ /* If we don't have kdbus, we don't know the
+ * well-known names of the senders. In that,
+ * let's just hope that dbus-daemon doesn't
+ * send us stuff we didn't want. */
+
+ if (node->value.str[0] != ':' && value_str && value_str[0] == ':')
+ return true;
+ }
+
+ return false;
+
+ case BUS_MATCH_DESTINATION:
+ case BUS_MATCH_INTERFACE:
+ case BUS_MATCH_MEMBER:
+ case BUS_MATCH_PATH:
+ case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST:
+
+ if (value_str)
+ return streq_ptr(node->value.str, value_str);
+
+ return false;
+
+ case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: {
+ STRV_FOREACH(i, value_strv)
+ if (streq_ptr(node->value.str, *i))
+ return true;
+
+ return false;
+ }
+
+ case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST:
+ if (value_str)
+ return namespace_simple_pattern(node->value.str, value_str);
+
+ return false;
+
+ case BUS_MATCH_PATH_NAMESPACE:
+ return path_simple_pattern(node->value.str, value_str);
+
+ case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST:
+ if (value_str)
+ return path_complex_pattern(node->value.str, value_str);
+
+ return false;
+
+ default:
+ assert_not_reached();
+ }
+}
+
+static bool value_node_same(
+ struct bus_match_node *node,
+ enum bus_match_node_type parent_type,
+ uint8_t value_u8,
+ const char *value_str) {
+
+ /* Tests parameters against this value node, not doing prefix
+ * magic and stuff, i.e. this one actually compares the match
+ * itself. */
+
+ assert(node);
+ assert(node->type == BUS_MATCH_VALUE);
+
+ switch (parent_type) {
+
+ case BUS_MATCH_MESSAGE_TYPE:
+ return node->value.u8 == value_u8;
+
+ case BUS_MATCH_SENDER:
+ case BUS_MATCH_DESTINATION:
+ case BUS_MATCH_INTERFACE:
+ case BUS_MATCH_MEMBER:
+ case BUS_MATCH_PATH:
+ case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST:
+ case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST:
+ case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST:
+ case BUS_MATCH_PATH_NAMESPACE:
+ case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST:
+ return streq(node->value.str, value_str);
+
+ default:
+ assert_not_reached();
+ }
+}
+
+int bus_match_run(
+ sd_bus *bus,
+ struct bus_match_node *node,
+ sd_bus_message *m) {
+
+ _cleanup_strv_free_ char **test_strv = NULL;
+ const char *test_str = NULL;
+ uint8_t test_u8 = 0;
+ int r;
+
+ assert(m);
+
+ if (!node)
+ return 0;
+
+ if (bus && bus->match_callbacks_modified)
+ return 0;
+
+ /* Not these special semantics: when traversing the tree we
+ * usually let bus_match_run() when called for a node
+ * recursively invoke bus_match_run(). There's are two
+ * exceptions here though, which are BUS_NODE_ROOT (which
+ * cannot have a sibling), and BUS_NODE_VALUE (whose siblings
+ * are invoked anyway by its parent. */
+
+ switch (node->type) {
+
+ case BUS_MATCH_ROOT:
+
+ /* Run all children. Since we cannot have any siblings
+ * we won't call any. The children of the root node
+ * are compares or leaves, they will automatically
+ * call their siblings. */
+ return bus_match_run(bus, node->child, m);
+
+ case BUS_MATCH_VALUE:
+
+ /* Run all children. We don't execute any siblings, we
+ * assume our caller does that. The children of value
+ * nodes are compares or leaves, they will
+ * automatically call their siblings */
+
+ assert(node->child);
+ return bus_match_run(bus, node->child, m);
+
+ case BUS_MATCH_LEAF:
+
+ if (bus) {
+ /* Don't run this match as long as the AddMatch() call is not complete yet.
+ *
+ * Don't run this match unless the 'after' counter has been reached.
+ *
+ * Don't run this match more than once per iteration */
+
+ if (node->leaf.callback->install_slot ||
+ m->read_counter <= node->leaf.callback->after ||
+ node->leaf.callback->last_iteration == bus->iteration_counter)
+ return bus_match_run(bus, node->next, m);
+
+ node->leaf.callback->last_iteration = bus->iteration_counter;
+ }
+
+ r = sd_bus_message_rewind(m, true);
+ if (r < 0)
+ return r;
+
+ /* Run the callback. And then invoke siblings. */
+ if (node->leaf.callback->callback) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
+ sd_bus_slot *slot;
+
+ slot = container_of(node->leaf.callback, sd_bus_slot, match_callback);
+ if (bus) {
+ bus->current_slot = sd_bus_slot_ref(slot);
+ bus->current_handler = node->leaf.callback->callback;
+ bus->current_userdata = slot->userdata;
+ }
+ r = node->leaf.callback->callback(m, slot->userdata, &error_buffer);
+ if (bus) {
+ bus->current_userdata = NULL;
+ bus->current_handler = NULL;
+ bus->current_slot = sd_bus_slot_unref(slot);
+ }
+
+ r = bus_maybe_reply_error(m, r, &error_buffer);
+ if (r != 0)
+ return r;
+
+ if (bus && bus->match_callbacks_modified)
+ return 0;
+ }
+
+ return bus_match_run(bus, node->next, m);
+
+ case BUS_MATCH_MESSAGE_TYPE:
+ test_u8 = m->header->type;
+ break;
+
+ case BUS_MATCH_SENDER:
+ test_str = m->sender;
+ /* FIXME: resolve test_str from a well-known to a unique name first */
+ break;
+
+ case BUS_MATCH_DESTINATION:
+ test_str = m->destination;
+ break;
+
+ case BUS_MATCH_INTERFACE:
+ test_str = m->interface;
+ break;
+
+ case BUS_MATCH_MEMBER:
+ test_str = m->member;
+ break;
+
+ case BUS_MATCH_PATH:
+ case BUS_MATCH_PATH_NAMESPACE:
+ test_str = m->path;
+ break;
+
+ case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST:
+ (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG, &test_str);
+ break;
+
+ case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST:
+ (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG_PATH, &test_str);
+ break;
+
+ case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST:
+ (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG_NAMESPACE, &test_str);
+ break;
+
+ case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST:
+ (void) bus_message_get_arg_strv(m, node->type - BUS_MATCH_ARG_HAS, &test_strv);
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (BUS_MATCH_CAN_HASH(node->type)) {
+ struct bus_match_node *found;
+
+ /* Lookup via hash table, nice! So let's jump directly. */
+
+ if (test_str)
+ found = hashmap_get(node->compare.children, test_str);
+ else if (test_strv) {
+ STRV_FOREACH(i, test_strv) {
+ found = hashmap_get(node->compare.children, *i);
+ if (found) {
+ r = bus_match_run(bus, found, m);
+ if (r != 0)
+ return r;
+ }
+ }
+
+ found = NULL;
+ } else if (node->type == BUS_MATCH_MESSAGE_TYPE)
+ found = hashmap_get(node->compare.children, UINT_TO_PTR(test_u8));
+ else
+ found = NULL;
+
+ if (found) {
+ r = bus_match_run(bus, found, m);
+ if (r != 0)
+ return r;
+ }
+ } else
+ /* No hash table, so let's iterate manually... */
+ for (struct bus_match_node *c = node->child; c; c = c->next) {
+ if (!value_node_test(c, node->type, test_u8, test_str, test_strv, m))
+ continue;
+
+ r = bus_match_run(bus, c, m);
+ if (r != 0)
+ return r;
+
+ if (bus && bus->match_callbacks_modified)
+ return 0;
+ }
+
+ if (bus && bus->match_callbacks_modified)
+ return 0;
+
+ /* And now, let's invoke our siblings */
+ return bus_match_run(bus, node->next, m);
+}
+
+static int bus_match_add_compare_value(
+ struct bus_match_node *where,
+ enum bus_match_node_type t,
+ uint8_t value_u8,
+ const char *value_str,
+ struct bus_match_node **ret) {
+
+ struct bus_match_node *c, *n = NULL;
+ int r;
+
+ assert(where);
+ assert(IN_SET(where->type, BUS_MATCH_ROOT, BUS_MATCH_VALUE));
+ assert(BUS_MATCH_IS_COMPARE(t));
+ assert(ret);
+
+ for (c = where->child; c && c->type != t; c = c->next)
+ ;
+
+ if (c) {
+ /* Comparison node already exists? Then let's see if the value node exists too. */
+
+ if (t == BUS_MATCH_MESSAGE_TYPE)
+ n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8));
+ else if (BUS_MATCH_CAN_HASH(t))
+ n = hashmap_get(c->compare.children, value_str);
+ else
+ for (n = c->child; n && !value_node_same(n, t, value_u8, value_str); n = n->next)
+ ;
+
+ if (n) {
+ *ret = n;
+ return 0;
+ }
+ } else {
+ /* Comparison node, doesn't exist yet? Then let's create it. */
+
+ c = new0(struct bus_match_node, 1);
+ if (!c) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ c->type = t;
+ c->parent = where;
+ c->next = where->child;
+ if (c->next)
+ c->next->prev = c;
+ where->child = c;
+
+ if (t == BUS_MATCH_MESSAGE_TYPE) {
+ c->compare.children = hashmap_new(NULL);
+ if (!c->compare.children) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ } else if (BUS_MATCH_CAN_HASH(t)) {
+ c->compare.children = hashmap_new(&string_hash_ops);
+ if (!c->compare.children) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+ }
+
+ n = new0(struct bus_match_node, 1);
+ if (!n) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ n->type = BUS_MATCH_VALUE;
+ n->value.u8 = value_u8;
+ if (value_str) {
+ n->value.str = strdup(value_str);
+ if (!n->value.str) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ n->parent = c;
+ if (c->compare.children) {
+
+ if (t == BUS_MATCH_MESSAGE_TYPE)
+ r = hashmap_put(c->compare.children, UINT_TO_PTR(value_u8), n);
+ else
+ r = hashmap_put(c->compare.children, n->value.str, n);
+
+ if (r < 0)
+ goto fail;
+ } else {
+ n->next = c->child;
+ if (n->next)
+ n->next->prev = n;
+ c->child = n;
+ }
+
+ *ret = n;
+ return 1;
+
+fail:
+ if (c)
+ bus_match_node_maybe_free(c);
+
+ if (n) {
+ free(n->value.str);
+ free(n);
+ }
+
+ return r;
+}
+
+static int bus_match_add_leaf(
+ struct bus_match_node *where,
+ struct match_callback *callback) {
+
+ struct bus_match_node *n;
+
+ assert(where);
+ assert(IN_SET(where->type, BUS_MATCH_ROOT, BUS_MATCH_VALUE));
+ assert(callback);
+
+ n = new0(struct bus_match_node, 1);
+ if (!n)
+ return -ENOMEM;
+
+ n->type = BUS_MATCH_LEAF;
+ n->parent = where;
+ n->next = where->child;
+ if (n->next)
+ n->next->prev = n;
+
+ n->leaf.callback = callback;
+ callback->match_node = n;
+
+ where->child = n;
+
+ return 1;
+}
+
+enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n) {
+ assert(k);
+
+ if (n == 4 && startswith(k, "type"))
+ return BUS_MATCH_MESSAGE_TYPE;
+ if (n == 6 && startswith(k, "sender"))
+ return BUS_MATCH_SENDER;
+ if (n == 11 && startswith(k, "destination"))
+ return BUS_MATCH_DESTINATION;
+ if (n == 9 && startswith(k, "interface"))
+ return BUS_MATCH_INTERFACE;
+ if (n == 6 && startswith(k, "member"))
+ return BUS_MATCH_MEMBER;
+ if (n == 4 && startswith(k, "path"))
+ return BUS_MATCH_PATH;
+ if (n == 14 && startswith(k, "path_namespace"))
+ return BUS_MATCH_PATH_NAMESPACE;
+
+ if (n == 4 && startswith(k, "arg")) {
+ int j;
+
+ j = undecchar(k[3]);
+ if (j < 0)
+ return -EINVAL;
+
+ return BUS_MATCH_ARG + j;
+ }
+
+ if (n == 5 && startswith(k, "arg")) {
+ int a, b;
+ enum bus_match_node_type t;
+
+ a = undecchar(k[3]);
+ b = undecchar(k[4]);
+ if (a <= 0 || b < 0)
+ return -EINVAL;
+
+ t = BUS_MATCH_ARG + a * 10 + b;
+ if (t > BUS_MATCH_ARG_LAST)
+ return -EINVAL;
+
+ return t;
+ }
+
+ if (n == 8 && startswith(k, "arg") && startswith(k + 4, "path")) {
+ int j;
+
+ j = undecchar(k[3]);
+ if (j < 0)
+ return -EINVAL;
+
+ return BUS_MATCH_ARG_PATH + j;
+ }
+
+ if (n == 9 && startswith(k, "arg") && startswith(k + 5, "path")) {
+ enum bus_match_node_type t;
+ int a, b;
+
+ a = undecchar(k[3]);
+ b = undecchar(k[4]);
+ if (a <= 0 || b < 0)
+ return -EINVAL;
+
+ t = BUS_MATCH_ARG_PATH + a * 10 + b;
+ if (t > BUS_MATCH_ARG_PATH_LAST)
+ return -EINVAL;
+
+ return t;
+ }
+
+ if (n == 13 && startswith(k, "arg") && startswith(k + 4, "namespace")) {
+ int j;
+
+ j = undecchar(k[3]);
+ if (j < 0)
+ return -EINVAL;
+
+ return BUS_MATCH_ARG_NAMESPACE + j;
+ }
+
+ if (n == 14 && startswith(k, "arg") && startswith(k + 5, "namespace")) {
+ enum bus_match_node_type t;
+ int a, b;
+
+ a = undecchar(k[3]);
+ b = undecchar(k[4]);
+ if (a <= 0 || b < 0)
+ return -EINVAL;
+
+ t = BUS_MATCH_ARG_NAMESPACE + a * 10 + b;
+ if (t > BUS_MATCH_ARG_NAMESPACE_LAST)
+ return -EINVAL;
+
+ return t;
+ }
+
+ if (n == 7 && startswith(k, "arg") && startswith(k + 4, "has")) {
+ int j;
+
+ j = undecchar(k[3]);
+ if (j < 0)
+ return -EINVAL;
+
+ return BUS_MATCH_ARG_HAS + j;
+ }
+
+ if (n == 8 && startswith(k, "arg") && startswith(k + 5, "has")) {
+ enum bus_match_node_type t;
+ int a, b;
+
+ a = undecchar(k[3]);
+ b = undecchar(k[4]);
+ if (a <= 0 || b < 0)
+ return -EINVAL;
+
+ t = BUS_MATCH_ARG_HAS + a * 10 + b;
+ if (t > BUS_MATCH_ARG_HAS_LAST)
+ return -EINVAL;
+
+ return t;
+ }
+
+ return -EINVAL;
+}
+
+static int match_component_compare(const struct bus_match_component *a, const struct bus_match_component *b) {
+ return CMP(a->type, b->type);
+}
+
+void bus_match_parse_free(struct bus_match_component *components, unsigned n_components) {
+ for (unsigned i = 0; i < n_components; i++)
+ free(components[i].value_str);
+
+ free(components);
+}
+
+int bus_match_parse(
+ const char *match,
+ struct bus_match_component **ret_components,
+ unsigned *ret_n_components) {
+
+ struct bus_match_component *components = NULL;
+ unsigned n_components = 0;
+ int r;
+
+ assert(match);
+ assert(ret_components);
+ assert(ret_n_components);
+
+ while (*match != '\0') {
+ const char *eq, *q;
+ enum bus_match_node_type t;
+ unsigned j = 0;
+ _cleanup_free_ char *value = NULL;
+ bool escaped = false, quoted;
+ uint8_t u;
+
+ /* Avahi's match rules appear to include whitespace, skip over it */
+ match += strspn(match, " ");
+
+ eq = strchr(match, '=');
+ if (!eq) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ t = bus_match_node_type_from_string(match, eq - match);
+ if (t < 0) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ quoted = eq[1] == '\'';
+
+ for (q = eq + 1 + quoted;; q++) {
+
+ if (*q == '\0') {
+
+ if (quoted) {
+ r = -EINVAL;
+ goto fail;
+ } else {
+ if (value)
+ value[j] = '\0';
+ break;
+ }
+ }
+
+ if (!escaped) {
+ if (*q == '\\') {
+ escaped = true;
+ continue;
+ }
+
+ if (quoted) {
+ if (*q == '\'') {
+ if (value)
+ value[j] = '\0';
+ break;
+ }
+ } else {
+ if (*q == ',') {
+ if (value)
+ value[j] = '\0';
+ break;
+ }
+ }
+ }
+
+ if (!GREEDY_REALLOC(value, j + 2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ value[j++] = *q;
+ escaped = false;
+ }
+
+ if (!value) {
+ value = strdup("");
+ if (!value) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (t == BUS_MATCH_MESSAGE_TYPE) {
+ r = bus_message_type_from_string(value, &u);
+ if (r < 0)
+ goto fail;
+
+ value = mfree(value);
+ } else
+ u = 0;
+
+ if (!GREEDY_REALLOC(components, n_components + 1)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ components[n_components++] = (struct bus_match_component) {
+ .type = t,
+ .value_str = TAKE_PTR(value),
+ .value_u8 = u,
+ };
+
+ if (q[quoted] == 0)
+ break;
+
+ if (q[quoted] != ',') {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ match = q + 1 + quoted;
+ }
+
+ /* Order the whole thing, so that we always generate the same tree */
+ typesafe_qsort(components, n_components, match_component_compare);
+
+ /* Check for duplicates */
+ for (unsigned i = 0; i+1 < n_components; i++)
+ if (components[i].type == components[i+1].type) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ *ret_components = components;
+ *ret_n_components = n_components;
+
+ return 0;
+
+fail:
+ bus_match_parse_free(components, n_components);
+ return r;
+}
+
+char *bus_match_to_string(struct bus_match_component *components, unsigned n_components) {
+ _cleanup_free_ char *buffer = NULL;
+ size_t size = 0;
+ int r;
+
+ if (n_components <= 0)
+ return strdup("");
+
+ assert(components);
+
+ FILE *f = open_memstream_unlocked(&buffer, &size);
+ if (!f)
+ return NULL;
+
+ for (unsigned i = 0; i < n_components; i++) {
+ char buf[32];
+
+ if (i != 0)
+ fputc(',', f);
+
+ fputs(bus_match_node_type_to_string(components[i].type, buf, sizeof(buf)), f);
+ fputc('=', f);
+ fputc('\'', f);
+
+ if (components[i].type == BUS_MATCH_MESSAGE_TYPE)
+ fputs(bus_message_type_to_string(components[i].value_u8), f);
+ else
+ fputs(components[i].value_str, f);
+
+ fputc('\'', f);
+ }
+
+ r = fflush_and_check(f);
+ safe_fclose(f);
+ if (r < 0)
+ return NULL;
+ return TAKE_PTR(buffer);
+}
+
+int bus_match_add(
+ struct bus_match_node *root,
+ struct bus_match_component *components,
+ unsigned n_components,
+ struct match_callback *callback) {
+
+ int r;
+
+ assert(root);
+ assert(callback);
+
+ for (unsigned i = 0; i < n_components; i++) {
+ r = bus_match_add_compare_value(root,
+ components[i].type,
+ components[i].value_u8,
+ components[i].value_str,
+ &root);
+ if (r < 0)
+ return r;
+ }
+
+ return bus_match_add_leaf(root, callback);
+}
+
+int bus_match_remove(
+ struct bus_match_node *root,
+ struct match_callback *callback) {
+
+ struct bus_match_node *node, *pp;
+
+ assert(root);
+ assert(callback);
+
+ node = callback->match_node;
+ if (!node)
+ return 0;
+
+ assert(node->type == BUS_MATCH_LEAF);
+
+ callback->match_node = NULL;
+
+ /* Free the leaf */
+ pp = node->parent;
+ bus_match_node_free(node);
+
+ /* Prune the tree above */
+ while (pp) {
+ node = pp;
+ pp = node->parent;
+
+ if (!bus_match_node_maybe_free(node))
+ break;
+ }
+
+ return 1;
+}
+
+void bus_match_free(struct bus_match_node *node) {
+ struct bus_match_node *c;
+
+ if (!node)
+ return;
+
+ if (BUS_MATCH_CAN_HASH(node->type)) {
+
+ HASHMAP_FOREACH(c, node->compare.children)
+ bus_match_free(c);
+
+ assert(hashmap_isempty(node->compare.children));
+ }
+
+ while ((c = node->child))
+ bus_match_free(c);
+
+ if (node->type != BUS_MATCH_ROOT)
+ bus_match_node_free(node);
+}
+
+const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l) {
+ switch (t) {
+
+ case BUS_MATCH_ROOT:
+ return "root";
+
+ case BUS_MATCH_VALUE:
+ return "value";
+
+ case BUS_MATCH_LEAF:
+ return "leaf";
+
+ case BUS_MATCH_MESSAGE_TYPE:
+ return "type";
+
+ case BUS_MATCH_SENDER:
+ return "sender";
+
+ case BUS_MATCH_DESTINATION:
+ return "destination";
+
+ case BUS_MATCH_INTERFACE:
+ return "interface";
+
+ case BUS_MATCH_MEMBER:
+ return "member";
+
+ case BUS_MATCH_PATH:
+ return "path";
+
+ case BUS_MATCH_PATH_NAMESPACE:
+ return "path_namespace";
+
+ case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST:
+ return snprintf_ok(buf, l, "arg%i", t - BUS_MATCH_ARG);
+
+ case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST:
+ return snprintf_ok(buf, l, "arg%ipath", t - BUS_MATCH_ARG_PATH);
+
+ case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST:
+ return snprintf_ok(buf, l, "arg%inamespace", t - BUS_MATCH_ARG_NAMESPACE);
+
+ case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST:
+ return snprintf_ok(buf, l, "arg%ihas", t - BUS_MATCH_ARG_HAS);
+
+ default:
+ return NULL;
+ }
+}
+
+void bus_match_dump(FILE *out, struct bus_match_node *node, unsigned level) {
+ char buf[32];
+
+ if (!node)
+ return;
+
+ fprintf(out, "%*s[%s]", 2 * (int) level, "", bus_match_node_type_to_string(node->type, buf, sizeof(buf)));
+
+ if (node->type == BUS_MATCH_VALUE) {
+ if (node->parent->type == BUS_MATCH_MESSAGE_TYPE)
+ fprintf(out, " <%u>\n", node->value.u8);
+ else
+ fprintf(out, " <%s>\n", node->value.str);
+ } else if (node->type == BUS_MATCH_ROOT)
+ fputs(" root\n", out);
+ else if (node->type == BUS_MATCH_LEAF)
+ fprintf(out, " %p/%p\n", node->leaf.callback->callback,
+ container_of(node->leaf.callback, sd_bus_slot, match_callback)->userdata);
+ else
+ putc('\n', out);
+
+ if (BUS_MATCH_CAN_HASH(node->type)) {
+ struct bus_match_node *c;
+ HASHMAP_FOREACH(c, node->compare.children)
+ bus_match_dump(out, c, level + 1);
+ }
+
+ for (struct bus_match_node *c = node->child; c; c = c->next)
+ bus_match_dump(out, c, level + 1);
+}
+
+enum bus_match_scope bus_match_get_scope(const struct bus_match_component *components, unsigned n_components) {
+ bool found_driver = false;
+
+ if (n_components <= 0)
+ return BUS_MATCH_GENERIC;
+
+ assert(components);
+
+ /* Checks whether the specified match can only match the
+ * pseudo-service for local messages, which we detect by
+ * sender, interface or path. If a match is not restricted to
+ * local messages, then we check if it only matches on the
+ * driver. */
+
+ for (unsigned i = 0; i < n_components; i++) {
+ const struct bus_match_component *c = components + i;
+
+ if (c->type == BUS_MATCH_SENDER) {
+ if (streq_ptr(c->value_str, "org.freedesktop.DBus.Local"))
+ return BUS_MATCH_LOCAL;
+
+ if (streq_ptr(c->value_str, "org.freedesktop.DBus"))
+ found_driver = true;
+ }
+
+ if (c->type == BUS_MATCH_INTERFACE && streq_ptr(c->value_str, "org.freedesktop.DBus.Local"))
+ return BUS_MATCH_LOCAL;
+
+ if (c->type == BUS_MATCH_PATH && streq_ptr(c->value_str, "/org/freedesktop/DBus/Local"))
+ return BUS_MATCH_LOCAL;
+ }
+
+ return found_driver ? BUS_MATCH_DRIVER : BUS_MATCH_GENERIC;
+}
diff --git a/src/libsystemd/sd-bus/bus-match.h b/src/libsystemd/sd-bus/bus-match.h
new file mode 100644
index 0000000..6042f90
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-match.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdio.h>
+
+#include "sd-bus.h"
+
+#include "hashmap.h"
+
+enum bus_match_node_type {
+ BUS_MATCH_ROOT,
+ BUS_MATCH_VALUE,
+ BUS_MATCH_LEAF,
+
+ /* The following are all different kinds of compare nodes */
+ BUS_MATCH_SENDER,
+ BUS_MATCH_MESSAGE_TYPE,
+ BUS_MATCH_DESTINATION,
+ BUS_MATCH_INTERFACE,
+ BUS_MATCH_MEMBER,
+ BUS_MATCH_PATH,
+ BUS_MATCH_PATH_NAMESPACE,
+ BUS_MATCH_ARG,
+ BUS_MATCH_ARG_LAST = BUS_MATCH_ARG + 63,
+ BUS_MATCH_ARG_PATH,
+ BUS_MATCH_ARG_PATH_LAST = BUS_MATCH_ARG_PATH + 63,
+ BUS_MATCH_ARG_NAMESPACE,
+ BUS_MATCH_ARG_NAMESPACE_LAST = BUS_MATCH_ARG_NAMESPACE + 63,
+ BUS_MATCH_ARG_HAS,
+ BUS_MATCH_ARG_HAS_LAST = BUS_MATCH_ARG_HAS + 63,
+ _BUS_MATCH_NODE_TYPE_MAX,
+ _BUS_MATCH_NODE_TYPE_INVALID = -EINVAL,
+};
+
+struct bus_match_node {
+ enum bus_match_node_type type;
+ struct bus_match_node *parent, *next, *prev, *child;
+
+ union {
+ struct {
+ char *str;
+ uint8_t u8;
+ } value;
+ struct {
+ struct match_callback *callback;
+ } leaf;
+ struct {
+ /* If this is set, then the child is NULL */
+ Hashmap *children;
+ } compare;
+ };
+};
+
+struct bus_match_component {
+ enum bus_match_node_type type;
+ uint8_t value_u8;
+ char *value_str;
+};
+
+enum bus_match_scope {
+ BUS_MATCH_GENERIC,
+ BUS_MATCH_LOCAL,
+ BUS_MATCH_DRIVER,
+};
+
+int bus_match_run(sd_bus *bus, struct bus_match_node *root, sd_bus_message *m);
+
+int bus_match_add(struct bus_match_node *root, struct bus_match_component *components, unsigned n_components, struct match_callback *callback);
+int bus_match_remove(struct bus_match_node *root, struct match_callback *callback);
+
+void bus_match_free(struct bus_match_node *node);
+
+void bus_match_dump(FILE *out, struct bus_match_node *node, unsigned level);
+
+const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l);
+enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n);
+
+int bus_match_parse(const char *match, struct bus_match_component **ret_components, unsigned *ret_n_components);
+void bus_match_parse_free(struct bus_match_component *components, unsigned n_components);
+char *bus_match_to_string(struct bus_match_component *components, unsigned n_components);
+
+enum bus_match_scope bus_match_get_scope(const struct bus_match_component *components, unsigned n_components);
diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c
new file mode 100644
index 0000000..0fe80af
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-message.c
@@ -0,0 +1,4669 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-internal.h"
+#include "bus-message.h"
+#include "bus-signature.h"
+#include "bus-type.h"
+#include "fd-util.h"
+#include "io-util.h"
+#include "memfd-util.h"
+#include "memory-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+#include "utf8.h"
+
+static int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored);
+static int message_parse_fields(sd_bus_message *m);
+
+static void *adjust_pointer(const void *p, void *old_base, size_t sz, void *new_base) {
+
+ if (!p)
+ return NULL;
+
+ if (old_base == new_base)
+ return (void*) p;
+
+ if ((uint8_t*) p < (uint8_t*) old_base)
+ return (void*) p;
+
+ if ((uint8_t*) p >= (uint8_t*) old_base + sz)
+ return (void*) p;
+
+ return (uint8_t*) new_base + ((uint8_t*) p - (uint8_t*) old_base);
+}
+
+static void message_free_part(sd_bus_message *m, struct bus_body_part *part) {
+ assert(m);
+ assert(part);
+
+ if (part->memfd >= 0) {
+ /* erase if requested, but only if the memfd is not sealed yet, i.e. is writable */
+ if (m->sensitive && !m->sealed)
+ explicit_bzero_safe(part->data, part->size);
+
+ close_and_munmap(part->memfd, part->mmap_begin, part->mapped);
+ } else if (part->munmap_this)
+ /* We don't erase sensitive data here, since the data is memory mapped from someone else, and
+ * we just don't know if it's OK to write to it */
+ munmap(part->mmap_begin, part->mapped);
+ else {
+ /* Erase this if that is requested. Since this is regular memory we know we can write it. */
+ if (m->sensitive)
+ explicit_bzero_safe(part->data, part->size);
+
+ if (part->free_this)
+ free(part->data);
+ }
+
+ if (part != &m->body)
+ free(part);
+}
+
+static void message_reset_parts(sd_bus_message *m) {
+ struct bus_body_part *part;
+
+ assert(m);
+
+ part = &m->body;
+ while (m->n_body_parts > 0) {
+ struct bus_body_part *next = part->next;
+ message_free_part(m, part);
+ part = next;
+ m->n_body_parts--;
+ }
+
+ m->body_end = NULL;
+
+ m->cached_rindex_part = NULL;
+ m->cached_rindex_part_begin = 0;
+}
+
+static struct bus_container *message_get_last_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_last_container(m);
+
+ free(c->signature);
+ free(c->peeked_signature);
+
+ /* 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);
+
+ while (m->n_containers > 0)
+ message_free_last_container(m);
+
+ m->containers = mfree(m->containers);
+ m->root_container.index = 0;
+}
+
+static sd_bus_message* message_free(sd_bus_message *m) {
+ assert(m);
+
+ message_reset_parts(m);
+
+ if (m->free_header)
+ free(m->header);
+
+ /* Note that we don't unref m->bus here. That's already done by sd_bus_message_unref() as each user
+ * reference to the bus message also is considered a reference to the bus connection itself. */
+
+ if (m->free_fds) {
+ close_many(m->fds, m->n_fds);
+ free(m->fds);
+ }
+
+ if (m->iovec != m->iovec_fixed)
+ free(m->iovec);
+
+ message_reset_containers(m);
+ assert(m->n_containers == 0);
+ message_free_last_container(m);
+
+ bus_creds_done(&m->creds);
+ return mfree(m);
+}
+
+static void *message_extend_fields(sd_bus_message *m, size_t sz, bool add_offset) {
+ void *op, *np;
+ size_t old_size, new_size, start;
+
+ assert(m);
+
+ if (m->poisoned)
+ return NULL;
+
+ old_size = sizeof(struct bus_header) + m->fields_size;
+ start = ALIGN8(old_size);
+ new_size = start + sz;
+
+ if (new_size < start || new_size > UINT32_MAX)
+ goto poison;
+
+ if (old_size == new_size)
+ return (uint8_t*) m->header + old_size;
+
+ if (m->free_header) {
+ np = realloc(m->header, ALIGN8(new_size));
+ if (!np)
+ goto poison;
+ } else {
+ /* Initially, the header is allocated as part of
+ * the sd_bus_message itself, let's replace it by
+ * dynamic data */
+
+ np = malloc(ALIGN8(new_size));
+ if (!np)
+ goto poison;
+
+ memcpy(np, m->header, sizeof(struct bus_header));
+ }
+
+ /* Zero out padding */
+ if (start > old_size)
+ memzero((uint8_t*) np + old_size, start - old_size);
+
+ op = m->header;
+ m->header = np;
+ m->fields_size = new_size - sizeof(struct bus_header);
+
+ /* Adjust quick access pointers */
+ m->path = adjust_pointer(m->path, op, old_size, m->header);
+ m->interface = adjust_pointer(m->interface, op, old_size, m->header);
+ m->member = adjust_pointer(m->member, op, old_size, m->header);
+ m->destination = adjust_pointer(m->destination, op, old_size, m->header);
+ m->sender = adjust_pointer(m->sender, op, old_size, m->header);
+ m->error.name = adjust_pointer(m->error.name, op, old_size, m->header);
+
+ m->free_header = true;
+
+ if (add_offset) {
+ if (m->n_header_offsets >= ELEMENTSOF(m->header_offsets))
+ goto poison;
+
+ m->header_offsets[m->n_header_offsets++] = new_size - sizeof(struct bus_header);
+ }
+
+ return (uint8_t*) np + start;
+
+poison:
+ m->poisoned = true;
+ return NULL;
+}
+
+static int message_append_field_string(
+ sd_bus_message *m,
+ uint64_t h,
+ char type,
+ const char *s,
+ const char **ret) {
+
+ size_t l;
+ uint8_t *p;
+
+ assert(m);
+
+ /* dbus only allows 8bit header field ids */
+ if (h > 0xFF)
+ return -EINVAL;
+
+ /* dbus doesn't allow strings over 32bit */
+ l = strlen(s);
+ if (l > UINT32_MAX)
+ return -EINVAL;
+
+ /* Signature "(yv)" where the variant contains "s" */
+
+ /* (field id byte + (signature length + signature 's' + NUL) + (string length + string + NUL)) */
+ p = message_extend_fields(m, 4 + 4 + l + 1, false);
+ if (!p)
+ return -ENOMEM;
+
+ p[0] = (uint8_t) h;
+ p[1] = 1;
+ p[2] = type;
+ p[3] = 0;
+
+ ((uint32_t*) p)[1] = l;
+ memcpy(p + 8, s, l + 1);
+
+ if (ret)
+ *ret = (char*) p + 8;
+
+ return 0;
+}
+
+static int message_append_field_signature(
+ sd_bus_message *m,
+ uint64_t h,
+ const char *s,
+ const char **ret) {
+
+ size_t l;
+ uint8_t *p;
+
+ assert(m);
+
+ /* dbus only allows 8bit header field ids */
+ if (h > 0xFF)
+ return -EINVAL;
+
+ /* dbus doesn't allow signatures over 8bit */
+ l = strlen(s);
+ if (l > SD_BUS_MAXIMUM_SIGNATURE_LENGTH)
+ return -EINVAL;
+
+ /* Signature "(yv)" where the variant contains "g" */
+
+ /* (field id byte + (signature length + signature 'g' + NUL) + (string length + string + NUL)) */
+ p = message_extend_fields(m, 4 + 1 + l + 1, false);
+ if (!p)
+ return -ENOMEM;
+
+ p[0] = (uint8_t) h;
+ p[1] = 1;
+ p[2] = SD_BUS_TYPE_SIGNATURE;
+ p[3] = 0;
+ p[4] = l;
+ memcpy(p + 5, s, l + 1);
+
+ if (ret)
+ *ret = (const char*) p + 5;
+
+ return 0;
+}
+
+static int message_append_field_uint32(sd_bus_message *m, uint64_t h, uint32_t x) {
+ uint8_t *p;
+
+ assert(m);
+
+ /* dbus only allows 8bit header field ids */
+ if (h > 0xFF)
+ return -EINVAL;
+
+ /* (field id byte + (signature length + signature 'u' + NUL) + value) */
+ p = message_extend_fields(m, 4 + 4, false);
+ if (!p)
+ return -ENOMEM;
+
+ p[0] = (uint8_t) h;
+ p[1] = 1;
+ p[2] = 'u';
+ p[3] = 0;
+
+ ((uint32_t*) p)[1] = x;
+
+ return 0;
+}
+
+static int message_append_reply_cookie(sd_bus_message *m, uint64_t cookie) {
+ assert(m);
+
+ /* 64bit cookies are not supported */
+ if (cookie > UINT32_MAX)
+ return -EOPNOTSUPP;
+
+ return message_append_field_uint32(m, BUS_MESSAGE_HEADER_REPLY_SERIAL, (uint32_t) cookie);
+}
+
+static int message_from_header(
+ sd_bus *bus,
+ void *buffer,
+ size_t message_size,
+ int *fds,
+ size_t n_fds,
+ const char *label,
+ sd_bus_message **ret) {
+
+ _cleanup_free_ sd_bus_message *m = NULL;
+ struct bus_header *h;
+ size_t a, label_sz = 0; /* avoid false maybe-uninitialized warning */
+
+ assert(bus);
+ assert(buffer || message_size <= 0);
+ assert(fds || n_fds <= 0);
+ assert(ret);
+
+ if (message_size < sizeof(struct bus_header))
+ return -EBADMSG;
+
+ h = buffer;
+ if (!IN_SET(h->version, 1, 2))
+ return -EBADMSG;
+
+ if (h->type == _SD_BUS_MESSAGE_TYPE_INVALID)
+ return -EBADMSG;
+
+ if (!IN_SET(h->endian, BUS_LITTLE_ENDIAN, BUS_BIG_ENDIAN))
+ return -EBADMSG;
+
+ /* Note that we are happy with unknown flags in the flags header! */
+
+ a = ALIGN(sizeof(sd_bus_message));
+
+ if (label) {
+ label_sz = strlen(label);
+ a += label_sz + 1;
+ }
+
+ m = malloc0(a);
+ if (!m)
+ return -ENOMEM;
+
+ m->sealed = true;
+ m->header = buffer;
+
+ if (h->serial == 0)
+ return -EBADMSG;
+
+ m->fields_size = BUS_MESSAGE_BSWAP32(m, h->fields_size);
+ m->body_size = BUS_MESSAGE_BSWAP32(m, h->body_size);
+
+ assert(message_size >= sizeof(struct bus_header));
+ if (ALIGN8(m->fields_size) > message_size - sizeof(struct bus_header) ||
+ m->body_size != message_size - sizeof(struct bus_header) - ALIGN8(m->fields_size))
+ return -EBADMSG;
+
+ m->fds = fds;
+ m->n_fds = n_fds;
+
+ if (label) {
+ m->creds.label = (char*) m + ALIGN(sizeof(sd_bus_message));
+ memcpy(m->creds.label, label, label_sz + 1);
+
+ m->creds.mask |= SD_BUS_CREDS_SELINUX_CONTEXT;
+ }
+
+ m->n_ref = 1;
+ m->bus = sd_bus_ref(bus);
+
+ *ret = TAKE_PTR(m);
+
+ return 0;
+}
+
+int bus_message_from_malloc(
+ sd_bus *bus,
+ void *buffer,
+ size_t length,
+ int *fds,
+ size_t n_fds,
+ const char *label,
+ sd_bus_message **ret) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ size_t sz;
+ int r;
+
+ r = message_from_header(
+ bus,
+ buffer, length,
+ fds, n_fds,
+ label,
+ &m);
+ if (r < 0)
+ return r;
+
+ sz = length - sizeof(struct bus_header) - ALIGN8(m->fields_size);
+ if (sz > 0) {
+ m->n_body_parts = 1;
+ m->body.data = (uint8_t*) buffer + sizeof(struct bus_header) + ALIGN8(m->fields_size);
+ m->body.size = sz;
+ m->body.sealed = true;
+ m->body.memfd = -1;
+ }
+
+ m->n_iovec = 1;
+ m->iovec = m->iovec_fixed;
+ m->iovec[0] = IOVEC_MAKE(buffer, length);
+
+ r = message_parse_fields(m);
+ if (r < 0)
+ return r;
+
+ /* We take possession of the memory and fds now */
+ m->free_header = true;
+ m->free_fds = true;
+
+ *ret = TAKE_PTR(m);
+ return 0;
+}
+
+_public_ int sd_bus_message_new(
+ sd_bus *bus,
+ sd_bus_message **m,
+ uint8_t type) {
+
+ assert_return(bus, -ENOTCONN);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state != BUS_UNSET, -ENOTCONN);
+ assert_return(m, -EINVAL);
+ /* Creation of messages with _SD_BUS_MESSAGE_TYPE_INVALID is allowed. */
+ assert_return(type < _SD_BUS_MESSAGE_TYPE_MAX, -EINVAL);
+
+ sd_bus_message *t = malloc0(ALIGN(sizeof(sd_bus_message)) + sizeof(struct bus_header));
+ if (!t)
+ return -ENOMEM;
+
+ t->n_ref = 1;
+ t->bus = sd_bus_ref(bus);
+ t->header = (struct bus_header*) ((uint8_t*) t + ALIGN(sizeof(struct sd_bus_message)));
+ t->header->endian = BUS_NATIVE_ENDIAN;
+ t->header->type = type;
+ t->header->version = bus->message_version;
+ t->allow_fds = bus->can_fds || !IN_SET(bus->state, BUS_HELLO, BUS_RUNNING);
+
+ if (bus->allow_interactive_authorization)
+ t->header->flags |= BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION;
+
+ *m = t;
+ return 0;
+}
+
+_public_ int sd_bus_message_new_signal(
+ sd_bus *bus,
+ sd_bus_message **m,
+ const char *path,
+ const char *interface,
+ const char *member) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *t = NULL;
+ int r;
+
+ assert_return(bus, -ENOTCONN);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state != BUS_UNSET, -ENOTCONN);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(interface_name_is_valid(interface), -EINVAL);
+ assert_return(member_name_is_valid(member), -EINVAL);
+ assert_return(m, -EINVAL);
+
+ r = sd_bus_message_new(bus, &t, SD_BUS_MESSAGE_SIGNAL);
+ if (r < 0)
+ return -ENOMEM;
+
+ assert(t);
+
+ t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED;
+
+ r = message_append_field_string(t, BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path);
+ if (r < 0)
+ return r;
+ r = message_append_field_string(t, BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface);
+ if (r < 0)
+ return r;
+ r = message_append_field_string(t, BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member);
+ if (r < 0)
+ return r;
+
+ *m = TAKE_PTR(t);
+ return 0;
+}
+
+_public_ int sd_bus_message_new_method_call(
+ sd_bus *bus,
+ sd_bus_message **m,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *t = NULL;
+ int r;
+
+ assert_return(bus, -ENOTCONN);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state != BUS_UNSET, -ENOTCONN);
+ assert_return(!destination || service_name_is_valid(destination), -EINVAL);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(!interface || interface_name_is_valid(interface), -EINVAL);
+ assert_return(member_name_is_valid(member), -EINVAL);
+ assert_return(m, -EINVAL);
+
+ r = sd_bus_message_new(bus, &t, SD_BUS_MESSAGE_METHOD_CALL);
+ if (r < 0)
+ return -ENOMEM;
+
+ assert(t);
+
+ r = message_append_field_string(t, BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path);
+ if (r < 0)
+ return r;
+ r = message_append_field_string(t, BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member);
+ if (r < 0)
+ return r;
+
+ if (interface) {
+ r = message_append_field_string(t, BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface);
+ if (r < 0)
+ return r;
+ }
+
+ if (destination) {
+ r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &t->destination);
+ if (r < 0)
+ return r;
+ }
+
+ *m = TAKE_PTR(t);
+ return 0;
+}
+
+static int message_new_reply(
+ sd_bus_message *call,
+ uint8_t type,
+ sd_bus_message **m) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *t = NULL;
+ uint64_t cookie;
+ int r;
+
+ assert_return(call, -EINVAL);
+ assert_return(call->sealed, -EPERM);
+ assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
+ assert_return(call->bus->state != BUS_UNSET, -ENOTCONN);
+ assert_return(m, -EINVAL);
+
+ cookie = BUS_MESSAGE_COOKIE(call);
+ if (cookie == 0)
+ return -EOPNOTSUPP;
+
+ r = sd_bus_message_new(call->bus, &t, type);
+ if (r < 0)
+ return -ENOMEM;
+
+ assert(t);
+
+ t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED;
+ t->reply_cookie = cookie;
+ r = message_append_reply_cookie(t, t->reply_cookie);
+ if (r < 0)
+ return r;
+
+ if (call->sender) {
+ r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, call->sender, &t->destination);
+ if (r < 0)
+ return r;
+ }
+
+ t->dont_send = !!(call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED);
+ t->enforced_reply_signature = call->enforced_reply_signature;
+
+ /* let's copy the sensitive flag over. Let's do that as a safety precaution to keep a transaction
+ * wholly sensitive if already the incoming message was sensitive. This is particularly useful when a
+ * vtable record sets the SD_BUS_VTABLE_SENSITIVE flag on a method call, since this means it applies
+ * to both the message call and the reply. */
+ t->sensitive = call->sensitive;
+
+ *m = TAKE_PTR(t);
+ return 0;
+}
+
+_public_ int sd_bus_message_new_method_return(
+ sd_bus_message *call,
+ sd_bus_message **m) {
+
+ return message_new_reply(call, SD_BUS_MESSAGE_METHOD_RETURN, m);
+}
+
+_public_ int sd_bus_message_new_method_error(
+ sd_bus_message *call,
+ sd_bus_message **m,
+ const sd_bus_error *e) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *t = NULL;
+ int r;
+
+ assert_return(sd_bus_error_is_set(e), -EINVAL);
+ assert_return(m, -EINVAL);
+
+ r = message_new_reply(call, SD_BUS_MESSAGE_METHOD_ERROR, &t);
+ if (r < 0)
+ return r;
+
+ r = message_append_field_string(t, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, e->name, &t->error.name);
+ if (r < 0)
+ return r;
+
+ if (e->message) {
+ r = message_append_basic(t, SD_BUS_TYPE_STRING, e->message, (const void**) &t->error.message);
+ if (r < 0)
+ return r;
+ }
+
+ t->error._need_free = -1;
+
+ *m = TAKE_PTR(t);
+ return 0;
+}
+
+_public_ int sd_bus_message_new_method_errorf(
+ sd_bus_message *call,
+ sd_bus_message **m,
+ const char *name,
+ const char *format,
+ ...) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ va_list ap;
+
+ assert_return(name, -EINVAL);
+ assert_return(m, -EINVAL);
+
+ va_start(ap, format);
+ sd_bus_error_setfv(&error, name, format, ap);
+ va_end(ap);
+
+ return sd_bus_message_new_method_error(call, m, &error);
+}
+
+_public_ int sd_bus_message_new_method_errno(
+ sd_bus_message *call,
+ sd_bus_message **m,
+ int error,
+ const sd_bus_error *p) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL;
+
+ if (sd_bus_error_is_set(p))
+ return sd_bus_message_new_method_error(call, m, p);
+
+ sd_bus_error_set_errno(&berror, error);
+
+ return sd_bus_message_new_method_error(call, m, &berror);
+}
+
+_public_ int sd_bus_message_new_method_errnof(
+ sd_bus_message *call,
+ sd_bus_message **m,
+ int error,
+ const char *format,
+ ...) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL;
+ va_list ap;
+
+ va_start(ap, format);
+ sd_bus_error_set_errnofv(&berror, error, format, ap);
+ va_end(ap);
+
+ return sd_bus_message_new_method_error(call, m, &berror);
+}
+
+void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m) {
+ assert(bus);
+ assert(m);
+
+ m->sender = m->creds.unique_name = (char*) "org.freedesktop.DBus.Local";
+ m->creds.well_known_names_local = true;
+ m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask;
+}
+
+void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m) {
+ assert(bus);
+ assert(m);
+
+ m->sender = m->creds.unique_name = (char*) "org.freedesktop.DBus";
+ m->creds.well_known_names_driver = true;
+ m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask;
+}
+
+int bus_message_new_synthetic_error(
+ sd_bus *bus,
+ uint64_t cookie,
+ const sd_bus_error *e,
+ sd_bus_message **m) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *t = NULL;
+ int r;
+
+ assert(bus);
+ assert(sd_bus_error_is_set(e));
+ assert(m);
+
+ r = sd_bus_message_new(bus, &t, SD_BUS_MESSAGE_METHOD_ERROR);
+ if (r < 0)
+ return -ENOMEM;
+
+ assert(t);
+
+ t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED;
+ t->reply_cookie = cookie;
+
+ r = message_append_reply_cookie(t, t->reply_cookie);
+ if (r < 0)
+ return r;
+
+ if (bus && bus->unique_name) {
+ r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, bus->unique_name, &t->destination);
+ if (r < 0)
+ return r;
+ }
+
+ r = message_append_field_string(t, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, e->name, &t->error.name);
+ if (r < 0)
+ return r;
+
+ if (e->message) {
+ r = message_append_basic(t, SD_BUS_TYPE_STRING, e->message, (const void**) &t->error.message);
+ if (r < 0)
+ return r;
+ }
+
+ t->error._need_free = -1;
+
+ bus_message_set_sender_driver(bus, t);
+
+ *m = TAKE_PTR(t);
+ return 0;
+}
+
+_public_ sd_bus_message* sd_bus_message_ref(sd_bus_message *m) {
+ if (!m)
+ return NULL;
+
+ /* We are fine if this message so far was either explicitly reffed or not reffed but queued into at
+ * least one bus connection object. */
+ assert(m->n_ref > 0 || m->n_queued > 0);
+
+ m->n_ref++;
+
+ /* Each user reference to a bus message shall also be considered a ref on the bus */
+ sd_bus_ref(m->bus);
+ return m;
+}
+
+_public_ sd_bus_message* sd_bus_message_unref(sd_bus_message *m) {
+ if (!m)
+ return NULL;
+
+ assert(m->n_ref > 0);
+
+ sd_bus_unref(m->bus); /* Each regular ref is also a ref on the bus connection. Let's hence drop it
+ * here. Note we have to do this before decrementing our own n_ref here, since
+ * otherwise, if this message is currently queued sd_bus_unref() might call
+ * bus_message_unref_queued() for this which might then destroy the message
+ * while we are still processing it. */
+ m->n_ref--;
+
+ if (m->n_ref > 0 || m->n_queued > 0)
+ return NULL;
+
+ /* Unset the bus field if neither the user has a reference nor this message is queued. We are careful
+ * to reset the field only after the last reference to the bus is dropped, after all we might keep
+ * multiple references to the bus, once for each reference kept on ourselves. */
+ m->bus = NULL;
+
+ return message_free(m);
+}
+
+sd_bus_message* bus_message_ref_queued(sd_bus_message *m, sd_bus *bus) {
+ if (!m)
+ return NULL;
+
+ /* If this is a different bus than the message is associated with, then implicitly turn this into a
+ * regular reference. This means that you can create a memory leak by enqueuing a message generated
+ * on one bus onto another at the same time as enqueueing a message from the second one on the first,
+ * as we'll not detect the cyclic references there. */
+ if (bus != m->bus)
+ return sd_bus_message_ref(m);
+
+ assert(m->n_ref > 0 || m->n_queued > 0);
+ m->n_queued++;
+
+ return m;
+}
+
+sd_bus_message* bus_message_unref_queued(sd_bus_message *m, sd_bus *bus) {
+ if (!m)
+ return NULL;
+
+ if (bus != m->bus)
+ return sd_bus_message_unref(m);
+
+ assert(m->n_queued > 0);
+ m->n_queued--;
+
+ if (m->n_ref > 0 || m->n_queued > 0)
+ return NULL;
+
+ m->bus = NULL;
+
+ return message_free(m);
+}
+
+_public_ int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type) {
+ assert_return(m, -EINVAL);
+ assert_return(type, -EINVAL);
+
+ *type = m->header->type;
+ return 0;
+}
+
+_public_ int sd_bus_message_get_cookie(sd_bus_message *m, uint64_t *cookie) {
+ uint64_t c;
+
+ assert_return(m, -EINVAL);
+ assert_return(cookie, -EINVAL);
+
+ c = BUS_MESSAGE_COOKIE(m);
+ if (c == 0)
+ return -ENODATA;
+
+ *cookie = BUS_MESSAGE_COOKIE(m);
+ return 0;
+}
+
+_public_ int sd_bus_message_get_reply_cookie(sd_bus_message *m, uint64_t *cookie) {
+ assert_return(m, -EINVAL);
+ assert_return(cookie, -EINVAL);
+
+ if (m->reply_cookie == 0)
+ return -ENODATA;
+
+ *cookie = m->reply_cookie;
+ return 0;
+}
+
+_public_ int sd_bus_message_get_expect_reply(sd_bus_message *m) {
+ assert_return(m, -EINVAL);
+
+ return m->header->type == SD_BUS_MESSAGE_METHOD_CALL &&
+ !(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED);
+}
+
+_public_ int sd_bus_message_get_auto_start(sd_bus_message *m) {
+ assert_return(m, -EINVAL);
+
+ return !(m->header->flags & BUS_MESSAGE_NO_AUTO_START);
+}
+
+_public_ int sd_bus_message_get_allow_interactive_authorization(sd_bus_message *m) {
+ assert_return(m, -EINVAL);
+
+ return m->header->type == SD_BUS_MESSAGE_METHOD_CALL &&
+ (m->header->flags & BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION);
+}
+
+_public_ const char *sd_bus_message_get_path(sd_bus_message *m) {
+ assert_return(m, NULL);
+
+ return m->path;
+}
+
+_public_ const char *sd_bus_message_get_interface(sd_bus_message *m) {
+ assert_return(m, NULL);
+
+ return m->interface;
+}
+
+_public_ const char *sd_bus_message_get_member(sd_bus_message *m) {
+ assert_return(m, NULL);
+
+ return m->member;
+}
+
+_public_ const char *sd_bus_message_get_destination(sd_bus_message *m) {
+ assert_return(m, NULL);
+
+ return m->destination;
+}
+
+_public_ const char *sd_bus_message_get_sender(sd_bus_message *m) {
+ assert_return(m, NULL);
+
+ return m->sender;
+}
+
+_public_ const sd_bus_error *sd_bus_message_get_error(sd_bus_message *m) {
+ assert_return(m, NULL);
+
+ if (!sd_bus_error_is_set(&m->error))
+ return NULL;
+
+ return &m->error;
+}
+
+_public_ int sd_bus_message_get_monotonic_usec(sd_bus_message *m, uint64_t *usec) {
+ assert_return(m, -EINVAL);
+ assert_return(usec, -EINVAL);
+
+ if (m->monotonic <= 0)
+ return -ENODATA;
+
+ *usec = m->monotonic;
+ return 0;
+}
+
+_public_ int sd_bus_message_get_realtime_usec(sd_bus_message *m, uint64_t *usec) {
+ assert_return(m, -EINVAL);
+ assert_return(usec, -EINVAL);
+
+ if (m->realtime <= 0)
+ return -ENODATA;
+
+ *usec = m->realtime;
+ return 0;
+}
+
+_public_ int sd_bus_message_get_seqnum(sd_bus_message *m, uint64_t *seqnum) {
+ assert_return(m, -EINVAL);
+ assert_return(seqnum, -EINVAL);
+
+ if (m->seqnum <= 0)
+ return -ENODATA;
+
+ *seqnum = m->seqnum;
+ return 0;
+}
+
+_public_ sd_bus_creds *sd_bus_message_get_creds(sd_bus_message *m) {
+ assert_return(m, NULL);
+
+ if (m->creds.mask == 0)
+ return NULL;
+
+ return &m->creds;
+}
+
+_public_ int sd_bus_message_is_signal(
+ sd_bus_message *m,
+ const char *interface,
+ const char *member) {
+
+ assert_return(m, -EINVAL);
+
+ if (m->header->type != SD_BUS_MESSAGE_SIGNAL)
+ return 0;
+
+ if (interface && !streq_ptr(m->interface, interface))
+ return 0;
+
+ if (member && !streq_ptr(m->member, member))
+ return 0;
+
+ return 1;
+}
+
+_public_ int sd_bus_message_is_method_call(
+ sd_bus_message *m,
+ const char *interface,
+ const char *member) {
+
+ assert_return(m, -EINVAL);
+
+ if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL)
+ return 0;
+
+ if (interface && !streq_ptr(m->interface, interface))
+ return 0;
+
+ if (member && !streq_ptr(m->member, member))
+ return 0;
+
+ return 1;
+}
+
+_public_ int sd_bus_message_is_method_error(sd_bus_message *m, const char *name) {
+ assert_return(m, -EINVAL);
+
+ if (m->header->type != SD_BUS_MESSAGE_METHOD_ERROR)
+ return 0;
+
+ if (name && !streq_ptr(m->error.name, name))
+ return 0;
+
+ return 1;
+}
+
+_public_ int sd_bus_message_set_expect_reply(sd_bus_message *m, int b) {
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EPERM);
+
+ SET_FLAG(m->header->flags, BUS_MESSAGE_NO_REPLY_EXPECTED, !b);
+
+ return 0;
+}
+
+_public_ int sd_bus_message_set_auto_start(sd_bus_message *m, int b) {
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+
+ SET_FLAG(m->header->flags, BUS_MESSAGE_NO_AUTO_START, !b);
+
+ return 0;
+}
+
+_public_ int sd_bus_message_set_allow_interactive_authorization(sd_bus_message *m, int b) {
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+
+ SET_FLAG(m->header->flags, BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION, b);
+
+ return 0;
+}
+
+static struct bus_body_part *message_append_part(sd_bus_message *m) {
+ struct bus_body_part *part;
+
+ assert(m);
+
+ if (m->poisoned)
+ return NULL;
+
+ if (m->n_body_parts <= 0) {
+ part = &m->body;
+ zero(*part);
+ } else {
+ assert(m->body_end);
+
+ part = new0(struct bus_body_part, 1);
+ if (!part) {
+ m->poisoned = true;
+ return NULL;
+ }
+
+ m->body_end->next = part;
+ }
+
+ part->memfd = -1;
+ m->body_end = part;
+ m->n_body_parts++;
+
+ return part;
+}
+
+static void part_zero(struct bus_body_part *part, size_t sz) {
+ assert(part);
+ assert(sz > 0);
+ assert(sz < 8);
+
+ /* All other fields can be left in their defaults */
+ assert(!part->data);
+ assert(part->memfd < 0);
+
+ part->size = sz;
+ part->is_zero = true;
+ part->sealed = true;
+}
+
+static int part_make_space(
+ struct sd_bus_message *m,
+ struct bus_body_part *part,
+ size_t sz,
+ void **q) {
+
+ void *n;
+
+ assert(m);
+ assert(part);
+ assert(!part->sealed);
+
+ if (m->poisoned)
+ return -ENOMEM;
+
+ if (part->allocated == 0 || sz > part->allocated) {
+ size_t new_allocated;
+
+ new_allocated = sz > 0 ? 2 * sz : 64;
+ n = realloc(part->data, new_allocated);
+ if (!n) {
+ m->poisoned = true;
+ return -ENOMEM;
+ }
+
+ part->data = n;
+ part->allocated = new_allocated;
+ part->free_this = true;
+ }
+
+ if (q)
+ *q = part->data ? (uint8_t*) part->data + part->size : NULL;
+
+ part->size = sz;
+ return 0;
+}
+
+static void message_extend_containers(sd_bus_message *m, size_t expand) {
+ assert(m);
+
+ if (expand <= 0)
+ return;
+
+ if (m->n_containers <= 0)
+ return;
+
+ /* Update counters */
+ for (struct bus_container *c = m->containers; c < m->containers + m->n_containers; c++)
+ if (c->array_size)
+ *c->array_size += expand;
+}
+
+static void *message_extend_body(
+ sd_bus_message *m,
+ size_t align,
+ size_t sz) {
+
+ size_t start_body, end_body, padding, added;
+ void *p;
+ int r;
+
+ assert(m);
+ assert(align > 0);
+ assert(!m->sealed);
+
+ if (m->poisoned)
+ return NULL;
+
+ start_body = ALIGN_TO(m->body_size, align);
+ end_body = start_body + sz;
+
+ padding = start_body - m->body_size;
+ added = padding + sz;
+
+ /* Check for 32bit overflows */
+ if (end_body < start_body || end_body > UINT32_MAX) {
+ m->poisoned = true;
+ return NULL;
+ }
+
+ if (added > 0) {
+ struct bus_body_part *part = NULL;
+ bool add_new_part;
+
+ add_new_part =
+ m->n_body_parts <= 0 ||
+ m->body_end->sealed ||
+ (padding != ALIGN_TO(m->body_end->size, align) - m->body_end->size);
+ /* If this must be an inlined extension, let's create a new part if
+ * the previous part is large enough to be inlined. */
+
+ if (add_new_part) {
+ if (padding > 0) {
+ part = message_append_part(m);
+ if (!part)
+ return NULL;
+
+ part_zero(part, padding);
+ }
+
+ part = message_append_part(m);
+ if (!part)
+ return NULL;
+
+ r = part_make_space(m, part, sz, &p);
+ if (r < 0)
+ return NULL;
+ } else {
+ void *op;
+ size_t os, start_part, end_part;
+
+ part = m->body_end;
+ op = part->data;
+ os = part->size;
+
+ start_part = ALIGN_TO(part->size, align);
+ end_part = start_part + sz;
+
+ r = part_make_space(m, part, end_part, &p);
+ if (r < 0)
+ return NULL;
+
+ if (padding > 0) {
+ memzero(p, padding);
+ p = (uint8_t*) p + padding;
+ }
+
+ /* Readjust pointers */
+ if (m->n_containers > 0)
+ for (struct bus_container *c = m->containers; c < m->containers + m->n_containers; c++)
+ c->array_size = adjust_pointer(c->array_size, op, os, part->data);
+
+ m->error.message = (const char*) adjust_pointer(m->error.message, op, os, part->data);
+ }
+ } else
+ /* Return something that is not NULL and is aligned */
+ p = (uint8_t*) align;
+
+ m->body_size = end_body;
+ message_extend_containers(m, added);
+
+ return p;
+}
+
+static int message_push_fd(sd_bus_message *m, int fd) {
+ int *f, copy;
+
+ assert(m);
+
+ if (fd < 0)
+ return -EINVAL;
+
+ if (!m->allow_fds)
+ return -EOPNOTSUPP;
+
+ copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (copy < 0)
+ return -errno;
+
+ f = reallocarray(m->fds, m->n_fds + 1, sizeof(int));
+ if (!f) {
+ m->poisoned = true;
+ safe_close(copy);
+ return -ENOMEM;
+ }
+
+ m->fds = f;
+ m->fds[m->n_fds] = copy;
+ m->free_fds = true;
+
+ return copy;
+}
+
+int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored) {
+ _cleanup_close_ int fd = -1;
+ struct bus_container *c;
+ ssize_t align, sz;
+ uint32_t u32;
+ void *a;
+
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(bus_type_is_basic(type), -EINVAL);
+ assert_return(!m->poisoned, -ESTALE);
+
+ c = message_get_last_container(m);
+
+ if (c->signature && c->signature[c->index]) {
+ /* Container signature is already set */
+
+ if (c->signature[c->index] != type)
+ return -ENXIO;
+ } else {
+ char *e;
+
+ /* Maybe we can append to the signature? But only if this is the top-level container */
+ if (c->enclosing != 0)
+ return -ENXIO;
+
+ e = strextend(&c->signature, CHAR_TO_STR(type));
+ if (!e) {
+ m->poisoned = true;
+ return -ENOMEM;
+ }
+ }
+
+ switch (type) {
+
+ case SD_BUS_TYPE_STRING:
+ /* To make things easy we'll serialize a NULL string
+ * into the empty string */
+ p = strempty(p);
+
+ if (!utf8_is_valid(p))
+ return -EINVAL;
+
+ align = 4;
+ sz = 4 + strlen(p) + 1;
+ break;
+
+ case SD_BUS_TYPE_OBJECT_PATH:
+
+ if (!p)
+ return -EINVAL;
+
+ if (!object_path_is_valid(p))
+ return -EINVAL;
+
+ align = 4;
+ sz = 4 + strlen(p) + 1;
+ break;
+
+ case SD_BUS_TYPE_SIGNATURE:
+
+ p = strempty(p);
+
+ if (!signature_is_valid(p, /* allow_dict_entry = */ true))
+ return -EINVAL;
+
+ align = 1;
+ sz = 1 + strlen(p) + 1;
+ break;
+
+ case SD_BUS_TYPE_BOOLEAN:
+
+ u32 = p && *(int*) p;
+ p = &u32;
+
+ align = sz = 4;
+ break;
+
+ case SD_BUS_TYPE_UNIX_FD:
+
+ if (!p)
+ return -EINVAL;
+
+ fd = message_push_fd(m, *(int*) p);
+ if (fd < 0)
+ return fd;
+
+ u32 = m->n_fds;
+ p = &u32;
+
+ align = sz = 4;
+ break;
+
+ default:
+ align = bus_type_get_alignment(type);
+ sz = bus_type_get_size(type);
+ break;
+ }
+
+ assert(align > 0);
+ assert(sz > 0);
+
+ a = message_extend_body(m, align, sz);
+ if (!a)
+ return -ENOMEM;
+
+ if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH)) {
+ *(uint32_t*) a = sz - 5;
+ memcpy((uint8_t*) a + 4, p, sz - 4);
+
+ if (stored)
+ *stored = (const uint8_t*) a + 4;
+
+ } else if (type == SD_BUS_TYPE_SIGNATURE) {
+ *(uint8_t*) a = sz - 2;
+ memcpy((uint8_t*) a + 1, p, sz - 1);
+
+ if (stored)
+ *stored = (const uint8_t*) a + 1;
+ } else {
+ memcpy(a, p, sz);
+
+ if (stored)
+ *stored = a;
+ }
+
+ if (type == SD_BUS_TYPE_UNIX_FD)
+ m->n_fds++;
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ c->index++;
+
+ fd = -1;
+ return 0;
+}
+
+_public_ int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p) {
+ return message_append_basic(m, type, p, NULL);
+}
+
+_public_ int sd_bus_message_append_string_space(
+ sd_bus_message *m,
+ size_t size,
+ char **s) {
+
+ struct bus_container *c;
+ void *a;
+
+ assert_return(m, -EINVAL);
+ assert_return(s, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(!m->poisoned, -ESTALE);
+
+ c = message_get_last_container(m);
+
+ if (c->signature && c->signature[c->index]) {
+ /* Container signature is already set */
+
+ if (c->signature[c->index] != SD_BUS_TYPE_STRING)
+ return -ENXIO;
+ } else {
+ char *e;
+
+ /* Maybe we can append to the signature? But only if this is the top-level container */
+ if (c->enclosing != 0)
+ return -ENXIO;
+
+ e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING));
+ if (!e) {
+ m->poisoned = true;
+ return -ENOMEM;
+ }
+ }
+
+ a = message_extend_body(m, 4, 4 + size + 1);
+ if (!a)
+ return -ENOMEM;
+
+ *(uint32_t*) a = size;
+ *s = (char*) a + 4;
+
+ (*s)[size] = 0;
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ c->index++;
+
+ return 0;
+}
+
+_public_ int sd_bus_message_append_string_iovec(
+ sd_bus_message *m,
+ const struct iovec *iov,
+ unsigned n /* should be size_t, but is API now… 😞 */) {
+
+ size_t size;
+ unsigned i;
+ char *p;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(iov || n == 0, -EINVAL);
+ assert_return(!m->poisoned, -ESTALE);
+
+ size = IOVEC_TOTAL_SIZE(iov, n);
+
+ r = sd_bus_message_append_string_space(m, size, &p);
+ if (r < 0)
+ return r;
+
+ for (i = 0; i < n; i++) {
+
+ if (iov[i].iov_base)
+ memcpy(p, iov[i].iov_base, iov[i].iov_len);
+ else
+ memset(p, ' ', iov[i].iov_len);
+
+ p += iov[i].iov_len;
+ }
+
+ return 0;
+}
+
+static int bus_message_open_array(
+ sd_bus_message *m,
+ struct bus_container *c,
+ const char *contents,
+ uint32_t **array_size,
+ size_t *begin) {
+
+ unsigned nindex;
+ int alignment;
+ void *a, *op;
+ size_t os;
+ struct bus_body_part *o;
+
+ assert(m);
+ assert(c);
+ assert(contents);
+ assert(array_size);
+ assert(begin);
+
+ if (!signature_is_single(contents, true))
+ return -EINVAL;
+
+ if (c->signature && c->signature[c->index]) {
+
+ /* Verify the existing signature */
+
+ if (c->signature[c->index] != SD_BUS_TYPE_ARRAY)
+ return -ENXIO;
+
+ if (!startswith(c->signature + c->index + 1, contents))
+ return -ENXIO;
+
+ nindex = c->index + 1 + strlen(contents);
+ } else {
+ char *e;
+
+ if (c->enclosing != 0)
+ return -ENXIO;
+
+ /* Extend the existing signature */
+
+ e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_ARRAY), contents);
+ if (!e) {
+ m->poisoned = true;
+ return -ENOMEM;
+ }
+
+ nindex = e - c->signature;
+ }
+
+ alignment = bus_type_get_alignment(contents[0]);
+ if (alignment < 0)
+ return alignment;
+
+ a = message_extend_body(m, 4, 4);
+ if (!a)
+ return -ENOMEM;
+
+ o = m->body_end;
+ op = m->body_end->data;
+ os = m->body_end->size;
+
+ /* Add alignment between size and first element */
+ if (!message_extend_body(m, alignment, 0))
+ return -ENOMEM;
+
+ /* location of array size might have changed so let's readjust a */
+ if (o == m->body_end)
+ a = adjust_pointer(a, op, os, m->body_end->data);
+
+ *(uint32_t*) a = 0;
+ *array_size = a;
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ c->index = nindex;
+
+ return 0;
+}
+
+static int bus_message_open_variant(
+ sd_bus_message *m,
+ struct bus_container *c,
+ const char *contents) {
+
+ size_t l;
+ void *a;
+
+ assert(m);
+ assert(c);
+ assert(contents);
+
+ if (!signature_is_single(contents, false))
+ return -EINVAL;
+
+ if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN)
+ return -EINVAL;
+
+ if (c->signature && c->signature[c->index]) {
+
+ if (c->signature[c->index] != SD_BUS_TYPE_VARIANT)
+ return -ENXIO;
+
+ } else {
+ char *e;
+
+ if (c->enclosing != 0)
+ return -ENXIO;
+
+ e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_VARIANT));
+ if (!e) {
+ m->poisoned = true;
+ return -ENOMEM;
+ }
+ }
+
+ l = strlen(contents);
+ a = message_extend_body(m, 1, 1 + l + 1);
+ if (!a)
+ return -ENOMEM;
+
+ *(uint8_t*) a = l;
+ memcpy((uint8_t*) a + 1, contents, l + 1);
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ c->index++;
+
+ return 0;
+}
+
+static int bus_message_open_struct(
+ sd_bus_message *m,
+ struct bus_container *c,
+ const char *contents,
+ size_t *begin) {
+
+ size_t nindex;
+
+ assert(m);
+ assert(c);
+ assert(contents);
+ assert(begin);
+
+ if (!signature_is_valid(contents, false))
+ return -EINVAL;
+
+ if (c->signature && c->signature[c->index]) {
+ size_t l;
+
+ l = strlen(contents);
+
+ if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN ||
+ !startswith(c->signature + c->index + 1, contents) ||
+ c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END)
+ return -ENXIO;
+
+ nindex = c->index + 1 + l + 1;
+ } else {
+ char *e;
+
+ if (c->enclosing != 0)
+ return -ENXIO;
+
+ e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_BEGIN), contents, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_END));
+ if (!e) {
+ m->poisoned = true;
+ return -ENOMEM;
+ }
+
+ nindex = e - c->signature;
+ }
+
+ /* Align contents to 8 byte boundary */
+ if (!message_extend_body(m, 8, 0))
+ return -ENOMEM;
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ c->index = nindex;
+
+ return 0;
+}
+
+static int bus_message_open_dict_entry(
+ sd_bus_message *m,
+ struct bus_container *c,
+ const char *contents,
+ size_t *begin) {
+
+ assert(m);
+ assert(c);
+ assert(contents);
+ assert(begin);
+
+ if (!signature_is_pair(contents))
+ return -EINVAL;
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ return -ENXIO;
+
+ if (c->signature && c->signature[c->index]) {
+ size_t l;
+
+ l = strlen(contents);
+
+ if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN ||
+ !startswith(c->signature + c->index + 1, contents) ||
+ c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END)
+ return -ENXIO;
+ } else
+ return -ENXIO;
+
+ /* Align contents to 8 byte boundary */
+ if (!message_extend_body(m, 8, 0))
+ return -ENOMEM;
+
+ return 0;
+}
+
+_public_ int sd_bus_message_open_container(
+ sd_bus_message *m,
+ char type,
+ const char *contents) {
+
+ struct bus_container *c;
+ uint32_t *array_size = NULL;
+ _cleanup_free_ char *signature = NULL;
+ size_t before, begin = 0;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(contents, -EINVAL);
+ assert_return(!m->poisoned, -ESTALE);
+
+ /* Make sure we have space for one more container */
+ if (!GREEDY_REALLOC(m->containers, m->n_containers + 1)) {
+ m->poisoned = true;
+ return -ENOMEM;
+ }
+
+ c = message_get_last_container(m);
+
+ signature = strdup(contents);
+ if (!signature) {
+ m->poisoned = true;
+ return -ENOMEM;
+ }
+
+ /* Save old index in the parent container, in case we have to
+ * abort this container */
+ c->saved_index = c->index;
+ before = m->body_size;
+
+ if (type == SD_BUS_TYPE_ARRAY)
+ r = bus_message_open_array(m, c, contents, &array_size, &begin);
+ else if (type == SD_BUS_TYPE_VARIANT)
+ r = bus_message_open_variant(m, c, contents);
+ else if (type == SD_BUS_TYPE_STRUCT)
+ r = bus_message_open_struct(m, c, contents, &begin);
+ else if (type == SD_BUS_TYPE_DICT_ENTRY)
+ r = bus_message_open_dict_entry(m, c, contents, &begin);
+ else
+ r = -EINVAL;
+ if (r < 0)
+ return r;
+
+ /* OK, let's fill it in */
+ m->containers[m->n_containers++] = (struct bus_container) {
+ .enclosing = type,
+ .signature = TAKE_PTR(signature),
+ .array_size = array_size,
+ .before = before,
+ .begin = begin,
+ };
+
+ return 0;
+}
+
+_public_ int sd_bus_message_close_container(sd_bus_message *m) {
+ struct bus_container *c;
+
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(m->n_containers > 0, -EINVAL);
+ assert_return(!m->poisoned, -ESTALE);
+
+ c = message_get_last_container(m);
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ if (c->signature && c->signature[c->index] != 0)
+ return -EINVAL;
+
+ m->n_containers--;
+
+ free(c->signature);
+
+ return 0;
+}
+
+typedef struct {
+ const char *types;
+ unsigned n_struct;
+ unsigned n_array;
+} TypeStack;
+
+static int type_stack_push(TypeStack *stack, unsigned max, unsigned *i, const char *types, unsigned n_struct, unsigned n_array) {
+ assert(stack);
+ assert(max > 0);
+
+ if (*i >= max)
+ return -EINVAL;
+
+ stack[*i].types = types;
+ stack[*i].n_struct = n_struct;
+ stack[*i].n_array = n_array;
+ (*i)++;
+
+ return 0;
+}
+
+static int type_stack_pop(TypeStack *stack, unsigned max, unsigned *i, const char **types, unsigned *n_struct, unsigned *n_array) {
+ assert(stack);
+ assert(max > 0);
+ assert(types);
+ assert(n_struct);
+ assert(n_array);
+
+ if (*i <= 0)
+ return 0;
+
+ (*i)--;
+ *types = stack[*i].types;
+ *n_struct = stack[*i].n_struct;
+ *n_array = stack[*i].n_array;
+
+ return 1;
+}
+
+_public_ int sd_bus_message_appendv(
+ sd_bus_message *m,
+ const char *types,
+ va_list ap) {
+
+ unsigned n_array, n_struct;
+ TypeStack stack[BUS_CONTAINER_DEPTH];
+ unsigned stack_ptr = 0;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(types, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(!m->poisoned, -ESTALE);
+
+ n_array = UINT_MAX;
+ n_struct = strlen(types);
+
+ for (;;) {
+ const char *t;
+
+ if (n_array == 0 || (n_array == UINT_MAX && n_struct == 0)) {
+ r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ continue;
+ }
+
+ t = types;
+ if (n_array != UINT_MAX)
+ n_array--;
+ else {
+ types++;
+ n_struct--;
+ }
+
+ switch (*t) {
+
+ case SD_BUS_TYPE_BYTE: {
+ uint8_t x;
+
+ x = (uint8_t) va_arg(ap, int);
+ r = sd_bus_message_append_basic(m, *t, &x);
+ break;
+ }
+
+ case SD_BUS_TYPE_BOOLEAN:
+ case SD_BUS_TYPE_INT32:
+ case SD_BUS_TYPE_UINT32:
+ case SD_BUS_TYPE_UNIX_FD: {
+ uint32_t x;
+
+ /* We assume a boolean is the same as int32_t */
+ assert_cc(sizeof(int32_t) == sizeof(int));
+
+ x = va_arg(ap, uint32_t);
+ r = sd_bus_message_append_basic(m, *t, &x);
+ break;
+ }
+
+ case SD_BUS_TYPE_INT16:
+ case SD_BUS_TYPE_UINT16: {
+ uint16_t x;
+
+ x = (uint16_t) va_arg(ap, int);
+ r = sd_bus_message_append_basic(m, *t, &x);
+ break;
+ }
+
+ case SD_BUS_TYPE_INT64:
+ case SD_BUS_TYPE_UINT64: {
+ uint64_t x;
+
+ x = va_arg(ap, uint64_t);
+ r = sd_bus_message_append_basic(m, *t, &x);
+ break;
+ }
+
+ case SD_BUS_TYPE_DOUBLE: {
+ double x;
+
+ x = va_arg(ap, double);
+ r = sd_bus_message_append_basic(m, *t, &x);
+ break;
+ }
+
+ case SD_BUS_TYPE_STRING:
+ case SD_BUS_TYPE_OBJECT_PATH:
+ case SD_BUS_TYPE_SIGNATURE: {
+ const char *x;
+
+ x = va_arg(ap, const char*);
+ r = sd_bus_message_append_basic(m, *t, x);
+ break;
+ }
+
+ case SD_BUS_TYPE_ARRAY: {
+ size_t k;
+
+ r = signature_element_length(t + 1, &k);
+ if (r < 0)
+ return r;
+
+ {
+ char s[k + 1];
+ memcpy(s, t + 1, k);
+ s[k] = 0;
+
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s);
+ if (r < 0)
+ return r;
+ }
+
+ if (n_array == UINT_MAX) {
+ types += k;
+ n_struct -= k;
+ }
+
+ r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array);
+ if (r < 0)
+ return r;
+
+ types = t + 1;
+ n_struct = k;
+ n_array = va_arg(ap, unsigned);
+
+ break;
+ }
+
+ case SD_BUS_TYPE_VARIANT: {
+ const char *s;
+
+ s = va_arg(ap, const char*);
+ if (!s)
+ return -EINVAL;
+
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, s);
+ if (r < 0)
+ return r;
+
+ r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array);
+ if (r < 0)
+ return r;
+
+ types = s;
+ n_struct = strlen(s);
+ n_array = UINT_MAX;
+
+ break;
+ }
+
+ case SD_BUS_TYPE_STRUCT_BEGIN:
+ case SD_BUS_TYPE_DICT_ENTRY_BEGIN: {
+ size_t k;
+
+ r = signature_element_length(t, &k);
+ if (r < 0)
+ return r;
+ if (k < 2)
+ return -ERANGE;
+
+ {
+ char s[k - 1];
+
+ memcpy(s, t + 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 r;
+ }
+
+ if (n_array == UINT_MAX) {
+ types += k - 1;
+ n_struct -= k - 1;
+ }
+
+ r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array);
+ if (r < 0)
+ return r;
+
+ types = t + 1;
+ n_struct = k - 2;
+ n_array = UINT_MAX;
+
+ break;
+ }
+
+ default:
+ r = -EINVAL;
+ }
+
+ if (r < 0)
+ return r;
+ }
+
+ return 1;
+}
+
+_public_ int sd_bus_message_append(sd_bus_message *m, const char *types, ...) {
+ va_list ap;
+ int r;
+
+ va_start(ap, types);
+ r = sd_bus_message_appendv(m, types, ap);
+ va_end(ap);
+
+ return r;
+}
+
+_public_ int sd_bus_message_append_array_space(
+ sd_bus_message *m,
+ char type,
+ size_t size,
+ void **ptr) {
+
+ ssize_t align, sz;
+ void *a;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(bus_type_is_trivial(type) && type != SD_BUS_TYPE_BOOLEAN, -EINVAL);
+ assert_return(ptr || size == 0, -EINVAL);
+ assert_return(!m->poisoned, -ESTALE);
+
+ align = bus_type_get_alignment(type);
+ sz = bus_type_get_size(type);
+
+ assert_se(align > 0);
+ assert_se(sz > 0);
+
+ if (size % sz != 0)
+ return -EINVAL;
+
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type));
+ if (r < 0)
+ return r;
+
+ a = message_extend_body(m, align, size);
+ if (!a)
+ return -ENOMEM;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ *ptr = a;
+ return 0;
+}
+
+_public_ int sd_bus_message_append_array(
+ sd_bus_message *m,
+ char type,
+ const void *ptr,
+ size_t size) {
+ int r;
+ void *p;
+
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(bus_type_is_trivial(type), -EINVAL);
+ assert_return(ptr || size == 0, -EINVAL);
+ assert_return(!m->poisoned, -ESTALE);
+
+ r = sd_bus_message_append_array_space(m, type, size, &p);
+ if (r < 0)
+ return r;
+
+ memcpy_safe(p, ptr, size);
+
+ return 0;
+}
+
+_public_ int sd_bus_message_append_array_iovec(
+ sd_bus_message *m,
+ char type,
+ const struct iovec *iov,
+ unsigned n /* should be size_t, but is API now… 😞 */) {
+
+ size_t size;
+ unsigned i;
+ void *p;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(bus_type_is_trivial(type), -EINVAL);
+ assert_return(iov || n == 0, -EINVAL);
+ assert_return(!m->poisoned, -ESTALE);
+
+ size = IOVEC_TOTAL_SIZE(iov, n);
+
+ r = sd_bus_message_append_array_space(m, type, size, &p);
+ if (r < 0)
+ return r;
+
+ for (i = 0; i < n; i++) {
+
+ if (iov[i].iov_base)
+ memcpy(p, iov[i].iov_base, iov[i].iov_len);
+ else
+ memzero(p, iov[i].iov_len);
+
+ p = (uint8_t*) p + iov[i].iov_len;
+ }
+
+ return 0;
+}
+
+_public_ int sd_bus_message_append_array_memfd(
+ sd_bus_message *m,
+ char type,
+ int memfd,
+ uint64_t offset,
+ uint64_t size) {
+
+ _cleanup_close_ int copy_fd = -1;
+ struct bus_body_part *part;
+ ssize_t align, sz;
+ uint64_t real_size;
+ void *a;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(memfd >= 0, -EBADF);
+ assert_return(bus_type_is_trivial(type), -EINVAL);
+ assert_return(size > 0, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(!m->poisoned, -ESTALE);
+
+ r = memfd_set_sealed(memfd);
+ if (r < 0)
+ return r;
+
+ copy_fd = fcntl(memfd, F_DUPFD_CLOEXEC, 3);
+ if (copy_fd < 0)
+ return copy_fd;
+
+ r = memfd_get_size(memfd, &real_size);
+ if (r < 0)
+ return r;
+
+ if (offset == 0 && size == UINT64_MAX)
+ size = real_size;
+ else if (offset + size > real_size)
+ return -EMSGSIZE;
+
+ align = bus_type_get_alignment(type);
+ sz = bus_type_get_size(type);
+
+ assert_se(align > 0);
+ assert_se(sz > 0);
+
+ if (offset % align != 0)
+ return -EINVAL;
+
+ if (size % sz != 0)
+ return -EINVAL;
+
+ if (size > (uint64_t) UINT32_MAX)
+ return -EINVAL;
+
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type));
+ if (r < 0)
+ return r;
+
+ a = message_extend_body(m, align, 0);
+ if (!a)
+ return -ENOMEM;
+
+ part = message_append_part(m);
+ if (!part)
+ return -ENOMEM;
+
+ part->memfd = copy_fd;
+ part->memfd_offset = offset;
+ part->sealed = true;
+ part->size = size;
+ copy_fd = -1;
+
+ m->body_size += size;
+ message_extend_containers(m, size);
+
+ return sd_bus_message_close_container(m);
+}
+
+_public_ int sd_bus_message_append_string_memfd(
+ sd_bus_message *m,
+ int memfd,
+ uint64_t offset,
+ uint64_t size) {
+
+ _cleanup_close_ int copy_fd = -1;
+ struct bus_body_part *part;
+ struct bus_container *c;
+ uint64_t real_size;
+ void *a;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(memfd >= 0, -EBADF);
+ assert_return(size > 0, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(!m->poisoned, -ESTALE);
+
+ r = memfd_set_sealed(memfd);
+ if (r < 0)
+ return r;
+
+ copy_fd = fcntl(memfd, FD_CLOEXEC, 3);
+ if (copy_fd < 0)
+ return copy_fd;
+
+ r = memfd_get_size(memfd, &real_size);
+ if (r < 0)
+ return r;
+
+ if (offset == 0 && size == UINT64_MAX)
+ size = real_size;
+ else if (offset + size > real_size)
+ return -EMSGSIZE;
+
+ /* We require this to be NUL terminated */
+ if (size == 0)
+ return -EINVAL;
+
+ if (size > (uint64_t) UINT32_MAX)
+ return -EINVAL;
+
+ c = message_get_last_container(m);
+ if (c->signature && c->signature[c->index]) {
+ /* Container signature is already set */
+
+ if (c->signature[c->index] != SD_BUS_TYPE_STRING)
+ return -ENXIO;
+ } else {
+ char *e;
+
+ /* Maybe we can append to the signature? But only if this is the top-level container */
+ if (c->enclosing != 0)
+ return -ENXIO;
+
+ e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING));
+ if (!e) {
+ m->poisoned = true;
+ return -ENOMEM;
+ }
+ }
+
+ a = message_extend_body(m, 4, 4);
+ if (!a)
+ return -ENOMEM;
+
+ *(uint32_t*) a = size - 1;
+
+ part = message_append_part(m);
+ if (!part)
+ return -ENOMEM;
+
+ part->memfd = copy_fd;
+ part->memfd_offset = offset;
+ part->sealed = true;
+ part->size = size;
+ copy_fd = -1;
+
+ m->body_size += size;
+ message_extend_containers(m, size);
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ c->index++;
+
+ return 0;
+}
+
+_public_ int sd_bus_message_append_strv(sd_bus_message *m, char **l) {
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(!m->poisoned, -ESTALE);
+
+ r = sd_bus_message_open_container(m, 'a', "s");
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, l) {
+ r = sd_bus_message_append_basic(m, 's', *i);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(m);
+}
+
+static int bus_message_close_header(sd_bus_message *m) {
+ assert(m);
+
+ /* The actual user data is finished now, we just complete the variant and struct now. Remember
+ * this position, so that during parsing we know where to put the outer container end. */
+ m->user_body_size = m->body_size;
+
+ m->header->fields_size = m->fields_size;
+ m->header->body_size = m->body_size;
+
+ return 0;
+}
+
+_public_ int sd_bus_message_seal(sd_bus_message *m, uint64_t cookie, uint64_t timeout_usec) {
+ struct bus_body_part *part;
+ size_t a;
+ unsigned i;
+ int r;
+
+ assert_return(m, -EINVAL);
+
+ if (m->sealed)
+ return -EPERM;
+
+ if (m->n_containers > 0)
+ return -EBADMSG;
+
+ if (m->poisoned)
+ return -ESTALE;
+
+ if (cookie > UINT32_MAX)
+ return -EOPNOTSUPP;
+
+ /* In vtables the return signature of method calls is listed,
+ * let's check if they match if this is a response */
+ if (m->header->type == SD_BUS_MESSAGE_METHOD_RETURN &&
+ m->enforced_reply_signature &&
+ !streq(strempty(m->root_container.signature), m->enforced_reply_signature))
+ return -ENOMSG;
+
+ /* If there's a non-trivial signature set, then add it in here */
+ if (!isempty(m->root_container.signature)) {
+ r = message_append_field_signature(m, BUS_MESSAGE_HEADER_SIGNATURE, m->root_container.signature, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ if (m->n_fds > 0) {
+ r = message_append_field_uint32(m, BUS_MESSAGE_HEADER_UNIX_FDS, m->n_fds);
+ if (r < 0)
+ return r;
+ }
+
+ r = bus_message_close_header(m);
+ if (r < 0)
+ return r;
+
+ m->header->serial = (uint32_t) cookie;
+
+ m->timeout = m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED ? 0 : timeout_usec;
+
+ /* Add padding at the end of the fields part, since we know
+ * the body needs to start at an 8 byte alignment. We made
+ * sure we allocated enough space for this, so all we need to
+ * do here is to zero it out. */
+ a = ALIGN8(m->fields_size) - m->fields_size;
+ if (a > 0)
+ memzero((uint8_t*) BUS_MESSAGE_FIELDS(m) + m->fields_size, a);
+
+ /* If this is something we can send as memfd, then let's seal
+ the memfd now. Note that we can send memfds as payload only
+ for directed messages, and not for broadcasts. */
+ if (m->destination && m->bus->use_memfd) {
+ MESSAGE_FOREACH_PART(part, i, m)
+ if (part->memfd >= 0 &&
+ !part->sealed &&
+ (part->size > MEMFD_MIN_SIZE || m->bus->use_memfd < 0) &&
+ part != m->body_end) { /* The last part may never be sent as memfd */
+ uint64_t sz;
+
+ /* Try to seal it if that makes
+ * sense. First, unmap our own map to
+ * make sure we don't keep it busy. */
+ bus_body_part_unmap(part);
+
+ /* Then, sync up real memfd size */
+ sz = part->size;
+ r = memfd_set_size(part->memfd, sz);
+ if (r < 0)
+ return r;
+
+ /* Finally, try to seal */
+ if (memfd_set_sealed(part->memfd) >= 0)
+ part->sealed = true;
+ }
+ }
+
+ m->root_container.end = m->user_body_size;
+ m->root_container.index = 0;
+
+ m->sealed = true;
+
+ return 0;
+}
+
+int bus_body_part_map(struct bus_body_part *part) {
+ void *p;
+ size_t psz, shift;
+
+ assert_se(part);
+
+ if (part->data)
+ return 0;
+
+ if (part->size <= 0)
+ return 0;
+
+ /* For smaller zero parts (as used for padding) we don't need to map anything... */
+ if (part->memfd < 0 && part->is_zero && part->size < 8) {
+ static const uint8_t zeroes[7] = { };
+ part->data = (void*) zeroes;
+ return 0;
+ }
+
+ shift = PAGE_OFFSET(part->memfd_offset);
+ psz = PAGE_ALIGN(part->size + shift);
+
+ if (part->memfd >= 0)
+ p = mmap(NULL, psz, PROT_READ, MAP_PRIVATE, part->memfd, part->memfd_offset - shift);
+ else if (part->is_zero)
+ p = mmap(NULL, psz, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+ else
+ return -EINVAL;
+
+ if (p == MAP_FAILED)
+ return -errno;
+
+ part->mapped = psz;
+ part->mmap_begin = p;
+ part->data = (uint8_t*) p + shift;
+ part->munmap_this = true;
+
+ return 0;
+}
+
+void bus_body_part_unmap(struct bus_body_part *part) {
+
+ assert_se(part);
+
+ if (part->memfd < 0)
+ return;
+
+ if (!part->mmap_begin)
+ return;
+
+ if (!part->munmap_this)
+ return;
+
+ assert_se(munmap(part->mmap_begin, part->mapped) == 0);
+
+ part->mmap_begin = NULL;
+ part->data = NULL;
+ part->mapped = 0;
+ part->munmap_this = false;
+
+ return;
+}
+
+static bool message_end_of_signature(sd_bus_message *m) {
+ struct bus_container *c;
+
+ assert(m);
+
+ c = message_get_last_container(m);
+ return !c->signature || c->signature[c->index] == 0;
+}
+
+static bool message_end_of_array(sd_bus_message *m, size_t index) {
+ struct bus_container *c;
+
+ assert(m);
+
+ c = message_get_last_container(m);
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ return false;
+
+ assert(c->array_size);
+ return index >= c->begin + BUS_MESSAGE_BSWAP32(m, *c->array_size);
+}
+
+_public_ int sd_bus_message_at_end(sd_bus_message *m, int complete) {
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EPERM);
+
+ if (complete && m->n_containers > 0)
+ return false;
+
+ if (message_end_of_signature(m))
+ return true;
+
+ if (message_end_of_array(m, m->rindex))
+ return true;
+
+ return false;
+}
+
+static struct bus_body_part* find_part(sd_bus_message *m, size_t index, size_t sz, void **p) {
+ struct bus_body_part *part;
+ size_t begin;
+ int r;
+
+ assert(m);
+
+ if (m->cached_rindex_part && index >= m->cached_rindex_part_begin) {
+ part = m->cached_rindex_part;
+ begin = m->cached_rindex_part_begin;
+ } else {
+ part = &m->body;
+ begin = 0;
+ }
+
+ while (part) {
+ if (index < begin)
+ return NULL;
+
+ if (index + sz <= begin + part->size) {
+
+ r = bus_body_part_map(part);
+ if (r < 0)
+ return NULL;
+
+ if (p)
+ *p = part->data ? (uint8_t*) part->data + index - begin
+ : NULL; /* Avoid dereferencing a NULL pointer. */
+
+ m->cached_rindex_part = part;
+ m->cached_rindex_part_begin = begin;
+
+ return part;
+ }
+
+ begin += part->size;
+ part = part->next;
+ }
+
+ return NULL;
+}
+
+static int message_peek_body(
+ sd_bus_message *m,
+ size_t *rindex,
+ size_t align,
+ size_t nbytes,
+ void **ret) {
+
+ size_t k, start, end, padding;
+ struct bus_body_part *part;
+ uint8_t *q;
+
+ assert(m);
+ assert(rindex);
+ assert(align > 0);
+
+ start = ALIGN_TO(*rindex, align);
+ if (start > m->user_body_size)
+ return -EBADMSG;
+
+ padding = start - *rindex;
+
+ /* Avoid overflow below */
+ if (nbytes > SIZE_MAX - start)
+ return -EBADMSG;
+
+ end = start + nbytes;
+ if (end > m->user_body_size)
+ return -EBADMSG;
+
+ part = find_part(m, *rindex, padding, (void**) &q);
+ if (!part)
+ return -EBADMSG;
+
+ if (q) {
+ /* Verify padding */
+ for (k = 0; k < padding; k++)
+ if (q[k] != 0)
+ return -EBADMSG;
+ }
+
+ part = find_part(m, start, nbytes, (void**) &q);
+ if (!part || (nbytes > 0 && !q))
+ return -EBADMSG;
+
+ *rindex = end;
+
+ if (ret)
+ *ret = q;
+
+ return 0;
+}
+
+static bool validate_nul(const char *s, size_t l) {
+
+ /* Check for NUL chars in the string */
+ if (memchr(s, 0, l))
+ return false;
+
+ /* Check for NUL termination */
+ if (s[l] != 0)
+ return false;
+
+ return true;
+}
+
+static bool validate_string(const char *s, size_t l) {
+
+ if (!validate_nul(s, l))
+ return false;
+
+ /* Check if valid UTF8 */
+ if (!utf8_is_valid(s))
+ return false;
+
+ return true;
+}
+
+static bool validate_signature(const char *s, size_t l) {
+
+ if (!validate_nul(s, l))
+ return false;
+
+ /* Check if valid signature */
+ if (!signature_is_valid(s, true))
+ return false;
+
+ return true;
+}
+
+static bool validate_object_path(const char *s, size_t l) {
+
+ if (!validate_nul(s, l))
+ return false;
+
+ if (!object_path_is_valid(s))
+ return false;
+
+ return true;
+}
+
+_public_ int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p) {
+ struct bus_container *c;
+ size_t rindex;
+ void *q;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EPERM);
+ assert_return(bus_type_is_basic(type), -EINVAL);
+
+ if (message_end_of_signature(m))
+ return -ENXIO;
+
+ if (message_end_of_array(m, m->rindex))
+ return 0;
+
+ c = message_get_last_container(m);
+ if (c->signature[c->index] != type)
+ return -ENXIO;
+
+ rindex = m->rindex;
+
+ if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH)) {
+ uint32_t l;
+ bool ok;
+
+ r = message_peek_body(m, &rindex, 4, 4, &q);
+ if (r < 0)
+ 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;
+
+ if (type == SD_BUS_TYPE_OBJECT_PATH)
+ ok = validate_object_path(q, l);
+ else
+ ok = validate_string(q, l);
+ if (!ok)
+ return -EBADMSG;
+
+ if (p)
+ *(const char**) p = q;
+
+ } else if (type == SD_BUS_TYPE_SIGNATURE) {
+ uint8_t l;
+
+ r = message_peek_body(m, &rindex, 1, 1, &q);
+ if (r < 0)
+ 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;
+
+ if (!validate_signature(q, l))
+ return -EBADMSG;
+
+ if (p)
+ *(const char**) p = q;
+
+ } else {
+ ssize_t sz, align;
+
+ align = bus_type_get_alignment(type);
+ assert(align > 0);
+
+ sz = bus_type_get_size(type);
+ assert(sz > 0);
+
+ r = message_peek_body(m, &rindex, align, sz, &q);
+ if (r < 0)
+ return r;
+
+ switch (type) {
+
+ case SD_BUS_TYPE_BYTE:
+ if (p)
+ *(uint8_t*) p = *(uint8_t*) q;
+ break;
+
+ case SD_BUS_TYPE_BOOLEAN:
+ if (p)
+ *(int*) p = !!*(uint32_t*) q;
+ break;
+
+ case SD_BUS_TYPE_INT16:
+ case SD_BUS_TYPE_UINT16:
+ if (p)
+ *(uint16_t*) p = BUS_MESSAGE_BSWAP16(m, *(uint16_t*) q);
+ break;
+
+ case SD_BUS_TYPE_INT32:
+ case SD_BUS_TYPE_UINT32:
+ if (p)
+ *(uint32_t*) p = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q);
+ break;
+
+ case SD_BUS_TYPE_INT64:
+ case SD_BUS_TYPE_UINT64:
+ case SD_BUS_TYPE_DOUBLE:
+ if (p)
+ *(uint64_t*) p = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q);
+ break;
+
+ case SD_BUS_TYPE_UNIX_FD: {
+ uint32_t j;
+
+ j = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q);
+ if (j >= m->n_fds)
+ return -EBADMSG;
+
+ if (p)
+ *(int*) p = m->fds[j];
+ break;
+ }
+
+ default:
+ assert_not_reached();
+ }
+ }
+
+ m->rindex = rindex;
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ c->index++;
+
+ return 1;
+}
+
+static int bus_message_enter_array(
+ sd_bus_message *m,
+ struct bus_container *c,
+ const char *contents,
+ uint32_t **array_size) {
+
+ size_t rindex;
+ void *q;
+ int alignment, r;
+
+ assert(m);
+ assert(c);
+ assert(contents);
+ assert(array_size);
+
+ if (!signature_is_single(contents, true))
+ return -EINVAL;
+
+ if (!c->signature || c->signature[c->index] == 0)
+ return -ENXIO;
+
+ if (c->signature[c->index] != SD_BUS_TYPE_ARRAY)
+ return -ENXIO;
+
+ if (!startswith(c->signature + c->index + 1, contents))
+ return -ENXIO;
+
+ rindex = m->rindex;
+
+ r = message_peek_body(m, &rindex, 4, 4, &q);
+ if (r < 0)
+ return r;
+
+ if (BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q) > BUS_ARRAY_MAX_SIZE)
+ return -EBADMSG;
+
+ alignment = bus_type_get_alignment(contents[0]);
+ if (alignment < 0)
+ return alignment;
+
+ r = message_peek_body(m, &rindex, alignment, 0, NULL);
+ if (r < 0)
+ return r;
+
+ *array_size = (uint32_t*) q;
+
+ m->rindex = rindex;
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ c->index += 1 + strlen(contents);
+
+ return 1;
+}
+
+static int bus_message_enter_variant(
+ sd_bus_message *m,
+ struct bus_container *c,
+ const char *contents) {
+
+ size_t rindex;
+ uint8_t l;
+ void *q;
+ int r;
+
+ assert(m);
+ assert(c);
+ assert(contents);
+
+ if (!signature_is_single(contents, false))
+ return -EINVAL;
+
+ if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN)
+ return -EINVAL;
+
+ if (!c->signature || c->signature[c->index] == 0)
+ return -ENXIO;
+
+ if (c->signature[c->index] != SD_BUS_TYPE_VARIANT)
+ return -ENXIO;
+
+ rindex = m->rindex;
+
+ r = message_peek_body(m, &rindex, 1, 1, &q);
+ if (r < 0)
+ 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;
+
+ if (!validate_signature(q, l))
+ return -EBADMSG;
+
+ if (!streq(q, contents))
+ return -ENXIO;
+
+ m->rindex = rindex;
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ c->index++;
+
+ return 1;
+}
+
+static int bus_message_enter_struct(
+ sd_bus_message *m,
+ struct bus_container *c,
+ const char *contents) {
+
+ size_t l;
+ int r;
+
+ assert(m);
+ assert(c);
+ assert(contents);
+
+ if (!signature_is_valid(contents, false))
+ return -EINVAL;
+
+ if (!c->signature || c->signature[c->index] == 0)
+ return -ENXIO;
+
+ l = strlen(contents);
+
+ if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN ||
+ !startswith(c->signature + c->index + 1, contents) ||
+ c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END)
+ return -ENXIO;
+
+ r = message_peek_body(m, &m->rindex, 8, 0, NULL);
+ if (r < 0)
+ return r;
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ c->index += 1 + l + 1;
+
+ return 1;
+}
+
+static int bus_message_enter_dict_entry(
+ sd_bus_message *m,
+ struct bus_container *c,
+ const char *contents) {
+
+ size_t l;
+ int r;
+
+ assert(m);
+ assert(c);
+ assert(contents);
+
+ if (!signature_is_pair(contents))
+ return -EINVAL;
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ return -ENXIO;
+
+ if (!c->signature || c->signature[c->index] == 0)
+ return 0;
+
+ l = strlen(contents);
+
+ if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN ||
+ !startswith(c->signature + c->index + 1, contents) ||
+ c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END)
+ return -ENXIO;
+
+ r = message_peek_body(m, &m->rindex, 8, 0, NULL);
+ if (r < 0)
+ return r;
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY)
+ c->index += 1 + l + 1;
+
+ return 1;
+}
+
+_public_ int sd_bus_message_enter_container(sd_bus_message *m,
+ char type,
+ const char *contents) {
+ struct bus_container *c;
+ uint32_t *array_size = NULL;
+ _cleanup_free_ char *signature = NULL;
+ size_t before;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EPERM);
+ assert_return(type != 0 || !contents, -EINVAL);
+
+ if (type == 0 || !contents) {
+ const char *cc;
+ char tt;
+
+ /* Allow entering into anonymous containers */
+ r = sd_bus_message_peek_type(m, &tt, &cc);
+ if (r < 0)
+ return r;
+
+ if (type != 0 && type != tt)
+ return -ENXIO;
+
+ if (contents && !streq(contents, cc))
+ return -ENXIO;
+
+ type = tt;
+ contents = cc;
+ }
+
+ /*
+ * We enforce a global limit on container depth, that is much
+ * higher than the 32 structs and 32 arrays the specification
+ * mandates. This is simpler to implement for us, and we need
+ * this only to ensure our container array doesn't grow
+ * without bounds. We are happy to return any data from a
+ * message as long as the data itself is valid, even if the
+ * overall message might be not.
+ *
+ * Note that the message signature is validated when
+ * parsing the headers, and that validation does check the
+ * 32/32 limit.
+ *
+ * Note that the specification defines no limits on the depth
+ * of stacked variants, but we do.
+ */
+ if (m->n_containers >= BUS_CONTAINER_DEPTH)
+ return -EBADMSG;
+
+ if (!GREEDY_REALLOC(m->containers, m->n_containers + 1))
+ return -ENOMEM;
+
+ if (message_end_of_signature(m))
+ return -ENXIO;
+
+ if (message_end_of_array(m, m->rindex))
+ return 0;
+
+ c = message_get_last_container(m);
+
+ signature = strdup(contents);
+ if (!signature)
+ return -ENOMEM;
+
+ c->saved_index = c->index;
+ before = m->rindex;
+
+ if (type == SD_BUS_TYPE_ARRAY)
+ r = bus_message_enter_array(m, c, contents, &array_size);
+ else if (type == SD_BUS_TYPE_VARIANT)
+ r = bus_message_enter_variant(m, c, contents);
+ else if (type == SD_BUS_TYPE_STRUCT)
+ r = bus_message_enter_struct(m, c, contents);
+ else if (type == SD_BUS_TYPE_DICT_ENTRY)
+ r = bus_message_enter_dict_entry(m, c, contents);
+ else
+ r = -EINVAL;
+ if (r <= 0)
+ return r;
+
+ /* OK, let's fill it in */
+ 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 = m->rindex,
+ .array_size = array_size,
+ };
+
+ return 1;
+}
+
+_public_ int sd_bus_message_exit_container(sd_bus_message *m) {
+ struct bus_container *c;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EPERM);
+ assert_return(m->n_containers > 0, -ENXIO);
+
+ c = message_get_last_container(m);
+
+ if (c->enclosing != SD_BUS_TYPE_ARRAY) {
+ if (c->signature && c->signature[c->index] != 0)
+ return -EBUSY;
+ }
+
+ if (c->enclosing == SD_BUS_TYPE_ARRAY) {
+ uint32_t l;
+
+ l = BUS_MESSAGE_BSWAP32(m, *c->array_size);
+ if (c->begin + l != m->rindex)
+ return -EBUSY;
+ }
+
+ message_free_last_container(m);
+
+ return 1;
+}
+
+static void message_quit_container(sd_bus_message *m) {
+ struct bus_container *c;
+
+ assert(m);
+ assert(m->sealed);
+ assert(m->n_containers > 0);
+
+ /* Undo seeks */
+ c = message_get_last_container(m);
+ assert(m->rindex >= c->before);
+ m->rindex = c->before;
+
+ /* Free container */
+ message_free_last_container(m);
+
+ /* Correct index of new top-level container */
+ c = message_get_last_container(m);
+ c->index = c->saved_index;
+}
+
+_public_ int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents) {
+ struct bus_container *c;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EPERM);
+
+ if (message_end_of_signature(m))
+ goto eof;
+
+ if (message_end_of_array(m, m->rindex))
+ goto eof;
+
+ c = message_get_last_container(m);
+
+ if (bus_type_is_basic(c->signature[c->index])) {
+ if (contents)
+ *contents = NULL;
+ if (type)
+ *type = c->signature[c->index];
+ return 1;
+ }
+
+ if (c->signature[c->index] == SD_BUS_TYPE_ARRAY) {
+
+ if (contents) {
+ size_t l;
+
+ r = signature_element_length(c->signature+c->index+1, &l);
+ if (r < 0)
+ return r;
+
+ /* signature_element_length does verification internally */
+
+ /* 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;
+
+ *contents = c->peeked_signature;
+ }
+
+ if (type)
+ *type = SD_BUS_TYPE_ARRAY;
+
+ return 1;
+ }
+
+ if (IN_SET(c->signature[c->index], SD_BUS_TYPE_STRUCT_BEGIN, SD_BUS_TYPE_DICT_ENTRY_BEGIN)) {
+
+ if (contents) {
+ size_t l;
+
+ r = signature_element_length(c->signature+c->index, &l);
+ if (r < 0)
+ return r;
+
+ assert(l >= 3);
+ if (free_and_strndup(&c->peeked_signature,
+ c->signature + c->index + 1, l - 2) < 0)
+ return -ENOMEM;
+
+ *contents = c->peeked_signature;
+ }
+
+ if (type)
+ *type = c->signature[c->index] == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY;
+
+ return 1;
+ }
+
+ if (c->signature[c->index] == SD_BUS_TYPE_VARIANT) {
+ if (contents) {
+ size_t rindex, l;
+ void *q;
+
+ rindex = m->rindex;
+ r = message_peek_body(m, &rindex, 1, 1, &q);
+ if (r < 0)
+ 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;
+
+ if (!validate_signature(q, l))
+ return -EBADMSG;
+
+ *contents = q;
+ }
+
+ if (type)
+ *type = SD_BUS_TYPE_VARIANT;
+
+ return 1;
+ }
+
+ return -EINVAL;
+
+eof:
+ if (type)
+ *type = 0;
+ if (contents)
+ *contents = NULL;
+ return 0;
+}
+
+_public_ int sd_bus_message_rewind(sd_bus_message *m, int complete) {
+ struct bus_container *c;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EPERM);
+
+ if (complete) {
+ message_reset_containers(m);
+ m->rindex = 0;
+
+ c = message_get_last_container(m);
+ } else {
+ c = message_get_last_container(m);
+
+ c->index = 0;
+ m->rindex = c->begin;
+ }
+
+ return !isempty(c->signature);
+}
+
+_public_ int sd_bus_message_readv(
+ sd_bus_message *m,
+ const char *types,
+ va_list ap) {
+
+ unsigned n_array, n_struct;
+ TypeStack stack[BUS_CONTAINER_DEPTH];
+ unsigned stack_ptr = 0;
+ unsigned n_loop = 0;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EPERM);
+ assert_return(types, -EINVAL);
+
+ if (isempty(types))
+ return 0;
+
+ /* Ideally, we'd just call ourselves recursively on every
+ * complex type. However, the state of a va_list that is
+ * passed to a function is undefined after that function
+ * returns. This means we need to decode the va_list linearly
+ * in a single stackframe. We hence implement our own
+ * home-grown stack in an array. */
+
+ n_array = UINT_MAX; /* length of current array entries */
+ n_struct = strlen(types); /* length of current struct contents signature */
+
+ for (;;) {
+ const char *t;
+
+ n_loop++;
+
+ if (n_array == 0 || (n_array == UINT_MAX && n_struct == 0)) {
+ r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ continue;
+ }
+
+ t = types;
+ if (n_array != UINT_MAX)
+ n_array--;
+ else {
+ types++;
+ n_struct--;
+ }
+
+ switch (*t) {
+
+ case SD_BUS_TYPE_BYTE:
+ case SD_BUS_TYPE_BOOLEAN:
+ case SD_BUS_TYPE_INT16:
+ case SD_BUS_TYPE_UINT16:
+ case SD_BUS_TYPE_INT32:
+ case SD_BUS_TYPE_UINT32:
+ case SD_BUS_TYPE_INT64:
+ case SD_BUS_TYPE_UINT64:
+ case SD_BUS_TYPE_DOUBLE:
+ case SD_BUS_TYPE_STRING:
+ case SD_BUS_TYPE_OBJECT_PATH:
+ case SD_BUS_TYPE_SIGNATURE:
+ case SD_BUS_TYPE_UNIX_FD: {
+ void *p;
+
+ p = va_arg(ap, void*);
+ r = sd_bus_message_read_basic(m, *t, p);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ if (n_loop <= 1)
+ return 0;
+
+ return -ENXIO;
+ }
+
+ break;
+ }
+
+ case SD_BUS_TYPE_ARRAY: {
+ size_t k;
+
+ r = signature_element_length(t + 1, &k);
+ if (r < 0)
+ return r;
+
+ {
+ char s[k + 1];
+ memcpy(s, t + 1, k);
+ s[k] = 0;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, s);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ if (n_loop <= 1)
+ return 0;
+
+ return -ENXIO;
+ }
+ }
+
+ if (n_array == UINT_MAX) {
+ types += k;
+ n_struct -= k;
+ }
+
+ r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array);
+ if (r < 0)
+ return r;
+
+ types = t + 1;
+ n_struct = k;
+ n_array = va_arg(ap, unsigned);
+
+ break;
+ }
+
+ case SD_BUS_TYPE_VARIANT: {
+ const char *s;
+
+ s = va_arg(ap, const char *);
+ if (!s)
+ return -EINVAL;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, s);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ if (n_loop <= 1)
+ return 0;
+
+ return -ENXIO;
+ }
+
+ r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array);
+ if (r < 0)
+ return r;
+
+ types = s;
+ n_struct = strlen(s);
+ n_array = UINT_MAX;
+
+ break;
+ }
+
+ case SD_BUS_TYPE_STRUCT_BEGIN:
+ case SD_BUS_TYPE_DICT_ENTRY_BEGIN: {
+ size_t k;
+
+ r = signature_element_length(t, &k);
+ if (r < 0)
+ return r;
+ if (k < 2)
+ return -ERANGE;
+
+ {
+ char s[k - 1];
+ memcpy(s, t + 1, k - 2);
+ s[k - 2] = 0;
+
+ r = sd_bus_message_enter_container(m, *t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ if (n_loop <= 1)
+ return 0;
+ return -ENXIO;
+ }
+ }
+
+ if (n_array == UINT_MAX) {
+ types += k - 1;
+ n_struct -= k - 1;
+ }
+
+ r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array);
+ if (r < 0)
+ return r;
+
+ types = t + 1;
+ n_struct = k - 2;
+ n_array = UINT_MAX;
+
+ break;
+ }
+
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 1;
+}
+
+_public_ int sd_bus_message_read(sd_bus_message *m, const char *types, ...) {
+ va_list ap;
+ int r;
+
+ va_start(ap, types);
+ r = sd_bus_message_readv(m, types, ap);
+ va_end(ap);
+
+ return r;
+}
+
+_public_ int sd_bus_message_skip(sd_bus_message *m, const char *types) {
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EPERM);
+
+ /* If types is NULL, read exactly one element */
+ if (!types) {
+ struct bus_container *c;
+ size_t l;
+
+ if (message_end_of_signature(m))
+ return -ENXIO;
+
+ if (message_end_of_array(m, m->rindex))
+ return 0;
+
+ c = message_get_last_container(m);
+
+ r = signature_element_length(c->signature + c->index, &l);
+ if (r < 0)
+ return r;
+
+ types = strndupa_safe(c->signature + c->index, l);
+ }
+
+ switch (*types) {
+
+ case 0: /* Nothing to drop */
+ return 0;
+
+ case SD_BUS_TYPE_BYTE:
+ case SD_BUS_TYPE_BOOLEAN:
+ case SD_BUS_TYPE_INT16:
+ case SD_BUS_TYPE_UINT16:
+ case SD_BUS_TYPE_INT32:
+ case SD_BUS_TYPE_UINT32:
+ case SD_BUS_TYPE_INT64:
+ case SD_BUS_TYPE_UINT64:
+ case SD_BUS_TYPE_DOUBLE:
+ case SD_BUS_TYPE_STRING:
+ case SD_BUS_TYPE_OBJECT_PATH:
+ case SD_BUS_TYPE_SIGNATURE:
+ case SD_BUS_TYPE_UNIX_FD:
+
+ r = sd_bus_message_read_basic(m, *types, NULL);
+ if (r <= 0)
+ return r;
+
+ r = sd_bus_message_skip(m, types + 1);
+ if (r < 0)
+ return r;
+
+ return 1;
+
+ case SD_BUS_TYPE_ARRAY: {
+ size_t k;
+
+ r = signature_element_length(types + 1, &k);
+ if (r < 0)
+ return r;
+
+ {
+ char s[k+1];
+ memcpy(s, types+1, k);
+ s[k] = 0;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, s);
+ if (r <= 0)
+ return r;
+
+ for (;;) {
+ r = sd_bus_message_skip(m, s);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_skip(m, types + 1 + k);
+ if (r < 0)
+ return r;
+
+ return 1;
+ }
+
+ case SD_BUS_TYPE_VARIANT: {
+ const char *contents;
+ char x;
+
+ r = sd_bus_message_peek_type(m, &x, &contents);
+ if (r <= 0)
+ return r;
+
+ if (x != SD_BUS_TYPE_VARIANT)
+ return -ENXIO;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents);
+ if (r <= 0)
+ return r;
+
+ r = sd_bus_message_skip(m, contents);
+ if (r < 0)
+ return r;
+ assert(r != 0);
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_skip(m, types + 1);
+ if (r < 0)
+ return r;
+
+ return 1;
+ }
+
+ case SD_BUS_TYPE_STRUCT_BEGIN:
+ case SD_BUS_TYPE_DICT_ENTRY_BEGIN: {
+ size_t k;
+
+ r = signature_element_length(types, &k);
+ if (r < 0)
+ return r;
+ if (k < 2)
+ return -ERANGE;
+
+ {
+ char s[k-1];
+ memcpy(s, types+1, k-2);
+ s[k-2] = 0;
+
+ r = sd_bus_message_enter_container(m, *types == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s);
+ if (r <= 0)
+ return r;
+
+ r = sd_bus_message_skip(m, s);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_skip(m, types + k);
+ if (r < 0)
+ return r;
+
+ return 1;
+ }
+
+ default:
+ return -EINVAL;
+ }
+}
+
+_public_ int sd_bus_message_read_array(
+ sd_bus_message *m,
+ char type,
+ const void **ptr,
+ size_t *size) {
+
+ struct bus_container *c;
+ void *p;
+ size_t sz;
+ ssize_t align;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EPERM);
+ assert_return(bus_type_is_trivial(type), -EINVAL);
+ assert_return(ptr, -EINVAL);
+ assert_return(size, -EINVAL);
+ assert_return(!BUS_MESSAGE_NEED_BSWAP(m), -EOPNOTSUPP);
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type));
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ *ptr = NULL;
+ *size = 0;
+ return 0;
+ }
+
+ c = message_get_last_container(m);
+
+ align = bus_type_get_alignment(type);
+ if (align < 0)
+ return align;
+
+ sz = BUS_MESSAGE_BSWAP32(m, *c->array_size);
+
+ if (sz == 0)
+ /* Zero length array, let's return some aligned
+ * pointer that is not NULL */
+ p = (uint8_t*) align;
+ else {
+ r = message_peek_body(m, &m->rindex, align, sz, &p);
+ if (r < 0)
+ goto fail;
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ goto fail;
+
+ *ptr = (const void*) p;
+ *size = sz;
+
+ return 1;
+
+fail:
+ message_quit_container(m);
+ return r;
+}
+
+static int message_peek_fields(
+ sd_bus_message *m,
+ size_t *rindex,
+ size_t align,
+ size_t nbytes,
+ void **ret) {
+
+ size_t start, end;
+
+ assert(m);
+ assert(rindex);
+ assert(align > 0);
+
+ start = ALIGN_TO(*rindex, align);
+ if (start > m->fields_size)
+ return -EBADMSG;
+
+ /* Avoid overflow below */
+ if (nbytes > SIZE_MAX - start)
+ return -EBADMSG;
+
+ end = start + nbytes;
+ if (end > m->fields_size)
+ return -EBADMSG;
+
+ /* Verify that padding is 0 */
+ uint8_t *p = BUS_MESSAGE_FIELDS(m);
+ for (size_t k = *rindex; k < start; k++)
+ if (p[k] != 0)
+ return -EBADMSG;
+
+ if (ret)
+ *ret = p + start;
+
+ *rindex = end;
+ return 1;
+}
+
+static int message_peek_field_uint32(
+ sd_bus_message *m,
+ size_t *ri,
+ size_t item_size,
+ uint32_t *ret) {
+
+ int r;
+ void *q;
+
+ assert(m);
+ assert(ri);
+
+ r = message_peek_fields(m, ri, 4, 4, &q);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q);
+
+ return 0;
+}
+
+static int message_peek_field_string(
+ sd_bus_message *m,
+ bool (*validate)(const char *p),
+ size_t *ri,
+ size_t item_size,
+ const char **ret) {
+
+ uint32_t l;
+ int r;
+ void *q;
+
+ assert(m);
+ assert(ri);
+
+ r = message_peek_field_uint32(m, ri, 4, &l);
+ 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;
+
+ if (validate) {
+ if (!validate_nul(q, l))
+ return -EBADMSG;
+
+ if (!validate(q))
+ return -EBADMSG;
+ } else {
+ if (!validate_string(q, l))
+ return -EBADMSG;
+ }
+
+ if (ret)
+ *ret = q;
+
+ return 0;
+}
+
+static int message_peek_field_signature(
+ sd_bus_message *m,
+ size_t *ri,
+ size_t item_size,
+ const char **ret) {
+
+ size_t l;
+ int r;
+ void *q;
+
+ assert(m);
+ assert(ri);
+
+ r = message_peek_fields(m, ri, 1, 1, &q);
+ if (r < 0)
+ 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;
+
+ if (!validate_signature(q, l))
+ return -EBADMSG;
+
+ if (ret)
+ *ret = q;
+
+ return 0;
+}
+
+static int message_skip_fields(
+ sd_bus_message *m,
+ size_t *ri,
+ uint32_t array_size,
+ const char **signature) {
+
+ size_t original_index;
+ int r;
+
+ assert(m);
+ assert(ri);
+ assert(signature);
+
+ original_index = *ri;
+
+ for (;;) {
+ char t;
+ size_t l;
+
+ if (array_size != UINT32_MAX &&
+ array_size <= *ri - original_index)
+ return 0;
+
+ t = **signature;
+ if (!t)
+ return 0;
+
+ if (t == SD_BUS_TYPE_STRING) {
+
+ r = message_peek_field_string(m, NULL, ri, 0, NULL);
+ if (r < 0)
+ return r;
+
+ (*signature)++;
+
+ } else if (t == SD_BUS_TYPE_OBJECT_PATH) {
+
+ r = message_peek_field_string(m, object_path_is_valid, ri, 0, NULL);
+ if (r < 0)
+ return r;
+
+ (*signature)++;
+
+ } else if (t == SD_BUS_TYPE_SIGNATURE) {
+
+ r = message_peek_field_signature(m, ri, 0, NULL);
+ if (r < 0)
+ return r;
+
+ (*signature)++;
+
+ } else if (bus_type_is_basic(t)) {
+ ssize_t align, k;
+
+ align = bus_type_get_alignment(t);
+ k = bus_type_get_size(t);
+ assert(align > 0 && k > 0);
+
+ r = message_peek_fields(m, ri, align, k, NULL);
+ if (r < 0)
+ return r;
+
+ (*signature)++;
+
+ } else if (t == SD_BUS_TYPE_ARRAY) {
+
+ r = signature_element_length(*signature + 1, &l);
+ if (r < 0)
+ return r;
+
+ assert(l >= 1);
+ {
+ char sig[l + 1], *s = sig;
+ uint32_t nas;
+ int alignment;
+
+ strncpy(sig, *signature + 1, l);
+ sig[l] = '\0';
+
+ alignment = bus_type_get_alignment(sig[0]);
+ if (alignment < 0)
+ return alignment;
+
+ r = message_peek_field_uint32(m, ri, 0, &nas);
+ if (r < 0)
+ return r;
+ if (nas > BUS_ARRAY_MAX_SIZE)
+ return -EBADMSG;
+
+ r = message_peek_fields(m, ri, alignment, 0, NULL);
+ if (r < 0)
+ return r;
+
+ r = message_skip_fields(m, ri, nas, (const char**) &s);
+ if (r < 0)
+ return r;
+ }
+
+ (*signature) += 1 + l;
+
+ } else if (t == SD_BUS_TYPE_VARIANT) {
+ const char *s;
+
+ r = message_peek_field_signature(m, ri, 0, &s);
+ if (r < 0)
+ return r;
+
+ r = message_skip_fields(m, ri, UINT32_MAX, (const char**) &s);
+ if (r < 0)
+ return r;
+
+ (*signature)++;
+
+ } else if (IN_SET(t, SD_BUS_TYPE_STRUCT, SD_BUS_TYPE_DICT_ENTRY)) {
+
+ r = signature_element_length(*signature, &l);
+ if (r < 0)
+ return r;
+
+ assert(l >= 2);
+ {
+ char sig[l + 1], *s = sig;
+ strncpy(sig, *signature + 1, l);
+ sig[l] = '\0';
+
+ r = message_skip_fields(m, ri, UINT32_MAX, (const char**) &s);
+ if (r < 0)
+ return r;
+ }
+
+ *signature += l;
+ } else
+ return -EBADMSG;
+ }
+}
+
+static int message_parse_fields(sd_bus_message *m) {
+ uint32_t unix_fds = 0;
+ bool unix_fds_set = false;
+ int r;
+
+ assert(m);
+
+ m->user_body_size = m->body_size;
+
+ for (size_t ri = 0; ri < m->fields_size; ) {
+ const char *signature;
+ uint64_t field_type;
+ size_t item_size = SIZE_MAX;
+ uint8_t *u8;
+
+ r = message_peek_fields(m, &ri, 8, 1, (void**) &u8);
+ if (r < 0)
+ return r;
+
+ field_type = *u8;
+
+ r = message_peek_field_signature(m, &ri, 0, &signature);
+ if (r < 0)
+ return r;
+
+ switch (field_type) {
+
+ case _BUS_MESSAGE_HEADER_INVALID:
+ return -EBADMSG;
+
+ case BUS_MESSAGE_HEADER_PATH:
+
+ if (m->path)
+ return -EBADMSG;
+
+ if (!streq(signature, "o"))
+ return -EBADMSG;
+
+ r = message_peek_field_string(m, object_path_is_valid, &ri, item_size, &m->path);
+ break;
+
+ case BUS_MESSAGE_HEADER_INTERFACE:
+
+ if (m->interface)
+ return -EBADMSG;
+
+ if (!streq(signature, "s"))
+ return -EBADMSG;
+
+ r = message_peek_field_string(m, interface_name_is_valid, &ri, item_size, &m->interface);
+ break;
+
+ case BUS_MESSAGE_HEADER_MEMBER:
+
+ if (m->member)
+ return -EBADMSG;
+
+ if (!streq(signature, "s"))
+ return -EBADMSG;
+
+ r = message_peek_field_string(m, member_name_is_valid, &ri, item_size, &m->member);
+ break;
+
+ case BUS_MESSAGE_HEADER_ERROR_NAME:
+
+ if (m->error.name)
+ return -EBADMSG;
+
+ if (!streq(signature, "s"))
+ return -EBADMSG;
+
+ r = message_peek_field_string(m, error_name_is_valid, &ri, item_size, &m->error.name);
+ if (r >= 0)
+ m->error._need_free = -1;
+
+ break;
+
+ case BUS_MESSAGE_HEADER_DESTINATION:
+
+ if (m->destination)
+ return -EBADMSG;
+
+ if (!streq(signature, "s"))
+ return -EBADMSG;
+
+ r = message_peek_field_string(m, service_name_is_valid, &ri, item_size, &m->destination);
+ break;
+
+ case BUS_MESSAGE_HEADER_SENDER:
+
+ if (m->sender)
+ return -EBADMSG;
+
+ if (!streq(signature, "s"))
+ return -EBADMSG;
+
+ r = message_peek_field_string(m, service_name_is_valid, &ri, item_size, &m->sender);
+
+ if (r >= 0 && m->sender[0] == ':' && m->bus->bus_client) {
+ m->creds.unique_name = (char*) m->sender;
+ m->creds.mask |= SD_BUS_CREDS_UNIQUE_NAME & m->bus->creds_mask;
+ }
+
+ break;
+
+ case BUS_MESSAGE_HEADER_SIGNATURE: {
+ const char *s;
+ char *c;
+
+ if (m->root_container.signature)
+ return -EBADMSG;
+
+ if (!streq(signature, "g"))
+ return -EBADMSG;
+
+ r = message_peek_field_signature(m, &ri, item_size, &s);
+ if (r < 0)
+ return r;
+
+ c = strdup(s);
+ if (!c)
+ return -ENOMEM;
+
+ free_and_replace(m->root_container.signature, c);
+ break;
+ }
+
+ case BUS_MESSAGE_HEADER_REPLY_SERIAL: {
+ uint32_t serial;
+
+ if (m->reply_cookie != 0)
+ return -EBADMSG;
+
+
+ if (!streq(signature, "u"))
+ return -EBADMSG;
+
+ r = message_peek_field_uint32(m, &ri, item_size, &serial);
+ if (r < 0)
+ return r;
+
+ m->reply_cookie = serial;
+
+ if (m->reply_cookie == 0)
+ return -EBADMSG;
+
+ break;
+ }
+ case BUS_MESSAGE_HEADER_UNIX_FDS:
+ if (unix_fds_set)
+ return -EBADMSG;
+
+ if (!streq(signature, "u"))
+ return -EBADMSG;
+
+ r = message_peek_field_uint32(m, &ri, item_size, &unix_fds);
+ if (r < 0)
+ return -EBADMSG;
+
+ unix_fds_set = true;
+ break;
+
+ default:
+ r = message_skip_fields(m, &ri, UINT32_MAX, (const char **) &signature);
+ }
+ if (r < 0)
+ return r;
+ }
+
+ if (m->n_fds != unix_fds)
+ return -EBADMSG;
+
+ switch (m->header->type) {
+
+ case SD_BUS_MESSAGE_SIGNAL:
+ if (!m->path || !m->interface || !m->member)
+ return -EBADMSG;
+
+ if (m->reply_cookie != 0)
+ return -EBADMSG;
+
+ break;
+
+ case SD_BUS_MESSAGE_METHOD_CALL:
+
+ if (!m->path || !m->member)
+ return -EBADMSG;
+
+ if (m->reply_cookie != 0)
+ return -EBADMSG;
+
+ break;
+
+ case SD_BUS_MESSAGE_METHOD_RETURN:
+
+ if (m->reply_cookie == 0)
+ return -EBADMSG;
+ break;
+
+ case SD_BUS_MESSAGE_METHOD_ERROR:
+
+ if (m->reply_cookie == 0 || !m->error.name)
+ return -EBADMSG;
+ break;
+ }
+
+ /* Refuse non-local messages that claim they are local */
+ if (streq_ptr(m->path, "/org/freedesktop/DBus/Local"))
+ return -EBADMSG;
+ if (streq_ptr(m->interface, "org.freedesktop.DBus.Local"))
+ return -EBADMSG;
+ if (streq_ptr(m->sender, "org.freedesktop.DBus.Local"))
+ return -EBADMSG;
+
+ m->root_container.end = m->user_body_size;
+
+ /* Try to read the error message, but if we can't it's a non-issue */
+ if (m->header->type == SD_BUS_MESSAGE_METHOD_ERROR)
+ (void) sd_bus_message_read(m, "s", &m->error.message);
+
+ return 0;
+}
+
+_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);
+
+ return message_append_field_string(m, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &m->destination);
+}
+
+_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);
+
+ return message_append_field_string(m, BUS_MESSAGE_HEADER_SENDER, SD_BUS_TYPE_STRING, sender, &m->sender);
+}
+
+int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) {
+ size_t total;
+ void *p, *e;
+ size_t i;
+ struct bus_body_part *part;
+
+ assert(m);
+ assert(buffer);
+ assert(sz);
+
+ total = BUS_MESSAGE_SIZE(m);
+
+ p = malloc(total);
+ if (!p)
+ return -ENOMEM;
+
+ e = mempcpy(p, m->header, BUS_MESSAGE_BODY_BEGIN(m));
+ MESSAGE_FOREACH_PART(part, i, m)
+ e = mempcpy(e, part->data, part->size);
+
+ assert(total == (size_t) ((uint8_t*) e - (uint8_t*) p));
+
+ *buffer = p;
+ *sz = total;
+
+ return 0;
+}
+
+_public_ int sd_bus_message_read_strv_extend(sd_bus_message *m, char ***l) {
+ char type;
+ const char *contents, *s;
+ int r;
+
+ assert(m);
+ assert(l);
+
+ r = sd_bus_message_peek_type(m, &type, &contents);
+ if (r < 0)
+ return r;
+
+ if (type != SD_BUS_TYPE_ARRAY || !STR_IN_SET(contents, "s", "o", "g"))
+ return -ENXIO;
+
+ r = sd_bus_message_enter_container(m, 'a', NULL);
+ if (r <= 0)
+ return r;
+
+ /* sd_bus_message_read_basic() does content validation for us. */
+ while ((r = sd_bus_message_read_basic(m, *contents, &s)) > 0) {
+ r = strv_extend(l, s);
+ if (r < 0)
+ return r;
+ }
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+_public_ int sd_bus_message_read_strv(sd_bus_message *m, char ***l) {
+ _cleanup_strv_free_ char **strv = NULL;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EPERM);
+ assert_return(l, -EINVAL);
+
+ r = sd_bus_message_read_strv_extend(m, &strv);
+ if (r <= 0)
+ return r;
+
+ *l = TAKE_PTR(strv);
+ return 1;
+}
+
+static int bus_message_get_arg_skip(
+ sd_bus_message *m,
+ unsigned i,
+ char *_type,
+ const char **_contents) {
+
+ unsigned j;
+ int r;
+
+ r = sd_bus_message_rewind(m, true);
+ if (r < 0)
+ return r;
+
+ for (j = 0;; j++) {
+ const char *contents;
+ char type;
+
+ r = sd_bus_message_peek_type(m, &type, &contents);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENXIO;
+
+ /* Don't match against arguments after the first one we don't understand */
+ if (!IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE) &&
+ !(type == SD_BUS_TYPE_ARRAY && STR_IN_SET(contents, "s", "o", "g")))
+ return -ENXIO;
+
+ if (j >= i) {
+ if (_contents)
+ *_contents = contents;
+ if (_type)
+ *_type = type;
+ return 0;
+ }
+
+ r = sd_bus_message_skip(m, NULL);
+ if (r < 0)
+ return r;
+ }
+
+}
+
+int bus_message_get_arg(sd_bus_message *m, unsigned i, const char **str) {
+ char type;
+ int r;
+
+ assert(m);
+ assert(str);
+
+ r = bus_message_get_arg_skip(m, i, &type, NULL);
+ if (r < 0)
+ return r;
+
+ if (!IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE))
+ return -ENXIO;
+
+ return sd_bus_message_read_basic(m, type, str);
+}
+
+int bus_message_get_arg_strv(sd_bus_message *m, unsigned i, char ***strv) {
+ const char *contents;
+ char type;
+ int r;
+
+ assert(m);
+ assert(strv);
+
+ r = bus_message_get_arg_skip(m, i, &type, &contents);
+ if (r < 0)
+ return r;
+
+ if (type != SD_BUS_TYPE_ARRAY)
+ return -ENXIO;
+ if (!STR_IN_SET(contents, "s", "o", "g"))
+ return -ENXIO;
+
+ return sd_bus_message_read_strv(m, strv);
+}
+
+_public_ int sd_bus_message_get_errno(sd_bus_message *m) {
+ assert_return(m, EINVAL);
+
+ if (m->header->type != SD_BUS_MESSAGE_METHOD_ERROR)
+ return 0;
+
+ return sd_bus_error_get_errno(&m->error);
+}
+
+_public_ const char* sd_bus_message_get_signature(sd_bus_message *m, int complete) {
+ struct bus_container *c;
+
+ assert_return(m, NULL);
+
+ c = complete ? &m->root_container : message_get_last_container(m);
+ return strempty(c->signature);
+}
+
+_public_ int sd_bus_message_is_empty(sd_bus_message *m) {
+ assert_return(m, -EINVAL);
+
+ return isempty(m->root_container.signature);
+}
+
+_public_ int sd_bus_message_has_signature(sd_bus_message *m, const char *signature) {
+ assert_return(m, -EINVAL);
+
+ return streq(strempty(m->root_container.signature), strempty(signature));
+}
+
+_public_ int sd_bus_message_copy(sd_bus_message *m, sd_bus_message *source, int all) {
+ bool done_something = false;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(source, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+ assert_return(source->sealed, -EPERM);
+
+ do {
+ const char *contents;
+ 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(source, &type, &contents);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ done_something = true;
+
+ if (bus_type_is_container(type) > 0) {
+
+ r = sd_bus_message_enter_container(source, type, contents);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(m, type, contents);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_copy(m, source, true);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(source);
+ if (r < 0)
+ return r;
+
+ continue;
+ }
+
+ r = sd_bus_message_read_basic(source, type, &basic);
+ if (r < 0)
+ return r;
+
+ assert(r > 0);
+
+ if (IN_SET(type, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE, SD_BUS_TYPE_STRING))
+ r = sd_bus_message_append_basic(m, type, basic.string);
+ else
+ r = sd_bus_message_append_basic(m, type, &basic);
+
+ if (r < 0)
+ return r;
+
+ } while (all);
+
+ return done_something;
+}
+
+_public_ int sd_bus_message_verify_type(sd_bus_message *m, char type, const char *contents) {
+ const char *c;
+ char t;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EPERM);
+ assert_return(!type || bus_type_is_valid(type), -EINVAL);
+ assert_return(!contents || signature_is_valid(contents, true), -EINVAL);
+ assert_return(type || contents, -EINVAL);
+ assert_return(!contents || !type || bus_type_is_container(type), -EINVAL);
+
+ r = sd_bus_message_peek_type(m, &t, &c);
+ if (r <= 0)
+ return r;
+
+ if (type != 0 && type != t)
+ return 0;
+
+ if (contents && !streq_ptr(contents, c))
+ return 0;
+
+ return 1;
+}
+
+_public_ sd_bus *sd_bus_message_get_bus(sd_bus_message *m) {
+ assert_return(m, NULL);
+
+ return m->bus;
+}
+
+int bus_message_remarshal(sd_bus *bus, sd_bus_message **m) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *n = NULL;
+ usec_t timeout;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(*m);
+
+ switch ((*m)->header->type) {
+
+ case SD_BUS_MESSAGE_SIGNAL:
+ r = sd_bus_message_new_signal(bus, &n, (*m)->path, (*m)->interface, (*m)->member);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case SD_BUS_MESSAGE_METHOD_CALL:
+ r = sd_bus_message_new_method_call(bus, &n, (*m)->destination, (*m)->path, (*m)->interface, (*m)->member);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case SD_BUS_MESSAGE_METHOD_RETURN:
+ case SD_BUS_MESSAGE_METHOD_ERROR:
+
+ r = sd_bus_message_new(bus, &n, (*m)->header->type);
+ if (r < 0)
+ return -ENOMEM;
+
+ assert(n);
+
+ n->reply_cookie = (*m)->reply_cookie;
+
+ r = message_append_reply_cookie(n, n->reply_cookie);
+ if (r < 0)
+ return r;
+
+ if ((*m)->header->type == SD_BUS_MESSAGE_METHOD_ERROR && (*m)->error.name) {
+ r = message_append_field_string(n, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, (*m)->error.name, &n->error.message);
+ if (r < 0)
+ return r;
+
+ n->error._need_free = -1;
+ }
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if ((*m)->destination && !n->destination) {
+ r = message_append_field_string(n, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, (*m)->destination, &n->destination);
+ if (r < 0)
+ return r;
+ }
+
+ if ((*m)->sender && !n->sender) {
+ r = message_append_field_string(n, BUS_MESSAGE_HEADER_SENDER, SD_BUS_TYPE_STRING, (*m)->sender, &n->sender);
+ if (r < 0)
+ return r;
+ }
+
+ n->header->flags |= (*m)->header->flags & (BUS_MESSAGE_NO_REPLY_EXPECTED|BUS_MESSAGE_NO_AUTO_START);
+
+ r = sd_bus_message_copy(n, *m, true);
+ if (r < 0)
+ return r;
+
+ timeout = (*m)->timeout;
+ if (timeout == 0 && !((*m)->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)) {
+ r = sd_bus_get_method_call_timeout(bus, &timeout);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_seal(n, BUS_MESSAGE_COOKIE(*m), timeout);
+ if (r < 0)
+ return r;
+
+ sd_bus_message_unref(*m);
+ *m = TAKE_PTR(n);
+
+ return 0;
+}
+
+_public_ int sd_bus_message_get_priority(sd_bus_message *m, int64_t *priority) {
+ static bool warned = false;
+
+ assert_return(m, -EINVAL);
+ assert_return(priority, -EINVAL);
+
+ if (!warned) {
+ log_debug("sd_bus_message_get_priority() is deprecated and always returns 0.");
+ warned = true;
+ }
+
+ *priority = 0;
+ return 0;
+}
+
+_public_ int sd_bus_message_set_priority(sd_bus_message *m, int64_t priority) {
+ static bool warned = false;
+
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+
+ if (!warned) {
+ log_debug("sd_bus_message_set_priority() is deprecated and does nothing.");
+ warned = true;
+ }
+
+ return 0;
+}
+
+_public_ int sd_bus_message_sensitive(sd_bus_message *m) {
+ assert_return(m, -EINVAL);
+
+ m->sensitive = true;
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/bus-message.h b/src/libsystemd/sd-bus/bus-message.h
new file mode 100644
index 0000000..0439fbd
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-message.h
@@ -0,0 +1,189 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <byteswap.h>
+#include <stdbool.h>
+#include <sys/socket.h>
+
+#include "sd-bus.h"
+
+#include "bus-creds.h"
+#include "bus-protocol.h"
+#include "macro.h"
+#include "time-util.h"
+
+struct bus_container {
+ char enclosing;
+
+ /* Indexes into the signature string */
+ unsigned index, saved_index;
+ char *signature;
+
+ size_t before, begin, end;
+
+ /* pointer to the array size value, if this is a value */
+ uint32_t *array_size;
+
+ char *peeked_signature;
+};
+
+struct bus_body_part {
+ struct bus_body_part *next;
+ void *data;
+ void *mmap_begin;
+ size_t size;
+ size_t mapped;
+ size_t allocated;
+ uint64_t memfd_offset;
+ int memfd;
+ bool free_this:1;
+ bool munmap_this:1;
+ bool sealed:1;
+ bool is_zero:1;
+};
+
+struct sd_bus_message {
+ /* Caveat: a message can be referenced in two different ways: the main (user-facing) way will also
+ * pin the bus connection object the message is associated with. The secondary way ("queued") is used
+ * when a message is in the read or write queues of the bus connection object, which will not pin the
+ * bus connection object. This is necessary so that we don't have to have a pair of cyclic references
+ * between a message that is queued and its connection: as soon as a message is only referenced by
+ * the connection (by means of being queued) and the connection itself has no other references it
+ * will be freed. */
+
+ unsigned n_ref; /* Counter of references that pin the connection */
+ unsigned n_queued; /* Counter of references that do not pin the connection */
+
+ sd_bus *bus;
+
+ uint64_t reply_cookie;
+
+ const char *path;
+ const char *interface;
+ const char *member;
+ const char *destination;
+ const char *sender;
+
+ sd_bus_error error;
+
+ sd_bus_creds creds;
+
+ usec_t monotonic;
+ usec_t realtime;
+ uint64_t seqnum;
+ uint64_t verify_destination_id;
+
+ bool sealed:1;
+ bool dont_send:1;
+ bool allow_fds:1;
+ bool free_header:1;
+ bool free_fds:1;
+ bool poisoned:1;
+ bool sensitive:1;
+
+ /* The first bytes of the message */
+ struct bus_header *header;
+
+ size_t fields_size;
+ size_t body_size;
+ size_t user_body_size;
+
+ struct bus_body_part body;
+ struct bus_body_part *body_end;
+ unsigned n_body_parts;
+
+ size_t rindex;
+ struct bus_body_part *cached_rindex_part;
+ size_t cached_rindex_part_begin;
+
+ uint32_t n_fds;
+ int *fds;
+
+ struct bus_container root_container, *containers;
+ size_t n_containers;
+
+ struct iovec *iovec;
+ struct iovec iovec_fixed[2];
+ unsigned n_iovec;
+
+ char *peeked_signature;
+
+ /* If set replies to this message must carry the signature
+ * specified here to successfully seal. This is initialized
+ * from the vtable data */
+ const char *enforced_reply_signature;
+
+ usec_t timeout;
+
+ size_t header_offsets[_BUS_MESSAGE_HEADER_MAX];
+ unsigned n_header_offsets;
+
+ uint64_t read_counter;
+};
+
+static inline bool BUS_MESSAGE_NEED_BSWAP(sd_bus_message *m) {
+ return m->header->endian != BUS_NATIVE_ENDIAN;
+}
+
+static inline uint16_t BUS_MESSAGE_BSWAP16(sd_bus_message *m, uint16_t u) {
+ return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_16(u) : u;
+}
+
+static inline uint32_t BUS_MESSAGE_BSWAP32(sd_bus_message *m, uint32_t u) {
+ return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_32(u) : u;
+}
+
+static inline uint64_t BUS_MESSAGE_BSWAP64(sd_bus_message *m, uint64_t u) {
+ return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_64(u) : u;
+}
+
+static inline uint64_t BUS_MESSAGE_COOKIE(sd_bus_message *m) {
+ return BUS_MESSAGE_BSWAP32(m, m->header->serial);
+}
+
+static inline size_t BUS_MESSAGE_SIZE(sd_bus_message *m) {
+ return
+ sizeof(struct bus_header) +
+ ALIGN8(m->fields_size) +
+ m->body_size;
+}
+
+static inline size_t BUS_MESSAGE_BODY_BEGIN(sd_bus_message *m) {
+ return
+ sizeof(struct bus_header) +
+ ALIGN8(m->fields_size);
+}
+
+static inline void* BUS_MESSAGE_FIELDS(sd_bus_message *m) {
+ return (uint8_t*) m->header + sizeof(struct bus_header);
+}
+
+int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz);
+
+int bus_message_from_malloc(
+ sd_bus *bus,
+ void *buffer,
+ size_t length,
+ int *fds,
+ size_t n_fds,
+ const char *label,
+ sd_bus_message **ret);
+
+int bus_message_get_arg(sd_bus_message *m, unsigned i, const char **str);
+int bus_message_get_arg_strv(sd_bus_message *m, unsigned i, char ***strv);
+
+#define MESSAGE_FOREACH_PART(part, i, m) \
+ for ((i) = 0, (part) = &(m)->body; (i) < (m)->n_body_parts; (i)++, (part) = (part)->next)
+
+int bus_body_part_map(struct bus_body_part *part);
+void bus_body_part_unmap(struct bus_body_part *part);
+
+int bus_message_new_synthetic_error(sd_bus *bus, uint64_t serial, const sd_bus_error *e, sd_bus_message **m);
+
+int bus_message_remarshal(sd_bus *bus, sd_bus_message **m);
+
+void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m);
+void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m);
+
+sd_bus_message* bus_message_ref_queued(sd_bus_message *m, sd_bus *bus);
+sd_bus_message* bus_message_unref_queued(sd_bus_message *m, sd_bus *bus);
diff --git a/src/libsystemd/sd-bus/bus-objects.c b/src/libsystemd/sd-bus/bus-objects.c
new file mode 100644
index 0000000..2ad7a99
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-objects.c
@@ -0,0 +1,3033 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "bus-internal.h"
+#include "bus-introspect.h"
+#include "bus-message.h"
+#include "bus-objects.h"
+#include "bus-signature.h"
+#include "bus-slot.h"
+#include "bus-type.h"
+#include "missing_capability.h"
+#include "string-util.h"
+#include "strv.h"
+
+static int node_vtable_get_userdata(
+ sd_bus *bus,
+ const char *path,
+ struct node_vtable *c,
+ void **userdata,
+ sd_bus_error *error) {
+
+ sd_bus_slot *s;
+ void *u, *found_u = NULL;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(c);
+
+ s = container_of(c, sd_bus_slot, node_vtable);
+ u = s->userdata;
+ if (c->find) {
+ bus->current_slot = sd_bus_slot_ref(s);
+ bus->current_userdata = u;
+ r = c->find(bus, path, c->interface, u, &found_u, error);
+ bus->current_userdata = NULL;
+ bus->current_slot = sd_bus_slot_unref(s);
+
+ if (r < 0)
+ return r;
+ if (sd_bus_error_is_set(error))
+ return -sd_bus_error_get_errno(error);
+ if (r == 0)
+ return r;
+ } else
+ found_u = u;
+
+ if (userdata)
+ *userdata = found_u;
+
+ return 1;
+}
+
+static void *vtable_method_convert_userdata(const sd_bus_vtable *p, void *u) {
+ assert(p);
+
+ if (!u || FLAGS_SET(p->flags, SD_BUS_VTABLE_ABSOLUTE_OFFSET))
+ return SIZE_TO_PTR(p->x.method.offset); /* don't add offset on NULL, to make ubsan happy */
+
+ return (uint8_t*) u + p->x.method.offset;
+}
+
+static void *vtable_property_convert_userdata(const sd_bus_vtable *p, void *u) {
+ assert(p);
+
+ if (!u || FLAGS_SET(p->flags, SD_BUS_VTABLE_ABSOLUTE_OFFSET))
+ return SIZE_TO_PTR(p->x.property.offset); /* as above */
+
+ return (uint8_t*) u + p->x.property.offset;
+}
+
+static int vtable_property_get_userdata(
+ sd_bus *bus,
+ const char *path,
+ struct vtable_member *p,
+ void **userdata,
+ sd_bus_error *error) {
+
+ void *u;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(p);
+ assert(userdata);
+
+ r = node_vtable_get_userdata(bus, path, p->parent, &u, error);
+ if (r <= 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+
+ *userdata = vtable_property_convert_userdata(p->vtable, u);
+ return 1;
+}
+
+static int add_enumerated_to_set(
+ sd_bus *bus,
+ const char *prefix,
+ struct node_enumerator *first,
+ OrderedSet *s,
+ sd_bus_error *error) {
+
+ int r;
+
+ assert(bus);
+ assert(prefix);
+ assert(s);
+
+ LIST_FOREACH(enumerators, c, first) {
+ char **children = NULL;
+ sd_bus_slot *slot;
+
+ if (bus->nodes_modified)
+ return 0;
+
+ slot = container_of(c, sd_bus_slot, node_enumerator);
+
+ bus->current_slot = sd_bus_slot_ref(slot);
+ bus->current_userdata = slot->userdata;
+ r = c->callback(bus, prefix, slot->userdata, &children, error);
+ bus->current_userdata = NULL;
+ bus->current_slot = sd_bus_slot_unref(slot);
+
+ if (r < 0)
+ return r;
+ if (sd_bus_error_is_set(error))
+ return -sd_bus_error_get_errno(error);
+
+ STRV_FOREACH(k, children) {
+ if (r < 0) {
+ free(*k);
+ continue;
+ }
+
+ if (!object_path_is_valid(*k)) {
+ free(*k);
+ r = -EINVAL;
+ continue;
+ }
+
+ if (!object_path_startswith(*k, prefix)) {
+ free(*k);
+ continue;
+ }
+
+ r = ordered_set_consume(s, *k);
+ if (r == -EEXIST)
+ r = 0;
+ }
+
+ free(children);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+enum {
+ /* if set, add_subtree() works recursively */
+ CHILDREN_RECURSIVE = 1 << 0,
+ /* if set, add_subtree() scans object-manager hierarchies recursively */
+ CHILDREN_SUBHIERARCHIES = 1 << 1,
+};
+
+static int add_subtree_to_set(
+ sd_bus *bus,
+ const char *prefix,
+ struct node *n,
+ unsigned flags,
+ OrderedSet *s,
+ sd_bus_error *error) {
+
+ int r;
+
+ assert(bus);
+ assert(prefix);
+ assert(n);
+ assert(s);
+
+ r = add_enumerated_to_set(bus, prefix, n->enumerators, s, error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+
+ LIST_FOREACH(siblings, i, n->child) {
+ char *t;
+
+ if (!object_path_startswith(i->path, prefix))
+ continue;
+
+ t = strdup(i->path);
+ if (!t)
+ return -ENOMEM;
+
+ r = ordered_set_consume(s, t);
+ if (r < 0 && r != -EEXIST)
+ return r;
+
+ if ((flags & CHILDREN_RECURSIVE) &&
+ ((flags & CHILDREN_SUBHIERARCHIES) || !i->object_managers)) {
+ r = add_subtree_to_set(bus, prefix, i, flags, s, error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int get_child_nodes(
+ sd_bus *bus,
+ const char *prefix,
+ struct node *n,
+ unsigned flags,
+ OrderedSet **ret,
+ sd_bus_error *error) {
+
+ _cleanup_ordered_set_free_free_ OrderedSet *s = NULL;
+ int r;
+
+ assert(bus);
+ assert(prefix);
+ assert(n);
+ assert(ret);
+
+ s = ordered_set_new(&string_hash_ops);
+ if (!s)
+ return -ENOMEM;
+
+ r = add_subtree_to_set(bus, prefix, n, flags, s, error);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(s);
+ return 0;
+}
+
+static int node_callbacks_run(
+ sd_bus *bus,
+ sd_bus_message *m,
+ struct node_callback *first,
+ bool require_fallback,
+ bool *found_object) {
+
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(found_object);
+
+ LIST_FOREACH(callbacks, c, first) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
+ sd_bus_slot *slot;
+
+ if (bus->nodes_modified)
+ return 0;
+
+ if (require_fallback && !c->is_fallback)
+ continue;
+
+ *found_object = true;
+
+ if (c->last_iteration == bus->iteration_counter)
+ continue;
+
+ c->last_iteration = bus->iteration_counter;
+
+ r = sd_bus_message_rewind(m, true);
+ if (r < 0)
+ return r;
+
+ slot = container_of(c, sd_bus_slot, node_callback);
+
+ bus->current_slot = sd_bus_slot_ref(slot);
+ bus->current_handler = c->callback;
+ bus->current_userdata = slot->userdata;
+ r = c->callback(m, slot->userdata, &error_buffer);
+ bus->current_userdata = NULL;
+ bus->current_handler = NULL;
+ bus->current_slot = sd_bus_slot_unref(slot);
+
+ r = bus_maybe_reply_error(m, r, &error_buffer);
+ if (r != 0)
+ return r;
+ }
+
+ return 0;
+}
+
+#define CAPABILITY_SHIFT(x) (((x) >> __builtin_ctzll(_SD_BUS_VTABLE_CAPABILITY_MASK)) & 0xFFFF)
+
+static int check_access(sd_bus *bus, sd_bus_message *m, struct vtable_member *c, sd_bus_error *error) {
+ uint64_t cap;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(c);
+
+ /* If the entire bus is trusted let's grant access */
+ if (bus->trusted)
+ return 0;
+
+ /* If the member is marked UNPRIVILEGED let's grant access */
+ if (c->vtable->flags & SD_BUS_VTABLE_UNPRIVILEGED)
+ return 0;
+
+ /* Check that the caller has the requested capability set. Note that the flags value contains the
+ * capability number plus one, which we need to subtract here. We do this so that we have 0 as
+ * special value for the default. */
+ cap = CAPABILITY_SHIFT(c->vtable->flags);
+ if (cap == 0)
+ cap = CAPABILITY_SHIFT(c->parent->vtable[0].flags);
+ if (cap == 0)
+ cap = CAP_SYS_ADMIN;
+ else
+ cap--;
+
+ r = sd_bus_query_sender_privilege(m, cap);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 0;
+
+ return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Access to %s.%s() not permitted.", c->interface, c->member);
+}
+
+static int method_callbacks_run(
+ sd_bus *bus,
+ sd_bus_message *m,
+ struct vtable_member *c,
+ bool require_fallback,
+ bool *found_object) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ const char *signature;
+ void *u;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(c);
+ assert(found_object);
+
+ if (require_fallback && !c->parent->is_fallback)
+ return 0;
+
+ if (FLAGS_SET(c->vtable->flags, SD_BUS_VTABLE_SENSITIVE)) {
+ r = sd_bus_message_sensitive(m);
+ if (r < 0)
+ return r;
+ }
+
+ r = check_access(bus, m, c, &error);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, &error);
+
+ r = node_vtable_get_userdata(bus, m->path, c->parent, &u, &error);
+ if (r <= 0)
+ return bus_maybe_reply_error(m, r, &error);
+ if (bus->nodes_modified)
+ return 0;
+
+ u = vtable_method_convert_userdata(c->vtable, u);
+
+ *found_object = true;
+
+ if (c->last_iteration == bus->iteration_counter)
+ return 0;
+
+ c->last_iteration = bus->iteration_counter;
+
+ r = sd_bus_message_rewind(m, true);
+ if (r < 0)
+ return r;
+
+ signature = sd_bus_message_get_signature(m, true);
+ if (!signature)
+ return -EINVAL;
+
+ if (!streq(strempty(c->vtable->x.method.signature), signature))
+ return sd_bus_reply_method_errorf(
+ m,
+ SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid arguments '%s' to call %s.%s(), expecting '%s'.",
+ signature, c->interface, c->member, strempty(c->vtable->x.method.signature));
+
+ /* Keep track what the signature of the reply to this message
+ * should be, so that this can be enforced when sealing the
+ * reply. */
+ m->enforced_reply_signature = strempty(c->vtable->x.method.result);
+
+ if (c->vtable->x.method.handler) {
+ sd_bus_slot *slot;
+
+ slot = container_of(c->parent, sd_bus_slot, node_vtable);
+
+ bus->current_slot = sd_bus_slot_ref(slot);
+ bus->current_handler = c->vtable->x.method.handler;
+ bus->current_userdata = u;
+ r = c->vtable->x.method.handler(m, u, &error);
+ bus->current_userdata = NULL;
+ bus->current_handler = NULL;
+ bus->current_slot = sd_bus_slot_unref(slot);
+
+ return bus_maybe_reply_error(m, r, &error);
+ }
+
+ /* If the method callback is NULL, make this a successful NOP */
+ r = sd_bus_reply_method_return(m, NULL);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int invoke_property_get(
+ sd_bus *bus,
+ sd_bus_slot *slot,
+ const sd_bus_vtable *v,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ const void *p;
+ int r;
+
+ assert(bus);
+ assert(slot);
+ assert(v);
+ assert(path);
+ assert(interface);
+ assert(property);
+ assert(reply);
+
+ if (v->x.property.get) {
+
+ bus->current_slot = sd_bus_slot_ref(slot);
+ bus->current_userdata = userdata;
+ r = v->x.property.get(bus, path, interface, property, reply, userdata, error);
+ bus->current_userdata = NULL;
+ bus->current_slot = sd_bus_slot_unref(slot);
+
+ if (r < 0)
+ return r;
+ if (sd_bus_error_is_set(error))
+ return -sd_bus_error_get_errno(error);
+ return r;
+ }
+
+ /* Automatic handling if no callback is defined. */
+
+ if (streq(v->x.property.signature, "as"))
+ return sd_bus_message_append_strv(reply, *(char***) userdata);
+
+ assert(signature_is_single(v->x.property.signature, false));
+ assert(bus_type_is_basic(v->x.property.signature[0]));
+
+ switch (v->x.property.signature[0]) {
+
+ case SD_BUS_TYPE_STRING:
+ case SD_BUS_TYPE_SIGNATURE:
+ p = strempty(*(char**) userdata);
+ break;
+
+ case SD_BUS_TYPE_OBJECT_PATH:
+ p = *(char**) userdata;
+ assert(p);
+ break;
+
+ default:
+ p = userdata;
+ break;
+ }
+
+ return sd_bus_message_append_basic(reply, v->x.property.signature[0], p);
+}
+
+static int invoke_property_set(
+ sd_bus *bus,
+ sd_bus_slot *slot,
+ const sd_bus_vtable *v,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *value,
+ void *userdata,
+ sd_bus_error *error) {
+
+ int r;
+
+ assert(bus);
+ assert(slot);
+ assert(v);
+ assert(path);
+ assert(interface);
+ assert(property);
+ assert(value);
+
+ if (v->x.property.set) {
+
+ bus->current_slot = sd_bus_slot_ref(slot);
+ bus->current_userdata = userdata;
+ r = v->x.property.set(bus, path, interface, property, value, userdata, error);
+ bus->current_userdata = NULL;
+ bus->current_slot = sd_bus_slot_unref(slot);
+
+ if (r < 0)
+ return r;
+ if (sd_bus_error_is_set(error))
+ return -sd_bus_error_get_errno(error);
+ return r;
+ }
+
+ /* Automatic handling if no callback is defined. */
+
+ assert(signature_is_single(v->x.property.signature, false));
+ assert(bus_type_is_basic(v->x.property.signature[0]));
+
+ switch (v->x.property.signature[0]) {
+
+ case SD_BUS_TYPE_STRING:
+ case SD_BUS_TYPE_OBJECT_PATH:
+ case SD_BUS_TYPE_SIGNATURE: {
+ const char *p;
+ char *n;
+
+ r = sd_bus_message_read_basic(value, v->x.property.signature[0], &p);
+ if (r < 0)
+ return r;
+
+ n = strdup(p);
+ if (!n)
+ return -ENOMEM;
+
+ free(*(char**) userdata);
+ *(char**) userdata = n;
+
+ break;
+ }
+
+ default:
+ r = sd_bus_message_read_basic(value, v->x.property.signature[0], userdata);
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ return 1;
+}
+
+static int property_get_set_callbacks_run(
+ sd_bus *bus,
+ sd_bus_message *m,
+ struct vtable_member *c,
+ bool require_fallback,
+ bool is_get,
+ bool *found_object) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ sd_bus_slot *slot;
+ void *u = NULL;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(c);
+ assert(found_object);
+
+ if (require_fallback && !c->parent->is_fallback)
+ return 0;
+
+ if (FLAGS_SET(c->vtable->flags, SD_BUS_VTABLE_SENSITIVE)) {
+ r = sd_bus_message_sensitive(m);
+ if (r < 0)
+ return r;
+ }
+
+ r = vtable_property_get_userdata(bus, m->path, c, &u, &error);
+ if (r <= 0)
+ return bus_maybe_reply_error(m, r, &error);
+ if (bus->nodes_modified)
+ return 0;
+
+ slot = container_of(c->parent, sd_bus_slot, node_vtable);
+
+ *found_object = true;
+
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(c->vtable->flags, SD_BUS_VTABLE_SENSITIVE)) {
+ r = sd_bus_message_sensitive(reply);
+ if (r < 0)
+ return r;
+ }
+
+ if (is_get) {
+ /* Note that we do not protect against reexecution
+ * here (using the last_iteration check, see below),
+ * should the node tree have changed and we got called
+ * again. We assume that property Get() calls are
+ * ultimately without side-effects or if they aren't
+ * then at least idempotent. */
+
+ r = sd_bus_message_open_container(reply, 'v', c->vtable->x.property.signature);
+ if (r < 0)
+ return r;
+
+ /* Note that we do not do an access check here. Read
+ * access to properties is always unrestricted, since
+ * PropertiesChanged signals broadcast contents
+ * anyway. */
+
+ r = invoke_property_get(bus, slot, c->vtable, m->path, c->interface, c->member, reply, u, &error);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, &error);
+
+ if (bus->nodes_modified)
+ return 0;
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ } else {
+ const char *signature = NULL;
+ char type = 0;
+
+ if (c->vtable->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY)
+ return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Property '%s' is not writable.", c->member);
+
+ /* Avoid that we call the set routine more than once
+ * if the processing of this message got restarted
+ * because the node tree changed. */
+ if (c->last_iteration == bus->iteration_counter)
+ return 0;
+
+ c->last_iteration = bus->iteration_counter;
+
+ r = sd_bus_message_peek_type(m, &type, &signature);
+ if (r < 0)
+ return r;
+
+ if (type != 'v')
+ return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_SIGNATURE,
+ "Incorrect signature when setting property '%s', expected 'v', got '%c'.",
+ c->member, type);
+ if (!streq(strempty(signature), strempty(c->vtable->x.property.signature)))
+ return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS,
+ "Incorrect parameters for property '%s', expected '%s', got '%s'.",
+ c->member, strempty(c->vtable->x.property.signature), strempty(signature));
+
+ r = sd_bus_message_enter_container(m, 'v', c->vtable->x.property.signature);
+ if (r < 0)
+ return r;
+
+ r = check_access(bus, m, c, &error);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, &error);
+
+ r = invoke_property_set(bus, slot, c->vtable, m->path, c->interface, c->member, m, u, &error);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, &error);
+
+ if (bus->nodes_modified)
+ return 0;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_send(bus, reply, NULL);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int vtable_append_one_property(
+ sd_bus *bus,
+ sd_bus_message *reply,
+ const char *path,
+ struct node_vtable *c,
+ const sd_bus_vtable *v,
+ void *userdata,
+ sd_bus_error *error) {
+
+ sd_bus_slot *slot;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(path);
+ assert(c);
+ assert(v);
+
+ if (FLAGS_SET(c->vtable->flags, SD_BUS_VTABLE_SENSITIVE)) {
+ r = sd_bus_message_sensitive(reply);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_open_container(reply, 'e', "sv");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "s", v->x.property.member);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'v', v->x.property.signature);
+ if (r < 0)
+ return r;
+
+ slot = container_of(c, sd_bus_slot, node_vtable);
+
+ r = invoke_property_get(bus, slot, v, path, c->interface, v->x.property.member, reply, vtable_property_convert_userdata(v, userdata), error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int vtable_append_all_properties(
+ sd_bus *bus,
+ sd_bus_message *reply,
+ const char *path,
+ struct node_vtable *c,
+ void *userdata,
+ sd_bus_error *error) {
+
+ const sd_bus_vtable *v;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(path);
+ assert(c);
+
+ if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN)
+ return 1;
+
+ v = c->vtable;
+ for (v = bus_vtable_next(c->vtable, v); v->type != _SD_BUS_VTABLE_END; v = bus_vtable_next(c->vtable, v)) {
+ if (!IN_SET(v->type, _SD_BUS_VTABLE_PROPERTY, _SD_BUS_VTABLE_WRITABLE_PROPERTY))
+ continue;
+
+ if (v->flags & SD_BUS_VTABLE_HIDDEN)
+ continue;
+
+ /* Let's not include properties marked as "explicit" in any message that contains a generic
+ * dump of properties, but only in those generated as a response to an explicit request. */
+ if (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT)
+ continue;
+
+ /* Let's not include properties marked only for invalidation on change (i.e. in contrast to
+ * those whose new values are included in PropertiesChanges message) in any signals. This is
+ * useful to ensure they aren't included in InterfacesAdded messages. */
+ if (reply->header->type != SD_BUS_MESSAGE_METHOD_RETURN &&
+ FLAGS_SET(v->flags, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION))
+ continue;
+
+ r = vtable_append_one_property(bus, reply, path, c, v, userdata, error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+
+ return 1;
+}
+
+static int property_get_all_callbacks_run(
+ sd_bus *bus,
+ sd_bus_message *m,
+ struct node_vtable *first,
+ bool require_fallback,
+ const char *iface,
+ bool *found_object) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ bool found_interface;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(found_object);
+
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "{sv}");
+ if (r < 0)
+ return r;
+
+ found_interface = !iface || STR_IN_SET(iface,
+ "org.freedesktop.DBus.Properties",
+ "org.freedesktop.DBus.Peer",
+ "org.freedesktop.DBus.Introspectable");
+
+ LIST_FOREACH(vtables, c, first) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ void *u;
+
+ if (require_fallback && !c->is_fallback)
+ continue;
+
+ r = node_vtable_get_userdata(bus, m->path, c, &u, &error);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, &error);
+ if (bus->nodes_modified)
+ return 0;
+ if (r == 0)
+ continue;
+
+ *found_object = true;
+
+ if (iface && !streq(c->interface, iface))
+ continue;
+ found_interface = true;
+
+ r = vtable_append_all_properties(bus, reply, m->path, c, u, &error);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, &error);
+ if (bus->nodes_modified)
+ return 0;
+ }
+
+ if (!*found_object)
+ return 0;
+
+ if (!found_interface) {
+ r = sd_bus_reply_method_errorf(
+ m,
+ SD_BUS_ERROR_UNKNOWN_INTERFACE,
+ "Unknown interface '%s'.", iface);
+ if (r < 0)
+ return r;
+
+ return 1;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_send(bus, reply, NULL);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int bus_node_exists(
+ sd_bus *bus,
+ struct node *n,
+ const char *path,
+ bool require_fallback) {
+
+ int r;
+
+ assert(bus);
+ assert(n);
+ assert(path);
+
+ /* Tests if there's anything attached directly to this node
+ * for the specified path */
+
+ if (!require_fallback && (n->enumerators || n->object_managers))
+ return true;
+
+ LIST_FOREACH(callbacks, k, n->callbacks) {
+ if (require_fallback && !k->is_fallback)
+ continue;
+
+ return 1;
+ }
+
+ LIST_FOREACH(vtables, c, n->vtables) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ if (require_fallback && !c->is_fallback)
+ continue;
+
+ r = node_vtable_get_userdata(bus, path, c, NULL, &error);
+ if (r != 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+
+ return 0;
+}
+
+int introspect_path(
+ sd_bus *bus,
+ const char *path,
+ struct node *n,
+ bool require_fallback,
+ bool ignore_nodes_modified,
+ bool *found_object,
+ char **ret,
+ sd_bus_error *error) {
+
+ _cleanup_ordered_set_free_free_ OrderedSet *s = NULL;
+ _cleanup_(introspect_free) struct introspect intro = {};
+ bool empty;
+ int r;
+
+ if (!n) {
+ n = hashmap_get(bus->nodes, path);
+ if (!n)
+ return -ENOENT;
+ }
+
+ r = get_child_nodes(bus, path, n, 0, &s, error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified && !ignore_nodes_modified)
+ return 0;
+
+ r = introspect_begin(&intro, bus->trusted);
+ if (r < 0)
+ return r;
+
+ r = introspect_write_default_interfaces(&intro, !require_fallback && n->object_managers);
+ if (r < 0)
+ return r;
+
+ empty = ordered_set_isempty(s);
+
+ LIST_FOREACH(vtables, c, n->vtables) {
+ if (require_fallback && !c->is_fallback)
+ continue;
+
+ r = node_vtable_get_userdata(bus, path, c, NULL, error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified && !ignore_nodes_modified)
+ return 0;
+ if (r == 0)
+ continue;
+
+ empty = false;
+
+ if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN)
+ continue;
+
+ r = introspect_write_interface(&intro, c->interface, c->vtable);
+ if (r < 0)
+ return r;
+ }
+
+ if (empty) {
+ /* Nothing?, let's see if we exist at all, and if not
+ * refuse to do anything */
+ r = bus_node_exists(bus, n, path, require_fallback);
+ if (r <= 0)
+ return r;
+ if (bus->nodes_modified && !ignore_nodes_modified)
+ return 0;
+ }
+
+ if (found_object)
+ *found_object = true;
+
+ r = introspect_write_child_nodes(&intro, s, path);
+ if (r < 0)
+ return r;
+
+ r = introspect_finish(&intro, ret);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int process_introspect(
+ sd_bus *bus,
+ sd_bus_message *m,
+ struct node *n,
+ bool require_fallback,
+ bool *found_object) {
+
+ _cleanup_free_ char *s = NULL;
+ _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;
+
+ assert(bus);
+ assert(m);
+ assert(n);
+ assert(found_object);
+
+ r = introspect_path(bus, m->path, n, require_fallback, false, found_object, &s, &error);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, &error);
+ if (r == 0)
+ /* nodes_modified == true */
+ return 0;
+
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "s", s);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_send(bus, reply, NULL);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int object_manager_serialize_path(
+ sd_bus *bus,
+ sd_bus_message *reply,
+ const char *prefix,
+ const char *path,
+ bool require_fallback,
+ bool *found_object_manager,
+ sd_bus_error *error) {
+
+ const char *previous_interface = NULL;
+ bool found_something = false;
+ struct node *n;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(prefix);
+ assert(path);
+ assert(found_object_manager);
+ assert(error);
+
+ n = hashmap_get(bus->nodes, prefix);
+ if (!n)
+ return 0;
+
+ if (!require_fallback && n->object_managers)
+ *found_object_manager = true;
+
+ LIST_FOREACH(vtables, i, n->vtables) {
+ void *u;
+
+ if (require_fallback && !i->is_fallback)
+ continue;
+
+ r = node_vtable_get_userdata(bus, path, i, &u, error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ if (r == 0)
+ continue;
+
+ if (!found_something) {
+
+ /* Open the object part */
+
+ r = sd_bus_message_open_container(reply, 'e', "oa{sa{sv}}");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "o", path);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "{sa{sv}}");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "{sa{sv}}", "org.freedesktop.DBus.Peer", 0);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "{sa{sv}}", "org.freedesktop.DBus.Introspectable", 0);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "{sa{sv}}", "org.freedesktop.DBus.Properties", 0);
+ if (r < 0)
+ return r;
+
+ if (*found_object_manager) {
+ r = sd_bus_message_append(
+ reply, "{sa{sv}}", "org.freedesktop.DBus.ObjectManager", 0);
+ if (r < 0)
+ return r;
+ }
+
+ found_something = true;
+ }
+
+ if (!streq_ptr(previous_interface, i->interface)) {
+
+ /* Maybe close the previous interface part */
+
+ if (previous_interface) {
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+ }
+
+ /* Open the new interface part */
+
+ r = sd_bus_message_open_container(reply, 'e', "sa{sv}");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "s", i->interface);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "{sv}");
+ if (r < 0)
+ return r;
+ }
+
+ r = vtable_append_all_properties(bus, reply, path, i, u, error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+
+ previous_interface = i->interface;
+ }
+
+ if (previous_interface) {
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+ }
+
+ if (found_something) {
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+ }
+
+ return 1;
+}
+
+static int object_manager_serialize_path_and_fallbacks(
+ sd_bus *bus,
+ sd_bus_message *reply,
+ const char *path,
+ sd_bus_error *error) {
+
+ _cleanup_free_ char *prefix = NULL;
+ size_t pl;
+ int r;
+ bool found_object_manager = false;
+
+ assert(bus);
+ assert(reply);
+ assert(path);
+ assert(error);
+
+ /* First, add all vtables registered for this path */
+ r = object_manager_serialize_path(bus, reply, path, path, false, &found_object_manager, error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+
+ /* Second, add fallback vtables registered for any of the prefixes */
+ pl = strlen(path);
+ assert(pl <= BUS_PATH_SIZE_MAX);
+ prefix = new(char, pl + 1);
+ if (!prefix)
+ return -ENOMEM;
+
+ OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
+ r = object_manager_serialize_path(bus, reply, prefix, path, true, &found_object_manager, error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+
+ return 0;
+}
+
+static int process_get_managed_objects(
+ sd_bus *bus,
+ sd_bus_message *m,
+ struct node *n,
+ bool require_fallback,
+ bool *found_object) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_ordered_set_free_free_ OrderedSet *s = NULL;
+ char *path;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(n);
+ assert(found_object);
+
+ /* Spec says, GetManagedObjects() is only implemented on the root of a
+ * sub-tree. Therefore, we require a registered object-manager on
+ * exactly the queried path, otherwise, we refuse to respond. */
+
+ if (require_fallback || !n->object_managers)
+ return 0;
+
+ r = get_child_nodes(bus, m->path, n, CHILDREN_RECURSIVE, &s, &error);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, &error);
+ if (bus->nodes_modified)
+ return 0;
+
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "{oa{sa{sv}}}");
+ if (r < 0)
+ return r;
+
+ ORDERED_SET_FOREACH(path, s) {
+ r = object_manager_serialize_path_and_fallbacks(bus, reply, path, &error);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, &error);
+
+ if (bus->nodes_modified)
+ return 0;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_send(bus, reply, NULL);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int object_find_and_run(
+ sd_bus *bus,
+ sd_bus_message *m,
+ const char *p,
+ bool require_fallback,
+ bool *found_object) {
+
+ struct node *n;
+ struct vtable_member vtable_key, *v;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(p);
+ assert(found_object);
+
+ n = hashmap_get(bus->nodes, p);
+ if (!n)
+ return 0;
+
+ /* First, try object callbacks */
+ r = node_callbacks_run(bus, m, n->callbacks, require_fallback, found_object);
+ if (r != 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+
+ if (!m->interface || !m->member)
+ return 0;
+
+ /* Then, look for a known method */
+ vtable_key.path = (char*) p;
+ vtable_key.interface = m->interface;
+ vtable_key.member = m->member;
+
+ v = hashmap_get(bus->vtable_methods, &vtable_key);
+ if (v) {
+ r = method_callbacks_run(bus, m, v, require_fallback, found_object);
+ if (r != 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+
+ /* Then, look for a known property */
+ if (streq(m->interface, "org.freedesktop.DBus.Properties")) {
+ bool get = false;
+
+ get = streq(m->member, "Get");
+
+ if (get || streq(m->member, "Set")) {
+
+ r = sd_bus_message_rewind(m, true);
+ if (r < 0)
+ return r;
+
+ vtable_key.path = (char*) p;
+
+ r = sd_bus_message_read(m, "ss", &vtable_key.interface, &vtable_key.member);
+ if (r < 0)
+ return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface and member parameters");
+
+ v = hashmap_get(bus->vtable_properties, &vtable_key);
+ if (v) {
+ r = property_get_set_callbacks_run(bus, m, v, require_fallback, get, found_object);
+ if (r != 0)
+ return r;
+ }
+
+ } else if (streq(m->member, "GetAll")) {
+ const char *iface;
+
+ r = sd_bus_message_rewind(m, true);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(m, "s", &iface);
+ if (r < 0)
+ return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface parameter");
+
+ if (iface[0] == 0)
+ iface = NULL;
+
+ r = property_get_all_callbacks_run(bus, m, n->vtables, require_fallback, iface, found_object);
+ if (r != 0)
+ return r;
+ }
+
+ } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+
+ if (!isempty(sd_bus_message_get_signature(m, true)))
+ return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters");
+
+ r = process_introspect(bus, m, n, require_fallback, found_object);
+ if (r != 0)
+ return r;
+
+ } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) {
+
+ if (!isempty(sd_bus_message_get_signature(m, true)))
+ return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters");
+
+ r = process_get_managed_objects(bus, m, n, require_fallback, found_object);
+ if (r != 0)
+ return r;
+ }
+
+ if (bus->nodes_modified)
+ return 0;
+
+ if (!*found_object) {
+ r = bus_node_exists(bus, n, m->path, require_fallback);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, NULL);
+ if (bus->nodes_modified)
+ return 0;
+ if (r > 0)
+ *found_object = true;
+ }
+
+ return 0;
+}
+
+int bus_process_object(sd_bus *bus, sd_bus_message *m) {
+ _cleanup_free_ char *prefix = NULL;
+ int r;
+ size_t pl;
+ bool found_object = false;
+
+ assert(bus);
+ assert(m);
+
+ if (bus->is_monitor)
+ return 0;
+
+ if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL)
+ return 0;
+
+ if (hashmap_isempty(bus->nodes))
+ return 0;
+
+ /* Never respond to broadcast messages */
+ if (bus->bus_client && !m->destination)
+ return 0;
+
+ assert(m->path);
+ assert(m->member);
+
+ pl = strlen(m->path);
+ assert(pl <= BUS_PATH_SIZE_MAX);
+ prefix = new(char, pl + 1);
+ if (!prefix)
+ return -ENOMEM;
+
+ do {
+ bus->nodes_modified = false;
+
+ r = object_find_and_run(bus, m, m->path, false, &found_object);
+ if (r != 0)
+ return r;
+
+ /* Look for fallback prefixes */
+ OBJECT_PATH_FOREACH_PREFIX(prefix, m->path) {
+
+ if (bus->nodes_modified)
+ break;
+
+ r = object_find_and_run(bus, m, prefix, true, &found_object);
+ if (r != 0)
+ return r;
+ }
+
+ } while (bus->nodes_modified);
+
+ if (!found_object)
+ return 0;
+
+ if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get") ||
+ sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Set")) {
+ const char *interface = NULL, *property = NULL;
+
+ (void) sd_bus_message_rewind(m, true);
+ (void) sd_bus_message_read_basic(m, 's', &interface);
+ (void) sd_bus_message_read_basic(m, 's', &property);
+
+ r = sd_bus_reply_method_errorf(
+ m,
+ SD_BUS_ERROR_UNKNOWN_PROPERTY,
+ "Unknown interface %s or property %s.", strnull(interface), strnull(property));
+ } else
+ r = sd_bus_reply_method_errorf(
+ m,
+ SD_BUS_ERROR_UNKNOWN_METHOD,
+ "Unknown method %s or interface %s.", m->member, m->interface);
+
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static struct node* bus_node_allocate(sd_bus *bus, const char *path) {
+ struct node *n, *parent;
+ const char *e;
+ _cleanup_free_ char *s = NULL;
+ char *p;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(path[0] == '/');
+
+ n = hashmap_get(bus->nodes, path);
+ if (n)
+ return n;
+
+ r = hashmap_ensure_allocated(&bus->nodes, &string_hash_ops);
+ if (r < 0)
+ return NULL;
+
+ s = strdup(path);
+ if (!s)
+ return NULL;
+
+ if (streq(path, "/"))
+ parent = NULL;
+ else {
+ assert_se(e = strrchr(path, '/'));
+
+ p = strndupa_safe(path, MAX(1, e - path));
+
+ parent = bus_node_allocate(bus, p);
+ if (!parent)
+ return NULL;
+ }
+
+ n = new0(struct node, 1);
+ if (!n)
+ return NULL;
+
+ n->parent = parent;
+ n->path = TAKE_PTR(s);
+
+ r = hashmap_put(bus->nodes, n->path, n);
+ if (r < 0) {
+ free(n->path);
+ return mfree(n);
+ }
+
+ if (parent)
+ LIST_PREPEND(siblings, parent->child, n);
+
+ return n;
+}
+
+void bus_node_gc(sd_bus *b, struct node *n) {
+ assert(b);
+
+ if (!n)
+ return;
+
+ if (n->child ||
+ n->callbacks ||
+ n->vtables ||
+ n->enumerators ||
+ n->object_managers)
+ return;
+
+ assert_se(hashmap_remove(b->nodes, n->path) == n);
+
+ if (n->parent)
+ LIST_REMOVE(siblings, n->parent->child, n);
+
+ free(n->path);
+ bus_node_gc(b, n->parent);
+ free(n);
+}
+
+static int bus_find_parent_object_manager(sd_bus *bus, struct node **out, const char *path, bool* path_has_object_manager) {
+ struct node *n;
+
+ assert(bus);
+ assert(path);
+ assert(path_has_object_manager);
+
+ n = hashmap_get(bus->nodes, path);
+
+ if (n)
+ *path_has_object_manager = n->object_managers;
+
+ if (!n) {
+ _cleanup_free_ char *prefix = NULL;
+ size_t pl;
+
+ pl = strlen(path);
+ assert(pl <= BUS_PATH_SIZE_MAX);
+ prefix = new(char, pl + 1);
+ if (!prefix)
+ return -ENOMEM;
+
+ OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
+ n = hashmap_get(bus->nodes, prefix);
+ if (n)
+ break;
+ }
+ }
+
+ while (n && !n->object_managers)
+ n = n->parent;
+
+ if (out)
+ *out = n;
+ return !!n;
+}
+
+static int bus_add_object(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ bool fallback,
+ const char *path,
+ sd_bus_message_handler_t callback,
+ void *userdata) {
+
+ sd_bus_slot *s;
+ struct node *n;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(callback, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ n = bus_node_allocate(bus, path);
+ if (!n)
+ return -ENOMEM;
+
+ s = bus_slot_allocate(bus, !slot, BUS_NODE_CALLBACK, sizeof(struct node_callback), userdata);
+ if (!s) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ s->node_callback.callback = callback;
+ s->node_callback.is_fallback = fallback;
+
+ s->node_callback.node = n;
+ LIST_PREPEND(callbacks, n->callbacks, &s->node_callback);
+ bus->nodes_modified = true;
+
+ if (slot)
+ *slot = s;
+
+ return 0;
+
+fail:
+ sd_bus_slot_unref(s);
+ bus_node_gc(bus, n);
+
+ return r;
+}
+
+_public_ int sd_bus_add_object(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ const char *path,
+ sd_bus_message_handler_t callback,
+ void *userdata) {
+
+ return bus_add_object(bus, slot, false, path, callback, userdata);
+}
+
+_public_ int sd_bus_add_fallback(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ const char *prefix,
+ sd_bus_message_handler_t callback,
+ void *userdata) {
+
+ return bus_add_object(bus, slot, true, prefix, callback, userdata);
+}
+
+static void vtable_member_hash_func(const struct vtable_member *m, struct siphash *state) {
+ assert(m);
+
+ string_hash_func(m->path, state);
+ string_hash_func(m->interface, state);
+ string_hash_func(m->member, state);
+}
+
+static int vtable_member_compare_func(const struct vtable_member *x, const struct vtable_member *y) {
+ int r;
+
+ assert(x);
+ assert(y);
+
+ r = strcmp(x->path, y->path);
+ if (r != 0)
+ return r;
+
+ r = strcmp(x->interface, y->interface);
+ if (r != 0)
+ return r;
+
+ return strcmp(x->member, y->member);
+}
+
+DEFINE_PRIVATE_HASH_OPS(vtable_member_hash_ops, struct vtable_member, vtable_member_hash_func, vtable_member_compare_func);
+
+typedef enum {
+ NAMES_FIRST_PART = 1 << 0, /* first part of argument name list (input names). It is reset by names_are_valid() */
+ NAMES_PRESENT = 1 << 1, /* at least one argument name is present, so the names will checked.
+ This flag is set and used internally by names_are_valid(), but needs to be stored across calls for 2-parts list */
+ NAMES_SINGLE_PART = 1 << 2, /* argument name list consisting of a single part */
+} names_flags;
+
+static bool names_are_valid(const char *signature, const char **names, names_flags *flags) {
+ int r;
+
+ if ((*flags & NAMES_FIRST_PART || *flags & NAMES_SINGLE_PART) && **names != '\0')
+ *flags |= NAMES_PRESENT;
+
+ for (;*flags & NAMES_PRESENT;) {
+ size_t l;
+
+ if (!*signature)
+ break;
+
+ r = signature_element_length(signature, &l);
+ if (r < 0)
+ return false;
+
+ if (**names != '\0') {
+ if (!member_name_is_valid(*names))
+ return false;
+ *names += strlen(*names) + 1;
+ } else if (*flags & NAMES_PRESENT)
+ return false;
+
+ signature += l;
+ }
+ /* let's check if there are more argument names specified than the signature allows */
+ if (*flags & NAMES_PRESENT && **names != '\0' && !(*flags & NAMES_FIRST_PART))
+ return false;
+ *flags &= ~NAMES_FIRST_PART;
+ return true;
+}
+
+/* the current version of this struct is defined in sd-bus-vtable.h, but we need to list here the historical versions
+ to make sure the calling code is compatible with one of these */
+struct sd_bus_vtable_221 {
+ uint8_t type:8;
+ uint64_t flags:56;
+ union {
+ struct {
+ size_t element_size;
+ } start;
+ struct {
+ const char *member;
+ const char *signature;
+ const char *result;
+ sd_bus_message_handler_t handler;
+ size_t offset;
+ } method;
+ struct {
+ const char *member;
+ const char *signature;
+ } signal;
+ struct {
+ const char *member;
+ const char *signature;
+ sd_bus_property_get_t get;
+ sd_bus_property_set_t set;
+ size_t offset;
+ } property;
+ } x;
+};
+/* Structure size up to v241 */
+#define VTABLE_ELEMENT_SIZE_221 sizeof(struct sd_bus_vtable_221)
+
+/* Size of the structure when "features" field was added. If the structure definition is augmented, a copy of
+ * the structure definition will need to be made (similarly to the sd_bus_vtable_221 above), and this
+ * definition updated to refer to it. */
+#define VTABLE_ELEMENT_SIZE_242 sizeof(struct sd_bus_vtable)
+
+static int vtable_features(const sd_bus_vtable *vtable) {
+ if (vtable[0].x.start.element_size < VTABLE_ELEMENT_SIZE_242 ||
+ !vtable[0].x.start.vtable_format_reference)
+ return 0;
+ return vtable[0].x.start.features;
+}
+
+bool bus_vtable_has_names(const sd_bus_vtable *vtable) {
+ return vtable_features(vtable) & _SD_BUS_VTABLE_PARAM_NAMES;
+}
+
+const sd_bus_vtable* bus_vtable_next(const sd_bus_vtable *vtable, const sd_bus_vtable *v) {
+ return (const sd_bus_vtable*) ((char*) v + vtable[0].x.start.element_size);
+}
+
+static int add_object_vtable_internal(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ const char *path,
+ const char *interface,
+ const sd_bus_vtable *vtable,
+ bool fallback,
+ sd_bus_object_find_t find,
+ void *userdata) {
+
+ sd_bus_slot *s = NULL;
+ struct node_vtable *existing = NULL;
+ const sd_bus_vtable *v;
+ struct node *n;
+ int r;
+ const char *names = "";
+ names_flags nf;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(interface_name_is_valid(interface), -EINVAL);
+ assert_return(vtable, -EINVAL);
+ assert_return(vtable[0].type == _SD_BUS_VTABLE_START, -EINVAL);
+ assert_return(vtable[0].x.start.element_size == VTABLE_ELEMENT_SIZE_221 ||
+ vtable[0].x.start.element_size >= VTABLE_ELEMENT_SIZE_242,
+ -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!streq(interface, "org.freedesktop.DBus.Properties") &&
+ !streq(interface, "org.freedesktop.DBus.Introspectable") &&
+ !streq(interface, "org.freedesktop.DBus.Peer") &&
+ !streq(interface, "org.freedesktop.DBus.ObjectManager"), -EINVAL);
+
+ r = hashmap_ensure_allocated(&bus->vtable_methods, &vtable_member_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_ensure_allocated(&bus->vtable_properties, &vtable_member_hash_ops);
+ if (r < 0)
+ return r;
+
+ n = bus_node_allocate(bus, path);
+ if (!n)
+ return -ENOMEM;
+
+ LIST_FOREACH(vtables, i, n->vtables) {
+ if (i->is_fallback != fallback) {
+ r = -EPROTOTYPE;
+ goto fail;
+ }
+
+ if (streq(i->interface, interface)) {
+
+ if (i->vtable == vtable) {
+ r = -EEXIST;
+ goto fail;
+ }
+
+ existing = i;
+ }
+ }
+
+ s = bus_slot_allocate(bus, !slot, BUS_NODE_VTABLE, sizeof(struct node_vtable), userdata);
+ if (!s) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ s->node_vtable.is_fallback = fallback;
+ s->node_vtable.vtable = vtable;
+ s->node_vtable.find = find;
+
+ s->node_vtable.interface = strdup(interface);
+ if (!s->node_vtable.interface) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ v = s->node_vtable.vtable;
+ for (v = bus_vtable_next(vtable, v); v->type != _SD_BUS_VTABLE_END; v = bus_vtable_next(vtable, v)) {
+
+ switch (v->type) {
+
+ case _SD_BUS_VTABLE_METHOD: {
+ struct vtable_member *m;
+ nf = NAMES_FIRST_PART;
+
+ if (bus_vtable_has_names(vtable))
+ names = strempty(v->x.method.names);
+
+ if (!member_name_is_valid(v->x.method.member) ||
+ !signature_is_valid(strempty(v->x.method.signature), false) ||
+ !signature_is_valid(strempty(v->x.method.result), false) ||
+ !names_are_valid(strempty(v->x.method.signature), &names, &nf) ||
+ !names_are_valid(strempty(v->x.method.result), &names, &nf) ||
+ !(v->x.method.handler || (isempty(v->x.method.signature) && isempty(v->x.method.result))) ||
+ v->flags & (SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ m = new0(struct vtable_member, 1);
+ if (!m) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ m->parent = &s->node_vtable;
+ m->path = n->path;
+ m->interface = s->node_vtable.interface;
+ m->member = v->x.method.member;
+ m->vtable = v;
+
+ r = hashmap_put(bus->vtable_methods, m, m);
+ if (r < 0) {
+ free(m);
+ goto fail;
+ }
+
+ break;
+ }
+
+ case _SD_BUS_VTABLE_WRITABLE_PROPERTY:
+
+ if (!(v->x.property.set || bus_type_is_basic(v->x.property.signature[0]))) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ if (v->flags & SD_BUS_VTABLE_PROPERTY_CONST) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ _fallthrough_;
+ case _SD_BUS_VTABLE_PROPERTY: {
+ struct vtable_member *m;
+
+ if (!member_name_is_valid(v->x.property.member) ||
+ !signature_is_single(v->x.property.signature, false) ||
+ !(v->x.property.get || bus_type_is_basic(v->x.property.signature[0]) || streq(v->x.property.signature, "as")) ||
+ (v->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) ||
+ (!!(v->flags & SD_BUS_VTABLE_PROPERTY_CONST) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) > 1 ||
+ ((v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) && (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT)) ||
+ (v->flags & SD_BUS_VTABLE_UNPRIVILEGED && v->type == _SD_BUS_VTABLE_PROPERTY)) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ m = new0(struct vtable_member, 1);
+ if (!m) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ m->parent = &s->node_vtable;
+ m->path = n->path;
+ m->interface = s->node_vtable.interface;
+ m->member = v->x.property.member;
+ m->vtable = v;
+
+ r = hashmap_put(bus->vtable_properties, m, m);
+ if (r < 0) {
+ free(m);
+ goto fail;
+ }
+
+ break;
+ }
+
+ case _SD_BUS_VTABLE_SIGNAL:
+ nf = NAMES_SINGLE_PART;
+
+ if (bus_vtable_has_names(vtable))
+ names = strempty(v->x.signal.names);
+
+ if (!member_name_is_valid(v->x.signal.member) ||
+ !signature_is_valid(strempty(v->x.signal.signature), false) ||
+ !names_are_valid(strempty(v->x.signal.signature), &names, &nf) ||
+ v->flags & SD_BUS_VTABLE_UNPRIVILEGED) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ break;
+
+ default:
+ r = -EINVAL;
+ goto fail;
+ }
+ }
+
+ s->node_vtable.node = n;
+ LIST_INSERT_AFTER(vtables, n->vtables, existing, &s->node_vtable);
+ bus->nodes_modified = true;
+
+ if (slot)
+ *slot = s;
+
+ return 0;
+
+fail:
+ sd_bus_slot_unref(s);
+ bus_node_gc(bus, n);
+
+ return r;
+}
+
+/* This symbol exists solely to tell the linker that the "new" vtable format is used. */
+_public_ const unsigned sd_bus_object_vtable_format = 242;
+
+_public_ int sd_bus_add_object_vtable(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ const char *path,
+ const char *interface,
+ const sd_bus_vtable *vtable,
+ void *userdata) {
+
+ return add_object_vtable_internal(bus, slot, path, interface, vtable, false, NULL, userdata);
+}
+
+_public_ int sd_bus_add_fallback_vtable(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ const char *prefix,
+ const char *interface,
+ const sd_bus_vtable *vtable,
+ sd_bus_object_find_t find,
+ void *userdata) {
+
+ return add_object_vtable_internal(bus, slot, prefix, interface, vtable, true, find, userdata);
+}
+
+_public_ int sd_bus_add_node_enumerator(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ const char *path,
+ sd_bus_node_enumerator_t callback,
+ void *userdata) {
+
+ sd_bus_slot *s;
+ struct node *n;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(callback, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ n = bus_node_allocate(bus, path);
+ if (!n)
+ return -ENOMEM;
+
+ s = bus_slot_allocate(bus, !slot, BUS_NODE_ENUMERATOR, sizeof(struct node_enumerator), userdata);
+ if (!s) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ s->node_enumerator.callback = callback;
+
+ s->node_enumerator.node = n;
+ LIST_PREPEND(enumerators, n->enumerators, &s->node_enumerator);
+ bus->nodes_modified = true;
+
+ if (slot)
+ *slot = s;
+
+ return 0;
+
+fail:
+ sd_bus_slot_unref(s);
+ bus_node_gc(bus, n);
+
+ return r;
+}
+
+static int emit_properties_changed_on_interface(
+ sd_bus *bus,
+ const char *prefix,
+ const char *path,
+ const char *interface,
+ bool require_fallback,
+ bool *found_interface,
+ char **names) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ bool has_invalidating = false, has_changing = false;
+ struct vtable_member key = {};
+ struct node *n;
+ void *u = NULL;
+ int r;
+
+ assert(bus);
+ assert(prefix);
+ assert(path);
+ assert(interface);
+ assert(found_interface);
+
+ n = hashmap_get(bus->nodes, prefix);
+ if (!n)
+ return 0;
+
+ r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "s", interface);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(m, 'a', "{sv}");
+ if (r < 0)
+ return r;
+
+ key.path = prefix;
+ key.interface = interface;
+
+ LIST_FOREACH(vtables, c, n->vtables) {
+ if (require_fallback && !c->is_fallback)
+ continue;
+
+ if (!streq(c->interface, interface))
+ continue;
+
+ r = node_vtable_get_userdata(bus, path, c, &u, &error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ if (r == 0)
+ continue;
+
+ *found_interface = true;
+
+ if (names) {
+ /* If the caller specified a list of
+ * properties we include exactly those in the
+ * PropertiesChanged message */
+
+ STRV_FOREACH(property, names) {
+ struct vtable_member *v;
+
+ assert_return(member_name_is_valid(*property), -EINVAL);
+
+ key.member = *property;
+ v = hashmap_get(bus->vtable_properties, &key);
+ if (!v)
+ return -ENOENT;
+
+ /* If there are two vtables for the same
+ * interface, let's handle this property when
+ * we come to that vtable. */
+ if (c != v->parent)
+ continue;
+
+ assert_return(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE ||
+ v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION, -EDOM);
+
+ assert_return(!(v->vtable->flags & SD_BUS_VTABLE_HIDDEN), -EDOM);
+
+ if (v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) {
+ has_invalidating = true;
+ continue;
+ }
+
+ has_changing = true;
+
+ r = vtable_append_one_property(bus, m, m->path, c, v->vtable, u, &error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+ } else {
+ const sd_bus_vtable *v;
+
+ /* If the caller specified no properties list
+ * we include all properties that are marked
+ * as changing in the message. */
+
+ v = c->vtable;
+ for (v = bus_vtable_next(c->vtable, v); v->type != _SD_BUS_VTABLE_END; v = bus_vtable_next(c->vtable, v)) {
+ if (!IN_SET(v->type, _SD_BUS_VTABLE_PROPERTY, _SD_BUS_VTABLE_WRITABLE_PROPERTY))
+ continue;
+
+ if (v->flags & SD_BUS_VTABLE_HIDDEN)
+ continue;
+
+ if (v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) {
+ has_invalidating = true;
+ continue;
+ }
+
+ if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE))
+ continue;
+
+ has_changing = true;
+
+ r = vtable_append_one_property(bus, m, m->path, c, v, u, &error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+ }
+ }
+
+ if (!has_invalidating && !has_changing)
+ return 0;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(m, 'a', "s");
+ if (r < 0)
+ return r;
+
+ if (has_invalidating) {
+ LIST_FOREACH(vtables, c, n->vtables) {
+ if (require_fallback && !c->is_fallback)
+ continue;
+
+ if (!streq(c->interface, interface))
+ continue;
+
+ r = node_vtable_get_userdata(bus, path, c, &u, &error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ if (r == 0)
+ continue;
+
+ if (names) {
+ STRV_FOREACH(property, names) {
+ struct vtable_member *v;
+
+ key.member = *property;
+ assert_se(v = hashmap_get(bus->vtable_properties, &key));
+ assert(c == v->parent);
+
+ if (!(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION))
+ continue;
+
+ r = sd_bus_message_append(m, "s", *property);
+ if (r < 0)
+ return r;
+ }
+ } else {
+ const sd_bus_vtable *v;
+
+ v = c->vtable;
+ for (v = bus_vtable_next(c->vtable, v); v->type != _SD_BUS_VTABLE_END; v = bus_vtable_next(c->vtable, v)) {
+ if (!IN_SET(v->type, _SD_BUS_VTABLE_PROPERTY, _SD_BUS_VTABLE_WRITABLE_PROPERTY))
+ continue;
+
+ if (v->flags & SD_BUS_VTABLE_HIDDEN)
+ continue;
+
+ if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION))
+ continue;
+
+ r = sd_bus_message_append(m, "s", v->x.property.member);
+ if (r < 0)
+ return r;
+ }
+ }
+ }
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_send(bus, m, NULL);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+_public_ int sd_bus_emit_properties_changed_strv(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ char **names) {
+
+ _cleanup_free_ char *prefix = NULL;
+ bool found_interface = false;
+ size_t pl;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(interface_name_is_valid(interface), -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ /* A non-NULL but empty names list means nothing needs to be
+ generated. A NULL list OTOH indicates that all properties
+ that are set to EMITS_CHANGE or EMITS_INVALIDATION shall be
+ included in the PropertiesChanged message. */
+ if (names && names[0] == NULL)
+ return 0;
+
+ BUS_DONT_DESTROY(bus);
+
+ pl = strlen(path);
+ assert(pl <= BUS_PATH_SIZE_MAX);
+ prefix = new(char, pl + 1);
+ if (!prefix)
+ return -ENOMEM;
+
+ do {
+ bus->nodes_modified = false;
+
+ r = emit_properties_changed_on_interface(bus, path, path, interface, false, &found_interface, names);
+ if (r != 0)
+ return r;
+ if (bus->nodes_modified)
+ continue;
+
+ OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
+ r = emit_properties_changed_on_interface(bus, prefix, path, interface, true, &found_interface, names);
+ if (r != 0)
+ return r;
+ if (bus->nodes_modified)
+ break;
+ }
+
+ } while (bus->nodes_modified);
+
+ return found_interface ? 0 : -ENOENT;
+}
+
+_public_ int sd_bus_emit_properties_changed(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *name, ...) {
+
+ char **names;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(interface_name_is_valid(interface), -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ if (!name)
+ return 0;
+
+ names = strv_from_stdarg_alloca(name);
+
+ return sd_bus_emit_properties_changed_strv(bus, path, interface, names);
+}
+
+static int object_added_append_all_prefix(
+ sd_bus *bus,
+ sd_bus_message *m,
+ OrderedSet *s,
+ const char *prefix,
+ const char *path,
+ bool require_fallback) {
+
+ const char *previous_interface = NULL;
+ struct node *n;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(s);
+ assert(prefix);
+ assert(path);
+
+ n = hashmap_get(bus->nodes, prefix);
+ if (!n)
+ return 0;
+
+ LIST_FOREACH(vtables, c, n->vtables) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ void *u = NULL;
+
+ if (require_fallback && !c->is_fallback)
+ continue;
+
+ r = node_vtable_get_userdata(bus, path, c, &u, &error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ if (r == 0)
+ continue;
+
+ if (!streq_ptr(c->interface, previous_interface)) {
+ /* If a child-node already handled this interface, we
+ * skip it on any of its parents. The child vtables
+ * always fully override any conflicting vtables of
+ * any parent node. */
+ if (ordered_set_get(s, c->interface))
+ continue;
+
+ r = ordered_set_put(s, c->interface);
+ if (r < 0)
+ return r;
+
+ if (previous_interface) {
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_open_container(m, 'e', "sa{sv}");
+ if (r < 0)
+ return r;
+ r = sd_bus_message_append(m, "s", c->interface);
+ if (r < 0)
+ return r;
+ r = sd_bus_message_open_container(m, 'a', "{sv}");
+ if (r < 0)
+ return r;
+
+ previous_interface = c->interface;
+ }
+
+ r = vtable_append_all_properties(bus, m, path, c, u, &error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+
+ if (previous_interface) {
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int object_added_append_all(sd_bus *bus, sd_bus_message *m, const char *path, bool path_has_object_manager) {
+ _cleanup_ordered_set_free_ OrderedSet *s = NULL;
+ _cleanup_free_ char *prefix = NULL;
+ size_t pl;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(path);
+
+ /*
+ * This appends all interfaces registered on path @path. We first add
+ * the builtin interfaces, which are always available and handled by
+ * sd-bus. Then, we add all interfaces registered on the exact node,
+ * followed by all fallback interfaces registered on any parent prefix.
+ *
+ * If an interface is registered multiple times on the same node with
+ * different vtables, we merge all the properties across all vtables.
+ * However, if a child node has the same interface registered as one of
+ * its parent nodes has as fallback, we make the child overwrite the
+ * parent instead of extending it. Therefore, we keep a "Set" of all
+ * handled interfaces during parent traversal, so we skip interfaces on
+ * a parent that were overwritten by a child.
+ */
+
+ s = ordered_set_new(&string_hash_ops);
+ if (!s)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Peer", 0);
+ if (r < 0)
+ return r;
+ r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Introspectable", 0);
+ if (r < 0)
+ return r;
+ r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Properties", 0);
+ if (r < 0)
+ return r;
+ if (path_has_object_manager){
+ r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.ObjectManager", 0);
+ if (r < 0)
+ return r;
+ }
+
+ r = object_added_append_all_prefix(bus, m, s, path, path, false);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+
+ pl = strlen(path);
+ assert(pl <= BUS_PATH_SIZE_MAX);
+ prefix = new(char, pl + 1);
+ if (!prefix)
+ return -ENOMEM;
+
+ OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
+ r = object_added_append_all_prefix(bus, m, s, prefix, path, true);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+
+ return 0;
+}
+
+_public_ int sd_bus_emit_object_added(sd_bus *bus, const char *path) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ struct node *object_manager;
+ int r;
+
+ /*
+ * This emits an InterfacesAdded signal on the given path, by iterating
+ * all registered vtables and fallback vtables on the path. All
+ * properties are queried and included in the signal.
+ * This call is equivalent to sd_bus_emit_interfaces_added() with an
+ * explicit list of registered interfaces. However, unlike
+ * interfaces_added(), this call can figure out the list of supported
+ * interfaces itself. Furthermore, it properly adds the builtin
+ * org.freedesktop.DBus.* interfaces.
+ */
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ bool path_has_object_manager = false;
+ r = bus_find_parent_object_manager(bus, &object_manager, path, &path_has_object_manager);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ESRCH;
+
+ BUS_DONT_DESTROY(bus);
+
+ do {
+ bus->nodes_modified = false;
+ m = sd_bus_message_unref(m);
+
+ r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_basic(m, 'o', path);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(m, 'a', "{sa{sv}}");
+ if (r < 0)
+ return r;
+
+ r = object_added_append_all(bus, m, path, path_has_object_manager);
+ if (r < 0)
+ return r;
+
+ if (bus->nodes_modified)
+ continue;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ } while (bus->nodes_modified);
+
+ return sd_bus_send(bus, m, NULL);
+}
+
+static int object_removed_append_all_prefix(
+ sd_bus *bus,
+ sd_bus_message *m,
+ OrderedSet *s,
+ const char *prefix,
+ const char *path,
+ bool require_fallback) {
+
+ const char *previous_interface = NULL;
+ struct node *n;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(s);
+ assert(prefix);
+ assert(path);
+
+ n = hashmap_get(bus->nodes, prefix);
+ if (!n)
+ return 0;
+
+ LIST_FOREACH(vtables, c, n->vtables) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ void *u = NULL;
+
+ if (require_fallback && !c->is_fallback)
+ continue;
+ if (streq_ptr(c->interface, previous_interface))
+ continue;
+
+ /* If a child-node already handled this interface, we
+ * skip it on any of its parents. The child vtables
+ * always fully override any conflicting vtables of
+ * any parent node. */
+ if (ordered_set_get(s, c->interface))
+ continue;
+
+ r = node_vtable_get_userdata(bus, path, c, &u, &error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ if (r == 0)
+ continue;
+
+ r = ordered_set_put(s, c->interface);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "s", c->interface);
+ if (r < 0)
+ return r;
+
+ previous_interface = c->interface;
+ }
+
+ return 0;
+}
+
+static int object_removed_append_all(sd_bus *bus, sd_bus_message *m, const char *path, bool path_has_object_manager) {
+ _cleanup_ordered_set_free_ OrderedSet *s = NULL;
+ _cleanup_free_ char *prefix = NULL;
+ size_t pl;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(path);
+
+ /* see sd_bus_emit_object_added() for details */
+
+ s = ordered_set_new(&string_hash_ops);
+ if (!s)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Peer");
+ if (r < 0)
+ return r;
+ r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Introspectable");
+ if (r < 0)
+ return r;
+ r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Properties");
+ if (r < 0)
+ return r;
+
+ if (path_has_object_manager){
+ r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.ObjectManager");
+ if (r < 0)
+ return r;
+ }
+
+ r = object_removed_append_all_prefix(bus, m, s, path, path, false);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+
+ pl = strlen(path);
+ assert(pl <= BUS_PATH_SIZE_MAX);
+ prefix = new(char, pl + 1);
+ if (!prefix)
+ return -ENOMEM;
+
+ OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
+ r = object_removed_append_all_prefix(bus, m, s, prefix, path, true);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+
+ return 0;
+}
+
+_public_ int sd_bus_emit_object_removed(sd_bus *bus, const char *path) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ struct node *object_manager;
+ int r;
+
+ /*
+ * This is like sd_bus_emit_object_added(), but emits an
+ * InterfacesRemoved signal on the given path. This only includes any
+ * registered interfaces but skips the properties. Note that this will
+ * call into the find() callbacks of any registered vtable. Therefore,
+ * you must call this function before destroying/unlinking your object.
+ * Otherwise, the list of interfaces will be incomplete. However, note
+ * that this will *NOT* call into any property callback. Therefore, the
+ * object might be in an "destructed" state, as long as we can find it.
+ */
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ bool path_has_object_manager = false;
+ r = bus_find_parent_object_manager(bus, &object_manager, path, &path_has_object_manager);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ESRCH;
+
+ BUS_DONT_DESTROY(bus);
+
+ do {
+ bus->nodes_modified = false;
+ m = sd_bus_message_unref(m);
+
+ r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_basic(m, 'o', path);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(m, 'a', "s");
+ if (r < 0)
+ return r;
+
+ r = object_removed_append_all(bus, m, path, path_has_object_manager);
+ if (r < 0)
+ return r;
+
+ if (bus->nodes_modified)
+ continue;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ } while (bus->nodes_modified);
+
+ return sd_bus_send(bus, m, NULL);
+}
+
+static int interfaces_added_append_one_prefix(
+ sd_bus *bus,
+ sd_bus_message *m,
+ const char *prefix,
+ const char *path,
+ const char *interface,
+ bool require_fallback) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ bool found_interface = false;
+ struct node *n;
+ void *u = NULL;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(prefix);
+ assert(path);
+ assert(interface);
+
+ n = hashmap_get(bus->nodes, prefix);
+ if (!n)
+ return 0;
+
+ LIST_FOREACH(vtables, c, n->vtables) {
+ if (require_fallback && !c->is_fallback)
+ continue;
+
+ if (!streq(c->interface, interface))
+ continue;
+
+ r = node_vtable_get_userdata(bus, path, c, &u, &error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ if (r == 0)
+ continue;
+
+ if (!found_interface) {
+ r = sd_bus_message_append_basic(m, 's', interface);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(m, 'a', "{sv}");
+ if (r < 0)
+ return r;
+
+ found_interface = true;
+ }
+
+ r = vtable_append_all_properties(bus, m, path, c, u, &error);
+ if (r < 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+
+ if (found_interface) {
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+ }
+
+ return found_interface;
+}
+
+static int interfaces_added_append_one(
+ sd_bus *bus,
+ sd_bus_message *m,
+ const char *path,
+ const char *interface) {
+
+ _cleanup_free_ char *prefix = NULL;
+ size_t pl;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(path);
+ assert(interface);
+
+ r = interfaces_added_append_one_prefix(bus, m, path, path, interface, false);
+ if (r != 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+
+ pl = strlen(path);
+ assert(pl <= BUS_PATH_SIZE_MAX);
+ prefix = new(char, pl + 1);
+ if (!prefix)
+ return -ENOMEM;
+
+ OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
+ r = interfaces_added_append_one_prefix(bus, m, prefix, path, interface, true);
+ if (r != 0)
+ return r;
+ if (bus->nodes_modified)
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+_public_ int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ struct node *object_manager;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ if (strv_isempty(interfaces))
+ return 0;
+
+ bool path_has_object_manager = false;
+ r = bus_find_parent_object_manager(bus, &object_manager, path, &path_has_object_manager);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ESRCH;
+
+ BUS_DONT_DESTROY(bus);
+
+ do {
+ bus->nodes_modified = false;
+ m = sd_bus_message_unref(m);
+
+ r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_basic(m, 'o', path);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(m, 'a', "{sa{sv}}");
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, interfaces) {
+ assert_return(interface_name_is_valid(*i), -EINVAL);
+
+ r = sd_bus_message_open_container(m, 'e', "sa{sv}");
+ if (r < 0)
+ return r;
+
+ r = interfaces_added_append_one(bus, m, path, *i);
+ if (r < 0)
+ return r;
+
+ if (bus->nodes_modified)
+ break;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+ }
+
+ if (bus->nodes_modified)
+ continue;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ } while (bus->nodes_modified);
+
+ return sd_bus_send(bus, m, NULL);
+}
+
+_public_ int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) {
+ char **interfaces;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ interfaces = strv_from_stdarg_alloca(interface);
+
+ return sd_bus_emit_interfaces_added_strv(bus, path, interfaces);
+}
+
+_public_ int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ struct node *object_manager;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ if (strv_isempty(interfaces))
+ return 0;
+
+ bool path_has_object_manager = false;
+ r = bus_find_parent_object_manager(bus, &object_manager, path, &path_has_object_manager);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ESRCH;
+
+ r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_basic(m, 'o', path);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_strv(m, interfaces);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(bus, m, NULL);
+}
+
+_public_ int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...) {
+ char **interfaces;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ interfaces = strv_from_stdarg_alloca(interface);
+
+ return sd_bus_emit_interfaces_removed_strv(bus, path, interfaces);
+}
+
+_public_ int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) {
+ sd_bus_slot *s;
+ struct node *n;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ n = bus_node_allocate(bus, path);
+ if (!n)
+ return -ENOMEM;
+
+ s = bus_slot_allocate(bus, !slot, BUS_NODE_OBJECT_MANAGER, sizeof(struct node_object_manager), NULL);
+ if (!s) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ s->node_object_manager.node = n;
+ LIST_PREPEND(object_managers, n->object_managers, &s->node_object_manager);
+ bus->nodes_modified = true;
+
+ if (slot)
+ *slot = s;
+
+ return 0;
+
+fail:
+ sd_bus_slot_unref(s);
+ bus_node_gc(bus, n);
+
+ return r;
+}
diff --git a/src/libsystemd/sd-bus/bus-objects.h b/src/libsystemd/sd-bus/bus-objects.h
new file mode 100644
index 0000000..20fccfa
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-objects.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "bus-internal.h"
+#include "bus-introspect.h"
+
+const sd_bus_vtable* bus_vtable_next(const sd_bus_vtable *vtable, const sd_bus_vtable *v);
+bool bus_vtable_has_names(const sd_bus_vtable *vtable);
+int bus_process_object(sd_bus *bus, sd_bus_message *m);
+void bus_node_gc(sd_bus *b, struct node *n);
+
+int introspect_path(
+ sd_bus *bus,
+ const char *path,
+ struct node *n,
+ bool require_fallback,
+ bool ignore_nodes_modified,
+ bool *found_object,
+ char **ret,
+ sd_bus_error *error);
diff --git a/src/libsystemd/sd-bus/bus-protocol.h b/src/libsystemd/sd-bus/bus-protocol.h
new file mode 100644
index 0000000..be46b5f
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-protocol.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <endian.h>
+
+#include "macro.h"
+
+/* Packet header */
+
+struct _packed_ bus_header {
+ uint8_t endian;
+ uint8_t type;
+ uint8_t flags;
+ uint8_t version;
+ uint32_t body_size;
+ /* Note that what the bus spec calls "serial" we'll call "cookie" instead, because we don't
+ * want to imply that the cookie was in any way monotonically increasing. */
+ uint32_t serial;
+ uint32_t fields_size;
+};
+
+/* Endianness */
+
+enum {
+ _BUS_INVALID_ENDIAN = 0,
+ BUS_LITTLE_ENDIAN = 'l',
+ BUS_BIG_ENDIAN = 'B',
+#if __BYTE_ORDER == __BIG_ENDIAN
+ BUS_NATIVE_ENDIAN = BUS_BIG_ENDIAN,
+ BUS_REVERSE_ENDIAN = BUS_LITTLE_ENDIAN
+#else
+ BUS_NATIVE_ENDIAN = BUS_LITTLE_ENDIAN,
+ BUS_REVERSE_ENDIAN = BUS_BIG_ENDIAN
+#endif
+};
+
+/* Flags */
+
+enum {
+ BUS_MESSAGE_NO_REPLY_EXPECTED = 1 << 0,
+ BUS_MESSAGE_NO_AUTO_START = 1 << 1,
+ BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION = 1 << 2,
+};
+
+/* Header fields */
+
+enum {
+ _BUS_MESSAGE_HEADER_INVALID = 0,
+ BUS_MESSAGE_HEADER_PATH,
+ BUS_MESSAGE_HEADER_INTERFACE,
+ BUS_MESSAGE_HEADER_MEMBER,
+ BUS_MESSAGE_HEADER_ERROR_NAME,
+ BUS_MESSAGE_HEADER_REPLY_SERIAL,
+ BUS_MESSAGE_HEADER_DESTINATION,
+ BUS_MESSAGE_HEADER_SENDER,
+ BUS_MESSAGE_HEADER_SIGNATURE,
+ BUS_MESSAGE_HEADER_UNIX_FDS,
+ _BUS_MESSAGE_HEADER_MAX
+};
+
+/* RequestName parameters */
+
+enum {
+ BUS_NAME_ALLOW_REPLACEMENT = 1 << 0,
+ BUS_NAME_REPLACE_EXISTING = 1 << 1,
+ BUS_NAME_DO_NOT_QUEUE = 1 << 2,
+};
+
+/* RequestName returns */
+enum {
+ BUS_NAME_PRIMARY_OWNER = 1,
+ BUS_NAME_IN_QUEUE = 2,
+ BUS_NAME_EXISTS = 3,
+ BUS_NAME_ALREADY_OWNER = 4
+};
+
+/* ReleaseName returns */
+enum {
+ BUS_NAME_RELEASED = 1,
+ BUS_NAME_NON_EXISTENT = 2,
+ BUS_NAME_NOT_OWNER = 3,
+};
+
+/* StartServiceByName returns */
+enum {
+ BUS_START_REPLY_SUCCESS = 1,
+ BUS_START_REPLY_ALREADY_RUNNING = 2,
+};
diff --git a/src/libsystemd/sd-bus/bus-signature.c b/src/libsystemd/sd-bus/bus-signature.c
new file mode 100644
index 0000000..bd0842f
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-signature.c
@@ -0,0 +1,148 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <util.h>
+
+#include "sd-bus.h"
+
+#include "bus-signature.h"
+#include "bus-type.h"
+
+static int signature_element_length_internal(
+ const char *s,
+ bool allow_dict_entry,
+ unsigned array_depth,
+ unsigned struct_depth,
+ size_t *l) {
+
+ int r;
+
+ if (!s)
+ return -EINVAL;
+
+ assert(l);
+
+ if (bus_type_is_basic(*s) || *s == SD_BUS_TYPE_VARIANT) {
+ *l = 1;
+ return 0;
+ }
+
+ if (*s == SD_BUS_TYPE_ARRAY) {
+ size_t t;
+
+ if (array_depth >= 32)
+ return -EINVAL;
+
+ r = signature_element_length_internal(s + 1, true, array_depth+1, struct_depth, &t);
+ if (r < 0)
+ return r;
+
+ *l = t + 1;
+ return 0;
+ }
+
+ if (*s == SD_BUS_TYPE_STRUCT_BEGIN) {
+ const char *p = s + 1;
+
+ if (struct_depth >= 32)
+ return -EINVAL;
+
+ while (*p != SD_BUS_TYPE_STRUCT_END) {
+ size_t t;
+
+ r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t);
+ if (r < 0)
+ return r;
+
+ 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;
+ }
+
+ if (*s == SD_BUS_TYPE_DICT_ENTRY_BEGIN && allow_dict_entry) {
+ const char *p = s + 1;
+ unsigned n = 0;
+
+ if (struct_depth >= 32)
+ return -EINVAL;
+
+ while (*p != SD_BUS_TYPE_DICT_ENTRY_END) {
+ size_t t;
+
+ if (n == 0 && !bus_type_is_basic(*p))
+ return -EINVAL;
+
+ r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t);
+ if (r < 0)
+ return r;
+
+ p += t;
+ n++;
+ }
+
+ if (n != 2)
+ return -EINVAL;
+
+ *l = p - s + 1;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+int signature_element_length(const char *s, size_t *l) {
+ return signature_element_length_internal(s, true, 0, 0, l);
+}
+
+bool signature_is_single(const char *s, bool allow_dict_entry) {
+ int r;
+ size_t t;
+
+ if (!s)
+ return false;
+
+ r = signature_element_length_internal(s, allow_dict_entry, 0, 0, &t);
+ if (r < 0)
+ return false;
+
+ return s[t] == 0;
+}
+
+bool signature_is_pair(const char *s) {
+
+ if (!s)
+ return false;
+
+ if (!bus_type_is_basic(*s))
+ return false;
+
+ return signature_is_single(s + 1, false);
+}
+
+bool signature_is_valid(const char *s, bool allow_dict_entry) {
+ const char *p;
+ int r;
+
+ if (!s)
+ return false;
+
+ p = s;
+ while (*p) {
+ size_t t;
+
+ r = signature_element_length_internal(p, allow_dict_entry, 0, 0, &t);
+ if (r < 0)
+ return false;
+
+ p += t;
+ }
+
+ return p - s <= SD_BUS_MAXIMUM_SIGNATURE_LENGTH;
+}
diff --git a/src/libsystemd/sd-bus/bus-signature.h b/src/libsystemd/sd-bus/bus-signature.h
new file mode 100644
index 0000000..314fcc2
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-signature.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+bool signature_is_single(const char *s, bool allow_dict_entry);
+bool signature_is_pair(const char *s);
+bool signature_is_valid(const char *s, bool allow_dict_entry);
+
+int signature_element_length(const char *s, size_t *l);
diff --git a/src/libsystemd/sd-bus/bus-slot.c b/src/libsystemd/sd-bus/bus-slot.c
new file mode 100644
index 0000000..9f28957
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-slot.c
@@ -0,0 +1,311 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-control.h"
+#include "bus-objects.h"
+#include "bus-slot.h"
+#include "string-util.h"
+
+sd_bus_slot *bus_slot_allocate(
+ sd_bus *bus,
+ bool floating,
+ BusSlotType type,
+ size_t extra,
+ void *userdata) {
+
+ sd_bus_slot *slot;
+
+ assert(bus);
+
+ slot = malloc0(offsetof(sd_bus_slot, reply_callback) + extra);
+ if (!slot)
+ return NULL;
+
+ slot->n_ref = 1;
+ slot->type = type;
+ slot->bus = bus;
+ slot->floating = floating;
+ slot->userdata = userdata;
+
+ if (!floating)
+ sd_bus_ref(bus);
+
+ LIST_PREPEND(slots, bus->slots, slot);
+
+ return slot;
+}
+
+void bus_slot_disconnect(sd_bus_slot *slot, bool unref) {
+ sd_bus *bus;
+
+ assert(slot);
+
+ if (!slot->bus)
+ return;
+
+ switch (slot->type) {
+
+ case BUS_REPLY_CALLBACK:
+
+ if (slot->reply_callback.cookie != 0)
+ ordered_hashmap_remove(slot->bus->reply_callbacks, &slot->reply_callback.cookie);
+
+ if (slot->reply_callback.timeout_usec != 0)
+ prioq_remove(slot->bus->reply_callbacks_prioq, &slot->reply_callback, &slot->reply_callback.prioq_idx);
+
+ break;
+
+ case BUS_FILTER_CALLBACK:
+ slot->bus->filter_callbacks_modified = true;
+ LIST_REMOVE(callbacks, slot->bus->filter_callbacks, &slot->filter_callback);
+ break;
+
+ case BUS_MATCH_CALLBACK:
+
+ if (slot->match_added)
+ (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, true);
+ slot->match_callback.install_slot = sd_bus_slot_unref(slot->match_callback.install_slot);
+ }
+
+ slot->bus->match_callbacks_modified = true;
+ bus_match_remove(&slot->bus->match_callbacks, &slot->match_callback);
+
+ slot->match_callback.match_string = mfree(slot->match_callback.match_string);
+
+ break;
+
+ case BUS_NODE_CALLBACK:
+
+ if (slot->node_callback.node) {
+ LIST_REMOVE(callbacks, slot->node_callback.node->callbacks, &slot->node_callback);
+ slot->bus->nodes_modified = true;
+
+ bus_node_gc(slot->bus, slot->node_callback.node);
+ }
+
+ break;
+
+ case BUS_NODE_ENUMERATOR:
+
+ if (slot->node_enumerator.node) {
+ LIST_REMOVE(enumerators, slot->node_enumerator.node->enumerators, &slot->node_enumerator);
+ slot->bus->nodes_modified = true;
+
+ bus_node_gc(slot->bus, slot->node_enumerator.node);
+ }
+
+ break;
+
+ case BUS_NODE_OBJECT_MANAGER:
+
+ if (slot->node_object_manager.node) {
+ LIST_REMOVE(object_managers, slot->node_object_manager.node->object_managers, &slot->node_object_manager);
+ slot->bus->nodes_modified = true;
+
+ bus_node_gc(slot->bus, slot->node_object_manager.node);
+ }
+
+ break;
+
+ case BUS_NODE_VTABLE:
+
+ if (slot->node_vtable.node && slot->node_vtable.interface && slot->node_vtable.vtable) {
+ const sd_bus_vtable *v;
+
+ for (v = slot->node_vtable.vtable; v->type != _SD_BUS_VTABLE_END; v = bus_vtable_next(slot->node_vtable.vtable, v)) {
+ struct vtable_member *x = NULL;
+
+ switch (v->type) {
+
+ case _SD_BUS_VTABLE_METHOD: {
+ struct vtable_member key;
+
+ key.path = slot->node_vtable.node->path;
+ key.interface = slot->node_vtable.interface;
+ key.member = v->x.method.member;
+
+ x = hashmap_remove(slot->bus->vtable_methods, &key);
+ break;
+ }
+
+ case _SD_BUS_VTABLE_PROPERTY:
+ case _SD_BUS_VTABLE_WRITABLE_PROPERTY: {
+ struct vtable_member key;
+
+ key.path = slot->node_vtable.node->path;
+ key.interface = slot->node_vtable.interface;
+ key.member = v->x.method.member;
+
+ x = hashmap_remove(slot->bus->vtable_properties, &key);
+ break;
+ }}
+
+ free(x);
+ }
+ }
+
+ slot->node_vtable.interface = mfree(slot->node_vtable.interface);
+
+ if (slot->node_vtable.node) {
+ LIST_REMOVE(vtables, slot->node_vtable.node->vtables, &slot->node_vtable);
+ slot->bus->nodes_modified = true;
+
+ bus_node_gc(slot->bus, slot->node_vtable.node);
+ }
+
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ bus = slot->bus;
+
+ slot->type = _BUS_SLOT_INVALID;
+ slot->bus = NULL;
+ LIST_REMOVE(slots, bus->slots, slot);
+
+ if (!slot->floating)
+ sd_bus_unref(bus);
+ else if (unref)
+ sd_bus_slot_unref(slot);
+}
+
+static sd_bus_slot* bus_slot_free(sd_bus_slot *slot) {
+ assert(slot);
+
+ bus_slot_disconnect(slot, false);
+
+ if (slot->destroy_callback)
+ slot->destroy_callback(slot->userdata);
+
+ free(slot->description);
+ return mfree(slot);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_bus_slot, sd_bus_slot, bus_slot_free);
+
+_public_ sd_bus* sd_bus_slot_get_bus(sd_bus_slot *slot) {
+ assert_return(slot, NULL);
+
+ return slot->bus;
+}
+
+_public_ void *sd_bus_slot_get_userdata(sd_bus_slot *slot) {
+ assert_return(slot, NULL);
+
+ return slot->userdata;
+}
+
+_public_ void *sd_bus_slot_set_userdata(sd_bus_slot *slot, void *userdata) {
+ void *ret;
+
+ assert_return(slot, NULL);
+
+ ret = slot->userdata;
+ slot->userdata = userdata;
+
+ return ret;
+}
+
+_public_ int sd_bus_slot_set_destroy_callback(sd_bus_slot *slot, sd_bus_destroy_t callback) {
+ assert_return(slot, -EINVAL);
+
+ slot->destroy_callback = callback;
+ return 0;
+}
+
+_public_ int sd_bus_slot_get_destroy_callback(sd_bus_slot *slot, sd_bus_destroy_t *callback) {
+ assert_return(slot, -EINVAL);
+
+ if (callback)
+ *callback = slot->destroy_callback;
+
+ return !!slot->destroy_callback;
+}
+
+_public_ sd_bus_message *sd_bus_slot_get_current_message(sd_bus_slot *slot) {
+ assert_return(slot, NULL);
+ assert_return(slot->type >= 0, NULL);
+
+ if (slot->bus->current_slot != slot)
+ return NULL;
+
+ return slot->bus->current_message;
+}
+
+_public_ sd_bus_message_handler_t sd_bus_slot_get_current_handler(sd_bus_slot *slot) {
+ assert_return(slot, NULL);
+ assert_return(slot->type >= 0, NULL);
+
+ if (slot->bus->current_slot != slot)
+ return NULL;
+
+ return slot->bus->current_handler;
+}
+
+_public_ void* sd_bus_slot_get_current_userdata(sd_bus_slot *slot) {
+ assert_return(slot, NULL);
+ assert_return(slot->type >= 0, NULL);
+
+ if (slot->bus->current_slot != slot)
+ return NULL;
+
+ return slot->bus->current_userdata;
+}
+
+_public_ int sd_bus_slot_get_floating(sd_bus_slot *slot) {
+ assert_return(slot, -EINVAL);
+
+ return slot->floating;
+}
+
+_public_ int sd_bus_slot_set_floating(sd_bus_slot *slot, int b) {
+ assert_return(slot, -EINVAL);
+
+ if (slot->floating == !!b)
+ return 0;
+
+ if (!slot->bus) /* already disconnected slots can't be reconnected */
+ return -ESTALE;
+
+ slot->floating = b;
+
+ /* When a slot is "floating" then the bus references the slot. Otherwise the slot references the bus. Hence,
+ * when we move from one to the other, let's increase one reference and decrease the other. */
+
+ if (b) {
+ sd_bus_slot_ref(slot);
+ sd_bus_unref(slot->bus);
+ } else {
+ sd_bus_ref(slot->bus);
+ sd_bus_slot_unref(slot);
+ }
+
+ return 1;
+}
+
+_public_ int sd_bus_slot_set_description(sd_bus_slot *slot, const char *description) {
+ assert_return(slot, -EINVAL);
+
+ return free_and_strdup(&slot->description, description);
+}
+
+_public_ int sd_bus_slot_get_description(sd_bus_slot *slot, const char **description) {
+ assert_return(slot, -EINVAL);
+ assert_return(description, -EINVAL);
+
+ if (slot->description)
+ *description = slot->description;
+ else if (slot->type == BUS_MATCH_CALLBACK)
+ *description = slot->match_callback.match_string;
+ else
+ return -ENXIO;
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/bus-slot.h b/src/libsystemd/sd-bus/bus-slot.h
new file mode 100644
index 0000000..8116195
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-slot.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-bus.h"
+
+#include "bus-internal.h"
+
+sd_bus_slot *bus_slot_allocate(sd_bus *bus, bool floating, BusSlotType type, size_t extra, void *userdata);
+
+void bus_slot_disconnect(sd_bus_slot *slot, bool unref);
diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c
new file mode 100644
index 0000000..c94befe
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-socket.c
@@ -0,0 +1,1374 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <endian.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "sd-bus.h"
+#include "sd-daemon.h"
+
+#include "alloc-util.h"
+#include "bus-internal.h"
+#include "bus-message.h"
+#include "bus-socket.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "hexdecoct.h"
+#include "io-util.h"
+#include "macro.h"
+#include "memory-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "rlimit-util.h"
+#include "signal-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "user-util.h"
+#include "utf8.h"
+
+#define SNDBUF_SIZE (8*1024*1024)
+
+static void iovec_advance(struct iovec iov[], unsigned *idx, size_t size) {
+
+ while (size > 0) {
+ struct iovec *i = iov + *idx;
+
+ if (i->iov_len > size) {
+ i->iov_base = (uint8_t*) i->iov_base + size;
+ i->iov_len -= size;
+ return;
+ }
+
+ size -= i->iov_len;
+
+ *i = IOVEC_MAKE(NULL, 0);
+
+ (*idx)++;
+ }
+}
+
+static int append_iovec(sd_bus_message *m, const void *p, size_t sz) {
+ assert(m);
+ assert(p);
+ assert(sz > 0);
+
+ m->iovec[m->n_iovec++] = IOVEC_MAKE((void*) p, sz);
+
+ return 0;
+}
+
+static int bus_message_setup_iovec(sd_bus_message *m) {
+ struct bus_body_part *part;
+ unsigned n, i;
+ int r;
+
+ assert(m);
+ assert(m->sealed);
+
+ if (m->n_iovec > 0)
+ return 0;
+
+ assert(!m->iovec);
+
+ n = 1 + m->n_body_parts;
+ if (n < ELEMENTSOF(m->iovec_fixed))
+ m->iovec = m->iovec_fixed;
+ else {
+ m->iovec = new(struct iovec, n);
+ if (!m->iovec) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ r = append_iovec(m, m->header, BUS_MESSAGE_BODY_BEGIN(m));
+ if (r < 0)
+ goto fail;
+
+ MESSAGE_FOREACH_PART(part, i, m) {
+ r = bus_body_part_map(part);
+ if (r < 0)
+ goto fail;
+
+ r = append_iovec(m, part->data, part->size);
+ if (r < 0)
+ goto fail;
+ }
+
+ assert(n == m->n_iovec);
+
+ return 0;
+
+fail:
+ m->poisoned = true;
+ return r;
+}
+
+bool bus_socket_auth_needs_write(sd_bus *b) {
+
+ unsigned i;
+
+ if (b->auth_index >= ELEMENTSOF(b->auth_iovec))
+ return false;
+
+ for (i = b->auth_index; i < ELEMENTSOF(b->auth_iovec); i++) {
+ struct iovec *j = b->auth_iovec + i;
+
+ if (j->iov_len > 0)
+ return true;
+ }
+
+ return false;
+}
+
+static int bus_socket_auth_verify_client(sd_bus *b) {
+ char *l, *lines[4] = {};
+ sd_id128_t peer;
+ size_t i, n;
+ int r;
+
+ assert(b);
+
+ /*
+ * We expect up to three response lines:
+ * "DATA\r\n" (optional)
+ * "OK <server-id>\r\n"
+ * "AGREE_UNIX_FD\r\n" (optional)
+ */
+
+ n = 0;
+ lines[n] = b->rbuffer;
+ for (i = 0; i < 3; ++i) {
+ l = memmem_safe(lines[n], b->rbuffer_size - (lines[n] - (char*) b->rbuffer), "\r\n", 2);
+ if (l)
+ lines[++n] = l + 2;
+ else
+ break;
+ }
+
+ /*
+ * If we sent a non-empty initial response, then we just expect an OK
+ * reply. We currently do this if, and only if, we picked ANONYMOUS.
+ * If we did not send an initial response, then we expect a DATA
+ * challenge, reply with our own DATA, and expect an OK reply. We do
+ * this for EXTERNAL.
+ * If FD negotiation was requested, we additionally expect
+ * an AGREE_UNIX_FD response in all cases.
+ */
+ if (n < (b->anonymous_auth ? 1U : 2U) + !!b->accept_fd)
+ return 0; /* wait for more data */
+
+ i = 0;
+
+ /* In case of EXTERNAL, verify the first response was DATA. */
+ if (!b->anonymous_auth) {
+ l = lines[i++];
+ if (lines[i] - l == 4 + 2) {
+ if (memcmp(l, "DATA", 4))
+ return -EPERM;
+ } else if (lines[i] - l == 3 + 32 + 2) {
+ /*
+ * Old versions of the server-side implementation of
+ * `sd-bus` replied with "OK <id>" to "AUTH" requests
+ * from a client, even if the "AUTH" line did not
+ * contain inlined arguments. Therefore, we also accept
+ * "OK <id>" here, even though it is technically the
+ * wrong reply. We ignore the "<id>" parameter, though,
+ * since it has no real value.
+ */
+ if (memcmp(l, "OK ", 3))
+ return -EPERM;
+ } else
+ return -EPERM;
+ }
+
+ /* Now check the OK line. */
+ l = lines[i++];
+
+ if (lines[i] - l != 3 + 32 + 2)
+ return -EPERM;
+ if (memcmp(l, "OK ", 3))
+ return -EPERM;
+
+ b->auth = b->anonymous_auth ? BUS_AUTH_ANONYMOUS : BUS_AUTH_EXTERNAL;
+
+ for (unsigned j = 0; j < 32; j += 2) {
+ int x, y;
+
+ x = unhexchar(l[3 + j]);
+ y = unhexchar(l[3 + j + 1]);
+
+ if (x < 0 || y < 0)
+ return -EINVAL;
+
+ peer.bytes[j/2] = ((uint8_t) x << 4 | (uint8_t) y);
+ }
+
+ if (!sd_id128_is_null(b->server_id) &&
+ !sd_id128_equal(b->server_id, peer))
+ return -EPERM;
+
+ b->server_id = peer;
+
+ /* And possibly check the third line, too */
+ if (b->accept_fd) {
+ l = lines[i++];
+ b->can_fds = !!memory_startswith(l, lines[i] - l, "AGREE_UNIX_FD");
+ }
+
+ assert(i == n);
+
+ b->rbuffer_size -= (lines[i] - (char*) b->rbuffer);
+ memmove(b->rbuffer, lines[i], b->rbuffer_size);
+
+ r = bus_start_running(b);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static bool line_equals(const char *s, size_t m, const char *line) {
+ size_t l;
+
+ l = strlen(line);
+ if (l != m)
+ return false;
+
+ return memcmp(s, line, l) == 0;
+}
+
+static bool line_begins(const char *s, size_t m, const char *word) {
+ const char *p;
+
+ p = memory_startswith(s, m, word);
+ return p && (p == (s + m) || *p == ' ');
+}
+
+static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) {
+ _cleanup_free_ char *token = NULL;
+ size_t len;
+ int r;
+
+ if (!b->anonymous_auth)
+ return 0;
+
+ if (l <= 0)
+ return 1;
+
+ assert(p[0] == ' ');
+ p++; l--;
+
+ if (l % 2 != 0)
+ return 0;
+
+ r = unhexmem(p, l, (void **) &token, &len);
+ if (r < 0)
+ return 0;
+
+ if (memchr(token, 0, len))
+ return 0;
+
+ return !!utf8_is_valid(token);
+}
+
+static int verify_external_token(sd_bus *b, const char *p, size_t l) {
+ _cleanup_free_ char *token = NULL;
+ size_t len;
+ uid_t u;
+ int r;
+
+ /* We don't do any real authentication here. Instead, if
+ * the owner of this bus wanted authentication they should have
+ * checked SO_PEERCRED before even creating the bus object. */
+
+ if (!b->anonymous_auth && !b->ucred_valid)
+ return 0;
+
+ if (l <= 0)
+ return 1;
+
+ assert(p[0] == ' ');
+ p++; l--;
+
+ if (l % 2 != 0)
+ return 0;
+
+ r = unhexmem(p, l, (void**) &token, &len);
+ if (r < 0)
+ return 0;
+
+ if (memchr(token, 0, len))
+ return 0;
+
+ r = parse_uid(token, &u);
+ if (r < 0)
+ return 0;
+
+ /* We ignore the passed value if anonymous authentication is
+ * on anyway. */
+ if (!b->anonymous_auth && u != b->ucred.uid)
+ return 0;
+
+ return 1;
+}
+
+static int bus_socket_auth_write(sd_bus *b, const char *t) {
+ char *p;
+ size_t l;
+
+ assert(b);
+ assert(t);
+
+ /* We only make use of the first iovec */
+ assert(IN_SET(b->auth_index, 0, 1));
+
+ l = strlen(t);
+ p = malloc(b->auth_iovec[0].iov_len + l);
+ if (!p)
+ return -ENOMEM;
+
+ memcpy_safe(p, b->auth_iovec[0].iov_base, b->auth_iovec[0].iov_len);
+ memcpy(p + b->auth_iovec[0].iov_len, t, l);
+
+ b->auth_iovec[0].iov_base = p;
+ b->auth_iovec[0].iov_len += l;
+
+ free(b->auth_buffer);
+ b->auth_buffer = p;
+ b->auth_index = 0;
+ return 0;
+}
+
+static int bus_socket_auth_write_ok(sd_bus *b) {
+ char t[3 + 32 + 2 + 1];
+
+ assert(b);
+
+ xsprintf(t, "OK " SD_ID128_FORMAT_STR "\r\n", SD_ID128_FORMAT_VAL(b->server_id));
+
+ return bus_socket_auth_write(b, t);
+}
+
+static int bus_socket_auth_verify_server(sd_bus *b) {
+ char *e;
+ const char *line;
+ size_t l;
+ bool processed = false;
+ int r;
+
+ assert(b);
+
+ if (b->rbuffer_size < 1)
+ return 0;
+
+ /* First char must be a NUL byte */
+ if (*(char*) b->rbuffer != 0)
+ return -EIO;
+
+ if (b->rbuffer_size < 3)
+ return 0;
+
+ /* Begin with the first line */
+ if (b->auth_rbegin <= 0)
+ b->auth_rbegin = 1;
+
+ for (;;) {
+ /* Check if line is complete */
+ line = (char*) b->rbuffer + b->auth_rbegin;
+ e = memmem_safe(line, b->rbuffer_size - b->auth_rbegin, "\r\n", 2);
+ if (!e)
+ return processed;
+
+ l = e - line;
+
+ if (line_begins(line, l, "AUTH ANONYMOUS")) {
+
+ r = verify_anonymous_token(b,
+ line + strlen("AUTH ANONYMOUS"),
+ l - strlen("AUTH ANONYMOUS"));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ r = bus_socket_auth_write(b, "REJECTED\r\n");
+ else {
+ b->auth = BUS_AUTH_ANONYMOUS;
+ if (l <= strlen("AUTH ANONYMOUS"))
+ r = bus_socket_auth_write(b, "DATA\r\n");
+ else
+ r = bus_socket_auth_write_ok(b);
+ }
+
+ } else if (line_begins(line, l, "AUTH EXTERNAL")) {
+
+ r = verify_external_token(b,
+ line + strlen("AUTH EXTERNAL"),
+ l - strlen("AUTH EXTERNAL"));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ r = bus_socket_auth_write(b, "REJECTED\r\n");
+ else {
+ b->auth = BUS_AUTH_EXTERNAL;
+ if (l <= strlen("AUTH EXTERNAL"))
+ r = bus_socket_auth_write(b, "DATA\r\n");
+ else
+ r = bus_socket_auth_write_ok(b);
+ }
+
+ } else if (line_begins(line, l, "AUTH"))
+ r = bus_socket_auth_write(b, "REJECTED EXTERNAL ANONYMOUS\r\n");
+ else if (line_equals(line, l, "CANCEL") ||
+ line_begins(line, l, "ERROR")) {
+
+ b->auth = _BUS_AUTH_INVALID;
+ r = bus_socket_auth_write(b, "REJECTED\r\n");
+
+ } else if (line_equals(line, l, "BEGIN")) {
+
+ if (b->auth == _BUS_AUTH_INVALID)
+ r = bus_socket_auth_write(b, "ERROR\r\n");
+ else {
+ /* We can't leave from the auth phase
+ * before we haven't written
+ * everything queued, so let's check
+ * that */
+
+ if (bus_socket_auth_needs_write(b))
+ return 1;
+
+ b->rbuffer_size -= (e + 2 - (char*) b->rbuffer);
+ memmove(b->rbuffer, e + 2, b->rbuffer_size);
+ return bus_start_running(b);
+ }
+
+ } else if (line_begins(line, l, "DATA")) {
+
+ if (b->auth == _BUS_AUTH_INVALID)
+ r = bus_socket_auth_write(b, "ERROR\r\n");
+ else {
+ if (b->auth == BUS_AUTH_ANONYMOUS)
+ r = verify_anonymous_token(b, line + 4, l - 4);
+ else
+ r = verify_external_token(b, line + 4, l - 4);
+
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ b->auth = _BUS_AUTH_INVALID;
+ r = bus_socket_auth_write(b, "REJECTED\r\n");
+ } else
+ r = bus_socket_auth_write_ok(b);
+ }
+ } else if (line_equals(line, l, "NEGOTIATE_UNIX_FD")) {
+ if (b->auth == _BUS_AUTH_INVALID || !b->accept_fd)
+ r = bus_socket_auth_write(b, "ERROR\r\n");
+ else {
+ b->can_fds = true;
+ r = bus_socket_auth_write(b, "AGREE_UNIX_FD\r\n");
+ }
+ } else
+ r = bus_socket_auth_write(b, "ERROR\r\n");
+
+ if (r < 0)
+ return r;
+
+ b->auth_rbegin = e + 2 - (char*) b->rbuffer;
+
+ processed = true;
+ }
+}
+
+static int bus_socket_auth_verify(sd_bus *b) {
+ assert(b);
+
+ if (b->is_server)
+ return bus_socket_auth_verify_server(b);
+ else
+ return bus_socket_auth_verify_client(b);
+}
+
+static int bus_socket_write_auth(sd_bus *b) {
+ ssize_t k;
+
+ assert(b);
+ assert(b->state == BUS_AUTHENTICATING);
+
+ if (!bus_socket_auth_needs_write(b))
+ return 0;
+
+ if (b->prefer_writev)
+ k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index);
+ else {
+ struct msghdr mh = {
+ .msg_iov = b->auth_iovec + b->auth_index,
+ .msg_iovlen = ELEMENTSOF(b->auth_iovec) - b->auth_index,
+ };
+
+ k = sendmsg(b->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL);
+ if (k < 0 && errno == ENOTSOCK) {
+ b->prefer_writev = true;
+ k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index);
+ }
+ }
+
+ if (k < 0)
+ return ERRNO_IS_TRANSIENT(errno) ? 0 : -errno;
+
+ iovec_advance(b->auth_iovec, &b->auth_index, (size_t) k);
+
+ /* Now crank the state machine since we might be able to make progress after writing. For example,
+ * the server only processes "BEGIN" when the write buffer is empty.
+ */
+ return bus_socket_auth_verify(b);
+}
+
+static int bus_socket_read_auth(sd_bus *b) {
+ struct msghdr mh;
+ struct iovec iov = {};
+ size_t n;
+ ssize_t k;
+ int r;
+ void *p;
+ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int) * BUS_FDS_MAX)) control;
+ bool handle_cmsg = false;
+
+ assert(b);
+ assert(b->state == BUS_AUTHENTICATING);
+
+ r = bus_socket_auth_verify(b);
+ if (r != 0)
+ return r;
+
+ n = MAX(256u, b->rbuffer_size * 2);
+
+ if (n > BUS_AUTH_SIZE_MAX)
+ n = BUS_AUTH_SIZE_MAX;
+
+ if (b->rbuffer_size >= n)
+ return -ENOBUFS;
+
+ p = realloc(b->rbuffer, n);
+ if (!p)
+ return -ENOMEM;
+
+ b->rbuffer = p;
+
+ iov = IOVEC_MAKE((uint8_t *)b->rbuffer + b->rbuffer_size, n - b->rbuffer_size);
+
+ if (b->prefer_readv) {
+ k = readv(b->input_fd, &iov, 1);
+ if (k < 0)
+ k = -errno;
+ } else {
+ mh = (struct msghdr) {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+
+ k = recvmsg_safe(b->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+ if (k == -ENOTSOCK) {
+ b->prefer_readv = true;
+ k = readv(b->input_fd, &iov, 1);
+ if (k < 0)
+ k = -errno;
+ } else
+ handle_cmsg = true;
+ }
+ if (k < 0) {
+ if (ERRNO_IS_TRANSIENT(k))
+ return 0;
+ return (int) k;
+ }
+ if (k == 0) {
+ if (handle_cmsg)
+ cmsg_close_all(&mh); /* paranoia, we shouldn't have gotten any fds on EOF */
+ return -ECONNRESET;
+ }
+
+ b->rbuffer_size += k;
+
+ if (handle_cmsg) {
+ struct cmsghdr *cmsg;
+
+ CMSG_FOREACH(cmsg, &mh)
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_RIGHTS) {
+ int j;
+
+ /* Whut? We received fds during the auth
+ * protocol? Somebody is playing games with
+ * us. Close them all, and fail */
+ j = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ close_many((int*) CMSG_DATA(cmsg), j);
+ return -EIO;
+ } else
+ log_debug("Got unexpected auxiliary data with level=%d and type=%d",
+ cmsg->cmsg_level, cmsg->cmsg_type);
+ }
+
+ r = bus_socket_auth_verify(b);
+ if (r != 0)
+ return r;
+
+ return 1;
+}
+
+void bus_socket_setup(sd_bus *b) {
+ assert(b);
+
+ /* Increase the buffers to 8 MB */
+ (void) fd_increase_rxbuf(b->input_fd, SNDBUF_SIZE);
+ (void) fd_inc_sndbuf(b->output_fd, SNDBUF_SIZE);
+
+ b->message_version = 1;
+ b->message_endian = 0;
+}
+
+static void bus_get_peercred(sd_bus *b) {
+ int r;
+
+ assert(b);
+ assert(!b->ucred_valid);
+ assert(!b->label);
+ assert(b->n_groups == SIZE_MAX);
+
+ /* Get the peer for socketpair() sockets */
+ b->ucred_valid = getpeercred(b->input_fd, &b->ucred) >= 0;
+
+ /* Get the SELinux context of the peer */
+ r = getpeersec(b->input_fd, &b->label);
+ if (r < 0 && !IN_SET(r, -EOPNOTSUPP, -ENOPROTOOPT))
+ log_debug_errno(r, "Failed to determine peer security context: %m");
+
+ /* Get the list of auxiliary groups of the peer */
+ r = getpeergroups(b->input_fd, &b->groups);
+ if (r >= 0)
+ b->n_groups = (size_t) r;
+ else if (!IN_SET(r, -EOPNOTSUPP, -ENOPROTOOPT))
+ log_debug_errno(r, "Failed to determine peer's group list: %m");
+}
+
+static int bus_socket_start_auth_client(sd_bus *b) {
+ static const char sasl_auth_anonymous[] = {
+ /*
+ * We use an arbitrary trace-string for the ANONYMOUS authentication. It can be used by the
+ * message broker to aid debugging of clients. We fully anonymize the connection and use a
+ * static default.
+ */
+ /* HEX a n o n y m o u s */
+ "\0AUTH ANONYMOUS 616e6f6e796d6f7573\r\n"
+ };
+ static const char sasl_auth_external[] = {
+ "\0AUTH EXTERNAL\r\n"
+ "DATA\r\n"
+ };
+ static const char sasl_negotiate_unix_fd[] = {
+ "NEGOTIATE_UNIX_FD\r\n"
+ };
+ static const char sasl_begin[] = {
+ "BEGIN\r\n"
+ };
+ size_t i = 0;
+
+ assert(b);
+
+ if (b->anonymous_auth)
+ b->auth_iovec[i++] = IOVEC_MAKE((char*) sasl_auth_anonymous, sizeof(sasl_auth_anonymous) - 1);
+ else
+ b->auth_iovec[i++] = IOVEC_MAKE((char*) sasl_auth_external, sizeof(sasl_auth_external) - 1);
+
+ if (b->accept_fd)
+ b->auth_iovec[i++] = IOVEC_MAKE_STRING(sasl_negotiate_unix_fd);
+
+ b->auth_iovec[i++] = IOVEC_MAKE_STRING(sasl_begin);
+
+ return bus_socket_write_auth(b);
+}
+
+int bus_socket_start_auth(sd_bus *b) {
+ assert(b);
+
+ bus_get_peercred(b);
+
+ bus_set_state(b, BUS_AUTHENTICATING);
+ b->auth_timeout = now(CLOCK_MONOTONIC) + BUS_AUTH_TIMEOUT;
+
+ if (sd_is_socket(b->input_fd, AF_UNIX, 0, 0) <= 0)
+ b->accept_fd = false;
+
+ if (b->output_fd != b->input_fd)
+ if (sd_is_socket(b->output_fd, AF_UNIX, 0, 0) <= 0)
+ b->accept_fd = false;
+
+ if (b->is_server)
+ return bus_socket_read_auth(b);
+ else
+ return bus_socket_start_auth_client(b);
+}
+
+static int bus_socket_inotify_setup(sd_bus *b) {
+ _cleanup_free_ int *new_watches = NULL;
+ _cleanup_free_ char *absolute = NULL;
+ size_t n = 0, done = 0, i;
+ unsigned max_follow = 32;
+ const char *p;
+ int wd, r;
+
+ assert(b);
+ assert(b->watch_bind);
+ assert(b->sockaddr.sa.sa_family == AF_UNIX);
+ assert(b->sockaddr.un.sun_path[0] != 0);
+
+ /* Sets up an inotify fd in case watch_bind is enabled: wait until the configured AF_UNIX file system socket
+ * appears before connecting to it. The implemented is pretty simplistic: we just subscribe to relevant changes
+ * to all prefix components of the path, and every time we get an event for that we try to reconnect again,
+ * without actually caring what precisely the event we got told us. If we still can't connect we re-subscribe
+ * to all relevant changes of anything in the path, so that our watches include any possibly newly created path
+ * components. */
+
+ if (b->inotify_fd < 0) {
+ b->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+ if (b->inotify_fd < 0)
+ return -errno;
+
+ b->inotify_fd = fd_move_above_stdio(b->inotify_fd);
+ }
+
+ /* Make sure the path is NUL terminated */
+ p = strndupa_safe(b->sockaddr.un.sun_path,
+ sizeof(b->sockaddr.un.sun_path));
+
+ /* Make sure the path is absolute */
+ r = path_make_absolute_cwd(p, &absolute);
+ if (r < 0)
+ goto fail;
+
+ /* Watch all parent directories, and don't mind any prefix that doesn't exist yet. For the innermost directory
+ * that exists we want to know when files are created or moved into it. For all parents of it we just care if
+ * they are removed or renamed. */
+
+ if (!GREEDY_REALLOC(new_watches, n + 1)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ /* Start with the top-level directory, which is a bit simpler than the rest, since it can't be a symlink, and
+ * always exists */
+ wd = inotify_add_watch(b->inotify_fd, "/", IN_CREATE|IN_MOVED_TO);
+ if (wd < 0) {
+ r = log_debug_errno(errno, "Failed to add inotify watch on /: %m");
+ goto fail;
+ } else
+ new_watches[n++] = wd;
+
+ for (;;) {
+ _cleanup_free_ char *component = NULL, *prefix = NULL, *destination = NULL;
+ size_t n_slashes, n_component;
+ char *c = NULL;
+
+ n_slashes = strspn(absolute + done, "/");
+ n_component = n_slashes + strcspn(absolute + done + n_slashes, "/");
+
+ if (n_component == 0) /* The end */
+ break;
+
+ component = strndup(absolute + done, n_component);
+ if (!component) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ /* A trailing slash? That's a directory, and not a socket then */
+ if (path_equal(component, "/")) {
+ r = -EISDIR;
+ goto fail;
+ }
+
+ /* A single dot? Let's eat this up */
+ if (path_equal(component, "/.")) {
+ done += n_component;
+ continue;
+ }
+
+ prefix = strndup(absolute, done + n_component);
+ if (!prefix) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ if (!GREEDY_REALLOC(new_watches, n + 1)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ wd = inotify_add_watch(b->inotify_fd, prefix, IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO|IN_DONT_FOLLOW);
+ log_debug("Added inotify watch for %s on bus %s: %i", prefix, strna(b->description), wd);
+
+ if (wd < 0) {
+ if (IN_SET(errno, ENOENT, ELOOP))
+ break; /* This component doesn't exist yet, or the path contains a cyclic symlink right now */
+
+ r = log_debug_errno(errno, "Failed to add inotify watch on %s: %m", empty_to_root(prefix));
+ goto fail;
+ } else
+ new_watches[n++] = wd;
+
+ /* Check if this is possibly a symlink. If so, let's follow it and watch it too. */
+ r = readlink_malloc(prefix, &destination);
+ if (r == -EINVAL) { /* not a symlink */
+ done += n_component;
+ continue;
+ }
+ if (r < 0)
+ goto fail;
+
+ if (isempty(destination)) { /* Empty symlink target? Yuck! */
+ r = -EINVAL;
+ goto fail;
+ }
+
+ if (max_follow <= 0) { /* Let's make sure we don't follow symlinks forever */
+ r = -ELOOP;
+ goto fail;
+ }
+
+ if (path_is_absolute(destination)) {
+ /* For absolute symlinks we build the new path and start anew */
+ c = strjoin(destination, absolute + done + n_component);
+ done = 0;
+ } else {
+ _cleanup_free_ char *t = NULL;
+
+ /* For relative symlinks we replace the last component, and try again */
+ t = strndup(absolute, done);
+ if (!t)
+ return -ENOMEM;
+
+ c = strjoin(t, "/", destination, absolute + done + n_component);
+ }
+ if (!c) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ free(absolute);
+ absolute = c;
+
+ max_follow--;
+ }
+
+ /* And now, let's remove all watches from the previous iteration we don't need anymore */
+ for (i = 0; i < b->n_inotify_watches; i++) {
+ bool found = false;
+ size_t j;
+
+ for (j = 0; j < n; j++)
+ if (new_watches[j] == b->inotify_watches[i]) {
+ found = true;
+ break;
+ }
+
+ if (found)
+ continue;
+
+ (void) inotify_rm_watch(b->inotify_fd, b->inotify_watches[i]);
+ }
+
+ free_and_replace(b->inotify_watches, new_watches);
+ b->n_inotify_watches = n;
+
+ return 0;
+
+fail:
+ bus_close_inotify_fd(b);
+ return r;
+}
+
+int bus_socket_connect(sd_bus *b) {
+ bool inotify_done = false;
+ int r;
+
+ assert(b);
+
+ for (;;) {
+ assert(b->input_fd < 0);
+ assert(b->output_fd < 0);
+ assert(b->sockaddr.sa.sa_family != AF_UNSPEC);
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *pretty = NULL;
+ (void) sockaddr_pretty(&b->sockaddr.sa, b->sockaddr_size, false, true, &pretty);
+ log_debug("sd-bus: starting bus%s%s by connecting to %s...",
+ b->description ? " " : "", strempty(b->description), strnull(pretty));
+ }
+
+ b->input_fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (b->input_fd < 0)
+ return -errno;
+
+ b->input_fd = fd_move_above_stdio(b->input_fd);
+
+ b->output_fd = b->input_fd;
+ bus_socket_setup(b);
+
+ if (connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size) < 0) {
+ if (errno == EINPROGRESS) {
+
+ /* If we have any inotify watches open, close them now, we don't need them anymore, as
+ * we have successfully initiated a connection */
+ bus_close_inotify_fd(b);
+
+ /* Note that very likely we are already in BUS_OPENING state here, as we enter it when
+ * we start parsing the address string. The only reason we set the state explicitly
+ * here, is to undo BUS_WATCH_BIND, in case we did the inotify magic. */
+ bus_set_state(b, BUS_OPENING);
+ return 1;
+ }
+
+ if (IN_SET(errno, ENOENT, ECONNREFUSED) && /* ENOENT → unix socket doesn't exist at all; ECONNREFUSED → unix socket stale */
+ b->watch_bind &&
+ b->sockaddr.sa.sa_family == AF_UNIX &&
+ b->sockaddr.un.sun_path[0] != 0) {
+
+ /* This connection attempt failed, let's release the socket for now, and start with a
+ * fresh one when reconnecting. */
+ bus_close_io_fds(b);
+
+ if (inotify_done) {
+ /* inotify set up already, don't do it again, just return now, and remember
+ * that we are waiting for inotify events now. */
+ bus_set_state(b, BUS_WATCH_BIND);
+ return 1;
+ }
+
+ /* This is a file system socket, and the inotify logic is enabled. Let's create the necessary inotify fd. */
+ r = bus_socket_inotify_setup(b);
+ if (r < 0)
+ return r;
+
+ /* Let's now try to connect a second time, because in theory there's otherwise a race
+ * here: the socket might have been created in the time between our first connect() and
+ * the time we set up the inotify logic. But let's remember that we set up inotify now,
+ * so that we don't do the connect() more than twice. */
+ inotify_done = true;
+
+ } else
+ return -errno;
+ } else
+ break;
+ }
+
+ /* Yay, established, we don't need no inotify anymore! */
+ bus_close_inotify_fd(b);
+
+ return bus_socket_start_auth(b);
+}
+
+int bus_socket_exec(sd_bus *b) {
+ int s[2], r;
+
+ assert(b);
+ assert(b->input_fd < 0);
+ assert(b->output_fd < 0);
+ assert(b->exec_path);
+ assert(b->busexec_pid == 0);
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *line = NULL;
+
+ if (b->exec_argv)
+ line = quote_command_line(b->exec_argv, SHELL_ESCAPE_EMPTY);
+
+ log_debug("sd-bus: starting bus%s%s with %s%s",
+ b->description ? " " : "", strempty(b->description),
+ line ?: b->exec_path,
+ b->exec_argv && !line ? "…" : "");
+ }
+
+ r = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, s);
+ if (r < 0)
+ return -errno;
+
+ r = safe_fork_full("(sd-busexec)", s+1, 1, FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS, &b->busexec_pid);
+ if (r < 0) {
+ safe_close_pair(s);
+ return r;
+ }
+ if (r == 0) {
+ /* Child */
+
+ r = rearrange_stdio(s[1], s[1], STDERR_FILENO);
+ TAKE_FD(s[1]);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+
+ (void) rlimit_nofile_safe();
+
+ if (b->exec_argv)
+ execvp(b->exec_path, b->exec_argv);
+ else
+ execvp(b->exec_path, STRV_MAKE(b->exec_path));
+
+ _exit(EXIT_FAILURE);
+ }
+
+ safe_close(s[1]);
+ b->output_fd = b->input_fd = fd_move_above_stdio(s[0]);
+
+ bus_socket_setup(b);
+
+ return bus_socket_start_auth(b);
+}
+
+int bus_socket_take_fd(sd_bus *b) {
+ assert(b);
+
+ bus_socket_setup(b);
+
+ return bus_socket_start_auth(b);
+}
+
+int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) {
+ struct iovec *iov;
+ ssize_t k;
+ size_t n;
+ unsigned j;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(idx);
+ assert(IN_SET(bus->state, BUS_RUNNING, BUS_HELLO));
+
+ if (*idx >= BUS_MESSAGE_SIZE(m))
+ return 0;
+
+ r = bus_message_setup_iovec(m);
+ if (r < 0)
+ return r;
+
+ n = m->n_iovec * sizeof(struct iovec);
+ iov = newa(struct iovec, n);
+ memcpy_safe(iov, m->iovec, n);
+
+ j = 0;
+ iovec_advance(iov, &j, *idx);
+
+ if (bus->prefer_writev)
+ k = writev(bus->output_fd, iov, m->n_iovec);
+ else {
+ struct msghdr mh = {
+ .msg_iov = iov,
+ .msg_iovlen = m->n_iovec,
+ };
+
+ if (m->n_fds > 0 && *idx == 0) {
+ struct cmsghdr *control;
+
+ mh.msg_controllen = CMSG_SPACE(sizeof(int) * m->n_fds);
+ mh.msg_control = alloca0(mh.msg_controllen);
+ control = CMSG_FIRSTHDR(&mh);
+ control->cmsg_len = CMSG_LEN(sizeof(int) * m->n_fds);
+ control->cmsg_level = SOL_SOCKET;
+ control->cmsg_type = SCM_RIGHTS;
+ memcpy(CMSG_DATA(control), m->fds, sizeof(int) * m->n_fds);
+ }
+
+ k = sendmsg(bus->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL);
+ if (k < 0 && errno == ENOTSOCK) {
+ bus->prefer_writev = true;
+ k = writev(bus->output_fd, iov, m->n_iovec);
+ }
+ }
+
+ if (k < 0)
+ return ERRNO_IS_TRANSIENT(errno) ? 0 : -errno;
+
+ *idx += (size_t) k;
+ return 1;
+}
+
+static int bus_socket_read_message_need(sd_bus *bus, size_t *need) {
+ uint32_t a, b;
+ uint8_t e;
+ uint64_t sum;
+
+ assert(bus);
+ assert(need);
+ assert(IN_SET(bus->state, BUS_RUNNING, BUS_HELLO));
+
+ if (bus->rbuffer_size < sizeof(struct bus_header)) {
+ *need = sizeof(struct bus_header) + 8;
+
+ /* Minimum message size:
+ *
+ * Header +
+ *
+ * Method Call: +2 string headers
+ * Signal: +3 string headers
+ * Method Error: +1 string headers
+ * +1 uint32 headers
+ * Method Reply: +1 uint32 headers
+ *
+ * A string header is at least 9 bytes
+ * A uint32 header is at least 8 bytes
+ *
+ * Hence the minimum message size of a valid message
+ * is header + 8 bytes */
+
+ return 0;
+ }
+
+ a = ((const uint32_t*) bus->rbuffer)[1];
+ b = ((const uint32_t*) bus->rbuffer)[3];
+
+ e = ((const uint8_t*) bus->rbuffer)[0];
+ if (e == BUS_LITTLE_ENDIAN) {
+ a = le32toh(a);
+ b = le32toh(b);
+ } else if (e == BUS_BIG_ENDIAN) {
+ a = be32toh(a);
+ b = be32toh(b);
+ } else
+ return -EBADMSG;
+
+ sum = (uint64_t) sizeof(struct bus_header) + (uint64_t) ALIGN8(b) + (uint64_t) a;
+ if (sum >= BUS_MESSAGE_SIZE_MAX)
+ return -ENOBUFS;
+
+ *need = (size_t) sum;
+ return 0;
+}
+
+static int bus_socket_make_message(sd_bus *bus, size_t size) {
+ sd_bus_message *t = NULL;
+ void *b;
+ int r;
+
+ assert(bus);
+ assert(bus->rbuffer_size >= size);
+ assert(IN_SET(bus->state, BUS_RUNNING, BUS_HELLO));
+
+ r = bus_rqueue_make_room(bus);
+ if (r < 0)
+ return r;
+
+ if (bus->rbuffer_size > size) {
+ b = memdup((const uint8_t*) bus->rbuffer + size,
+ bus->rbuffer_size - size);
+ if (!b)
+ return -ENOMEM;
+ } else
+ b = NULL;
+
+ r = bus_message_from_malloc(bus,
+ bus->rbuffer, size,
+ bus->fds, bus->n_fds,
+ NULL,
+ &t);
+ if (r == -EBADMSG) {
+ log_debug_errno(r, "Received invalid message from connection %s, dropping.", strna(bus->description));
+ free(bus->rbuffer); /* We want to drop current rbuffer and proceed with whatever remains in b */
+ } else if (r < 0) {
+ free(b);
+ return r;
+ }
+
+ /* rbuffer ownership was either transferred to t, or we got EBADMSG and dropped it. */
+ bus->rbuffer = b;
+ bus->rbuffer_size -= size;
+
+ bus->fds = NULL;
+ bus->n_fds = 0;
+
+ if (t) {
+ t->read_counter = ++bus->read_counter;
+ bus->rqueue[bus->rqueue_size++] = bus_message_ref_queued(t, bus);
+ sd_bus_message_unref(t);
+ }
+
+ return 1;
+}
+
+int bus_socket_read_message(sd_bus *bus) {
+ struct msghdr mh;
+ struct iovec iov = {};
+ ssize_t k;
+ size_t need;
+ int r;
+ void *b;
+ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int) * BUS_FDS_MAX)) control;
+ bool handle_cmsg = false;
+
+ assert(bus);
+ assert(IN_SET(bus->state, BUS_RUNNING, BUS_HELLO));
+
+ r = bus_socket_read_message_need(bus, &need);
+ if (r < 0)
+ return r;
+
+ if (bus->rbuffer_size >= need)
+ return bus_socket_make_message(bus, need);
+
+ b = realloc(bus->rbuffer, need);
+ if (!b)
+ return -ENOMEM;
+
+ bus->rbuffer = b;
+
+ iov = IOVEC_MAKE((uint8_t *)bus->rbuffer + bus->rbuffer_size, need - bus->rbuffer_size);
+
+ if (bus->prefer_readv) {
+ k = readv(bus->input_fd, &iov, 1);
+ if (k < 0)
+ k = -errno;
+ } else {
+ mh = (struct msghdr) {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+
+ k = recvmsg_safe(bus->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+ if (k == -ENOTSOCK) {
+ bus->prefer_readv = true;
+ k = readv(bus->input_fd, &iov, 1);
+ if (k < 0)
+ k = -errno;
+ } else
+ handle_cmsg = true;
+ }
+ if (k < 0) {
+ if (ERRNO_IS_TRANSIENT(k))
+ return 0;
+ return (int) k;
+ }
+ if (k == 0) {
+ if (handle_cmsg)
+ cmsg_close_all(&mh); /* On EOF we shouldn't have gotten an fd, but let's make sure */
+ return -ECONNRESET;
+ }
+
+ bus->rbuffer_size += k;
+
+ if (handle_cmsg) {
+ struct cmsghdr *cmsg;
+
+ CMSG_FOREACH(cmsg, &mh)
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_RIGHTS) {
+ int n, *f, i;
+
+ n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+
+ if (!bus->can_fds) {
+ /* Whut? We received fds but this
+ * isn't actually enabled? Close them,
+ * and fail */
+
+ close_many((int*) CMSG_DATA(cmsg), n);
+ return -EIO;
+ }
+
+ f = reallocarray(bus->fds, bus->n_fds + n, sizeof(int));
+ if (!f) {
+ close_many((int*) CMSG_DATA(cmsg), n);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < n; i++)
+ f[bus->n_fds++] = fd_move_above_stdio(((int*) CMSG_DATA(cmsg))[i]);
+ bus->fds = f;
+ } else
+ log_debug("Got unexpected auxiliary data with level=%d and type=%d",
+ cmsg->cmsg_level, cmsg->cmsg_type);
+ }
+
+ r = bus_socket_read_message_need(bus, &need);
+ if (r < 0)
+ return r;
+
+ if (bus->rbuffer_size >= need)
+ return bus_socket_make_message(bus, need);
+
+ return 1;
+}
+
+int bus_socket_process_opening(sd_bus *b) {
+ int error = 0, events, r;
+ socklen_t slen = sizeof(error);
+
+ assert(b->state == BUS_OPENING);
+
+ events = fd_wait_for_event(b->output_fd, POLLOUT, 0);
+ if (events < 0)
+ return events;
+ if (!(events & (POLLOUT|POLLERR|POLLHUP)))
+ return 0;
+
+ r = getsockopt(b->output_fd, SOL_SOCKET, SO_ERROR, &error, &slen);
+ if (r < 0)
+ b->last_connect_error = errno;
+ else if (error != 0)
+ b->last_connect_error = error;
+ else if (events & (POLLERR|POLLHUP))
+ b->last_connect_error = ECONNREFUSED;
+ else
+ return bus_socket_start_auth(b);
+
+ return bus_next_address(b);
+}
+
+int bus_socket_process_authenticating(sd_bus *b) {
+ int r;
+
+ assert(b);
+ assert(b->state == BUS_AUTHENTICATING);
+
+ if (now(CLOCK_MONOTONIC) >= b->auth_timeout)
+ return -ETIMEDOUT;
+
+ r = bus_socket_write_auth(b);
+ if (r != 0)
+ return r;
+
+ return bus_socket_read_auth(b);
+}
+
+int bus_socket_process_watch_bind(sd_bus *b) {
+ int r, q;
+
+ assert(b);
+ assert(b->state == BUS_WATCH_BIND);
+ assert(b->inotify_fd >= 0);
+
+ r = flush_fd(b->inotify_fd);
+ if (r <= 0)
+ return r;
+
+ log_debug("Got inotify event on bus %s.", strna(b->description));
+
+ /* We flushed events out of the inotify fd. In that case, maybe the socket is valid now? Let's try to connect
+ * to it again */
+
+ r = bus_socket_connect(b);
+ if (r < 0)
+ return r;
+
+ q = bus_attach_io_events(b);
+ if (q < 0)
+ return q;
+
+ q = bus_attach_inotify_event(b);
+ if (q < 0)
+ return q;
+
+ return r;
+}
diff --git a/src/libsystemd/sd-bus/bus-socket.h b/src/libsystemd/sd-bus/bus-socket.h
new file mode 100644
index 0000000..52bc404
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-socket.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-bus.h"
+
+void bus_socket_setup(sd_bus *b);
+
+int bus_socket_connect(sd_bus *b);
+int bus_socket_exec(sd_bus *b);
+int bus_socket_take_fd(sd_bus *b);
+int bus_socket_start_auth(sd_bus *b);
+
+int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx);
+int bus_socket_read_message(sd_bus *bus);
+
+int bus_socket_process_opening(sd_bus *b);
+int bus_socket_process_authenticating(sd_bus *b);
+int bus_socket_process_watch_bind(sd_bus *b);
+
+bool bus_socket_auth_needs_write(sd_bus *b);
diff --git a/src/libsystemd/sd-bus/bus-track.c b/src/libsystemd/sd-bus/bus-track.c
new file mode 100644
index 0000000..f9c59a1
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-track.c
@@ -0,0 +1,495 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-internal.h"
+#include "bus-track.h"
+#include "string-util.h"
+
+struct track_item {
+ unsigned n_ref;
+ char *name;
+ sd_bus_slot *slot;
+};
+
+struct sd_bus_track {
+ unsigned n_ref;
+ unsigned n_adding; /* are we in the process of adding a new name? */
+ sd_bus *bus;
+ sd_bus_track_handler_t handler;
+ void *userdata;
+ Hashmap *names;
+ LIST_FIELDS(sd_bus_track, queue);
+ Iterator iterator;
+ bool in_list:1; /* In bus->tracks? */
+ bool in_queue:1; /* In bus->track_queue? */
+ bool modified:1;
+ bool recursive:1;
+ sd_bus_destroy_t destroy_callback;
+
+ LIST_FIELDS(sd_bus_track, tracks);
+};
+
+#define MATCH_FOR_NAME(name) \
+ strjoina("type='signal'," \
+ "sender='org.freedesktop.DBus'," \
+ "path='/org/freedesktop/DBus'," \
+ "interface='org.freedesktop.DBus'," \
+ "member='NameOwnerChanged'," \
+ "arg0='", name, "'")
+
+static struct track_item* track_item_free(struct track_item *i) {
+ if (!i)
+ return NULL;
+
+ sd_bus_slot_unref(i->slot);
+ free(i->name);
+ return mfree(i);
+}
+
+DEFINE_PRIVATE_TRIVIAL_UNREF_FUNC(struct track_item, track_item, track_item_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct track_item*, track_item_unref);
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(track_item_hash_ops, char, string_hash_func, string_compare_func,
+ struct track_item, track_item_free);
+
+static void bus_track_add_to_queue(sd_bus_track *track) {
+ assert(track);
+
+ /* Adds the bus track object to the queue of objects we should dispatch next, subject to a number of
+ * conditions. */
+
+ /* Already in the queue? */
+ if (track->in_queue)
+ return;
+
+ /* if we are currently in the process of adding a new name, then let's not enqueue this just yet, let's wait
+ * until the addition is complete. */
+ if (track->n_adding > 0)
+ return;
+
+ /* still referenced? */
+ if (hashmap_size(track->names) > 0)
+ return;
+
+ /* Nothing to call? */
+ if (!track->handler)
+ return;
+
+ /* Already closed? */
+ if (!track->in_list)
+ return;
+
+ LIST_PREPEND(queue, track->bus->track_queue, track);
+ track->in_queue = true;
+}
+
+static void bus_track_remove_from_queue(sd_bus_track *track) {
+ assert(track);
+
+ if (!track->in_queue)
+ return;
+
+ LIST_REMOVE(queue, track->bus->track_queue, track);
+ track->in_queue = false;
+}
+
+static int bus_track_remove_name_fully(sd_bus_track *track, const char *name) {
+ struct track_item *i;
+
+ assert(track);
+ assert(name);
+
+ i = hashmap_remove(track->names, name);
+ if (!i)
+ return 0;
+
+ track_item_free(i);
+
+ bus_track_add_to_queue(track);
+
+ track->modified = true;
+ return 1;
+}
+
+_public_ int sd_bus_track_new(
+ sd_bus *bus,
+ sd_bus_track **track,
+ sd_bus_track_handler_t handler,
+ void *userdata) {
+
+ sd_bus_track *t;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(track, -EINVAL);
+
+ if (!bus->bus_client)
+ return -EINVAL;
+
+ t = new0(sd_bus_track, 1);
+ if (!t)
+ return -ENOMEM;
+
+ t->n_ref = 1;
+ t->handler = handler;
+ t->userdata = userdata;
+ t->bus = sd_bus_ref(bus);
+
+ LIST_PREPEND(tracks, bus->tracks, t);
+ t->in_list = true;
+
+ bus_track_add_to_queue(t);
+
+ *track = t;
+ return 0;
+}
+
+static sd_bus_track *track_free(sd_bus_track *track) {
+ assert(track);
+
+ if (track->in_list)
+ LIST_REMOVE(tracks, track->bus->tracks, track);
+
+ bus_track_remove_from_queue(track);
+ track->names = hashmap_free(track->names);
+ track->bus = sd_bus_unref(track->bus);
+
+ if (track->destroy_callback)
+ track->destroy_callback(track->userdata);
+
+ return mfree(track);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_bus_track, sd_bus_track, track_free);
+
+static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ sd_bus_track *track = ASSERT_PTR(userdata);
+ const char *name;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "sss", &name, NULL, NULL);
+ if (r < 0)
+ return 0;
+
+ bus_track_remove_name_fully(track, name);
+ return 0;
+}
+
+_public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) {
+ _cleanup_(track_item_unrefp) struct track_item *n = NULL;
+ struct track_item *i;
+ const char *match;
+ int r;
+
+ assert_return(track, -EINVAL);
+ assert_return(service_name_is_valid(name), -EINVAL);
+
+ i = hashmap_get(track->names, name);
+ if (i) {
+ if (track->recursive) {
+ assert(i->n_ref > 0);
+
+ /* Manual overflow check (instead of a DEFINE_TRIVIAL_REF_FUNC() helper or so), so
+ * that we can return a proper error, given this is almost always called in a
+ * directly client controllable way, and thus better should never hit an assertion
+ * here. */
+ if (i->n_ref >= UINT_MAX)
+ return -EOVERFLOW;
+
+ i->n_ref++;
+ }
+
+ bus_track_remove_from_queue(track);
+ return 0;
+ }
+
+ r = hashmap_ensure_allocated(&track->names, &track_item_hash_ops);
+ if (r < 0)
+ return r;
+
+ n = new(struct track_item, 1);
+ if (!n)
+ return -ENOMEM;
+
+ *n = (struct track_item) {
+ .n_ref = 1,
+ };
+
+ n->name = strdup(name);
+ if (!n->name)
+ return -ENOMEM;
+
+ /* First, subscribe to this name */
+ match = MATCH_FOR_NAME(name);
+
+ bus_track_remove_from_queue(track); /* don't dispatch this while we work in it */
+
+ r = sd_bus_add_match_async(track->bus, &n->slot, match, on_name_owner_changed, NULL, track);
+ if (r < 0) {
+ bus_track_add_to_queue(track);
+ return r;
+ }
+
+ r = hashmap_put(track->names, n->name, n);
+ if (r < 0) {
+ bus_track_add_to_queue(track);
+ return r;
+ }
+
+ /* Second, check if it is currently existing, or maybe doesn't, or maybe disappeared already. */
+ track->n_adding++; /* again, make sure this isn't dispatch while we are working in it */
+ r = sd_bus_get_name_creds(track->bus, name, 0, NULL);
+ track->n_adding--;
+ if (r < 0) {
+ hashmap_remove(track->names, name);
+ bus_track_add_to_queue(track);
+ return r;
+ }
+
+ TAKE_PTR(n);
+
+ bus_track_remove_from_queue(track);
+ track->modified = true;
+
+ return 1;
+}
+
+_public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) {
+ struct track_item *i;
+
+ assert_return(name, -EINVAL);
+
+ if (!track) /* Treat a NULL track object as an empty track object */
+ return 0;
+
+ i = hashmap_get(track->names, name);
+ if (!i)
+ return 0;
+
+ assert(i->n_ref >= 1);
+ if (i->n_ref <= 1)
+ return bus_track_remove_name_fully(track, name);
+
+ track_item_unref(i);
+
+ return 1;
+}
+
+_public_ unsigned sd_bus_track_count(sd_bus_track *track) {
+
+ if (!track) /* Let's consider a NULL object equivalent to an empty object */
+ return 0;
+
+ /* This signature really should have returned an int, so that we can propagate errors. But well, ... Also, note
+ * that this returns the number of names being watched, and multiple references to the same name are not
+ * counted. */
+
+ return hashmap_size(track->names);
+}
+
+_public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) {
+ assert_return(name, NULL);
+
+ if (!track) /* Let's consider a NULL object equivalent to an empty object */
+ return NULL;
+
+ return hashmap_contains(track->names, name) ? name : NULL;
+}
+
+_public_ const char* sd_bus_track_first(sd_bus_track *track) {
+ const char *n = NULL;
+
+ if (!track)
+ return NULL;
+
+ track->modified = false;
+ track->iterator = ITERATOR_FIRST;
+
+ (void) hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
+ return n;
+}
+
+_public_ const char* sd_bus_track_next(sd_bus_track *track) {
+ const char *n = NULL;
+
+ if (!track)
+ return NULL;
+
+ if (track->modified)
+ return NULL;
+
+ (void) hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n);
+ return n;
+}
+
+_public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) {
+ const char *sender;
+
+ assert_return(track, -EINVAL);
+ assert_return(m, -EINVAL);
+
+ if (sd_bus_message_get_bus(m) != track->bus)
+ return -EINVAL;
+
+ sender = sd_bus_message_get_sender(m);
+ if (!sender)
+ return -EINVAL;
+
+ return sd_bus_track_add_name(track, sender);
+}
+
+_public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) {
+ const char *sender;
+
+ assert_return(m, -EINVAL);
+
+ if (!track) /* Treat a NULL track object as an empty track object */
+ return 0;
+
+ if (sd_bus_message_get_bus(m) != track->bus)
+ return -EINVAL;
+
+ sender = sd_bus_message_get_sender(m);
+ if (!sender)
+ return -EINVAL;
+
+ return sd_bus_track_remove_name(track, sender);
+}
+
+_public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) {
+ assert_return(track, NULL);
+
+ return track->bus;
+}
+
+void bus_track_dispatch(sd_bus_track *track) {
+ int r;
+
+ assert(track);
+ assert(track->handler);
+
+ bus_track_remove_from_queue(track);
+
+ sd_bus_track_ref(track);
+
+ r = track->handler(track, track->userdata);
+ if (r < 0)
+ log_debug_errno(r, "Failed to process track handler: %m");
+ else if (r == 0)
+ bus_track_add_to_queue(track);
+
+ sd_bus_track_unref(track);
+}
+
+void bus_track_close(sd_bus_track *track) {
+ assert(track);
+
+ /* Called whenever our bus connected is closed. If so, and our track object is non-empty, dispatch it
+ * immediately, as we are closing now, but first flush out all names. */
+
+ if (!track->in_list)
+ return; /* We already closed this one, don't close it again. */
+
+ /* Remember that this one is closed now */
+ LIST_REMOVE(tracks, track->bus->tracks, track);
+ track->in_list = false;
+
+ /* If there's no name in this one anyway, we don't have to dispatch */
+ if (hashmap_isempty(track->names))
+ return;
+
+ /* Let's flush out all names */
+ hashmap_clear(track->names);
+
+ /* Invoke handler */
+ if (track->handler)
+ bus_track_dispatch(track);
+}
+
+_public_ void *sd_bus_track_get_userdata(sd_bus_track *track) {
+ assert_return(track, NULL);
+
+ return track->userdata;
+}
+
+_public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) {
+ void *ret;
+
+ assert_return(track, NULL);
+
+ ret = track->userdata;
+ track->userdata = userdata;
+
+ return ret;
+}
+
+_public_ int sd_bus_track_set_destroy_callback(sd_bus_track *track, sd_bus_destroy_t callback) {
+ assert_return(track, -EINVAL);
+
+ track->destroy_callback = callback;
+ return 0;
+}
+
+_public_ int sd_bus_track_get_destroy_callback(sd_bus_track *track, sd_bus_destroy_t *ret) {
+ assert_return(track, -EINVAL);
+
+ if (ret)
+ *ret = track->destroy_callback;
+
+ return !!track->destroy_callback;
+}
+
+_public_ int sd_bus_track_set_recursive(sd_bus_track *track, int b) {
+ assert_return(track, -EINVAL);
+
+ if (track->recursive == !!b)
+ return 0;
+
+ if (!hashmap_isempty(track->names))
+ return -EBUSY;
+
+ track->recursive = b;
+ return 0;
+}
+
+_public_ int sd_bus_track_get_recursive(sd_bus_track *track) {
+ assert_return(track, -EINVAL);
+
+ return track->recursive;
+}
+
+_public_ int sd_bus_track_count_sender(sd_bus_track *track, sd_bus_message *m) {
+ const char *sender;
+
+ assert_return(m, -EINVAL);
+
+ if (!track) /* Let's consider a NULL object equivalent to an empty object */
+ return 0;
+
+ if (sd_bus_message_get_bus(m) != track->bus)
+ return -EINVAL;
+
+ sender = sd_bus_message_get_sender(m);
+ if (!sender)
+ return -EINVAL;
+
+ return sd_bus_track_count_name(track, sender);
+}
+
+_public_ int sd_bus_track_count_name(sd_bus_track *track, const char *name) {
+ struct track_item *i;
+
+ assert_return(service_name_is_valid(name), -EINVAL);
+
+ if (!track) /* Let's consider a NULL object equivalent to an empty object */
+ return 0;
+
+ i = hashmap_get(track->names, name);
+ if (!i)
+ return 0;
+
+ return i->n_ref;
+}
diff --git a/src/libsystemd/sd-bus/bus-track.h b/src/libsystemd/sd-bus/bus-track.h
new file mode 100644
index 0000000..8dae1f3
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-track.h
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+void bus_track_dispatch(sd_bus_track *track);
+void bus_track_close(sd_bus_track *track);
diff --git a/src/libsystemd/sd-bus/bus-type.c b/src/libsystemd/sd-bus/bus-type.c
new file mode 100644
index 0000000..6a0f53d
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-type.c
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "sd-bus.h"
+
+#include "bus-internal.h"
+#include "bus-type.h"
+
+bool bus_type_is_valid(char c) {
+ static const char valid[] = {
+ SD_BUS_TYPE_BYTE,
+ SD_BUS_TYPE_BOOLEAN,
+ SD_BUS_TYPE_INT16,
+ SD_BUS_TYPE_UINT16,
+ SD_BUS_TYPE_INT32,
+ SD_BUS_TYPE_UINT32,
+ SD_BUS_TYPE_INT64,
+ SD_BUS_TYPE_UINT64,
+ SD_BUS_TYPE_DOUBLE,
+ SD_BUS_TYPE_STRING,
+ SD_BUS_TYPE_OBJECT_PATH,
+ SD_BUS_TYPE_SIGNATURE,
+ SD_BUS_TYPE_ARRAY,
+ SD_BUS_TYPE_VARIANT,
+ SD_BUS_TYPE_STRUCT,
+ SD_BUS_TYPE_DICT_ENTRY,
+ SD_BUS_TYPE_UNIX_FD
+ };
+
+ return !!memchr(valid, c, sizeof(valid));
+}
+
+bool bus_type_is_basic(char c) {
+ static const char valid[] = {
+ SD_BUS_TYPE_BYTE,
+ SD_BUS_TYPE_BOOLEAN,
+ SD_BUS_TYPE_INT16,
+ SD_BUS_TYPE_UINT16,
+ SD_BUS_TYPE_INT32,
+ SD_BUS_TYPE_UINT32,
+ SD_BUS_TYPE_INT64,
+ SD_BUS_TYPE_UINT64,
+ SD_BUS_TYPE_DOUBLE,
+ SD_BUS_TYPE_STRING,
+ SD_BUS_TYPE_OBJECT_PATH,
+ SD_BUS_TYPE_SIGNATURE,
+ SD_BUS_TYPE_UNIX_FD
+ };
+
+ return !!memchr(valid, c, sizeof(valid));
+}
+
+bool bus_type_is_trivial(char c) {
+ static const char valid[] = {
+ SD_BUS_TYPE_BYTE,
+ SD_BUS_TYPE_BOOLEAN,
+ SD_BUS_TYPE_INT16,
+ SD_BUS_TYPE_UINT16,
+ SD_BUS_TYPE_INT32,
+ SD_BUS_TYPE_UINT32,
+ SD_BUS_TYPE_INT64,
+ SD_BUS_TYPE_UINT64,
+ SD_BUS_TYPE_DOUBLE
+ };
+
+ return !!memchr(valid, c, sizeof(valid));
+}
+
+bool bus_type_is_container(char c) {
+ static const char valid[] = {
+ SD_BUS_TYPE_ARRAY,
+ SD_BUS_TYPE_VARIANT,
+ SD_BUS_TYPE_STRUCT,
+ SD_BUS_TYPE_DICT_ENTRY
+ };
+
+ return !!memchr(valid, c, sizeof(valid));
+}
+
+int bus_type_get_alignment(char c) {
+
+ switch (c) {
+ case SD_BUS_TYPE_BYTE:
+ case SD_BUS_TYPE_SIGNATURE:
+ case SD_BUS_TYPE_VARIANT:
+ return 1;
+
+ case SD_BUS_TYPE_INT16:
+ case SD_BUS_TYPE_UINT16:
+ return 2;
+
+ case SD_BUS_TYPE_BOOLEAN:
+ case SD_BUS_TYPE_INT32:
+ case SD_BUS_TYPE_UINT32:
+ case SD_BUS_TYPE_STRING:
+ case SD_BUS_TYPE_OBJECT_PATH:
+ case SD_BUS_TYPE_ARRAY:
+ case SD_BUS_TYPE_UNIX_FD:
+ return 4;
+
+ case SD_BUS_TYPE_INT64:
+ case SD_BUS_TYPE_UINT64:
+ case SD_BUS_TYPE_DOUBLE:
+ case SD_BUS_TYPE_STRUCT:
+ case SD_BUS_TYPE_STRUCT_BEGIN:
+ case SD_BUS_TYPE_DICT_ENTRY:
+ case SD_BUS_TYPE_DICT_ENTRY_BEGIN:
+ return 8;
+ }
+
+ return -EINVAL;
+}
+
+int bus_type_get_size(char c) {
+
+ switch (c) {
+ case SD_BUS_TYPE_BYTE:
+ return 1;
+
+ case SD_BUS_TYPE_INT16:
+ case SD_BUS_TYPE_UINT16:
+ return 2;
+
+ case SD_BUS_TYPE_BOOLEAN:
+ case SD_BUS_TYPE_INT32:
+ case SD_BUS_TYPE_UINT32:
+ case SD_BUS_TYPE_UNIX_FD:
+ return 4;
+
+ case SD_BUS_TYPE_INT64:
+ case SD_BUS_TYPE_UINT64:
+ case SD_BUS_TYPE_DOUBLE:
+ return 8;
+ }
+
+ return -EINVAL;
+}
+
+_public_ int sd_bus_interface_name_is_valid(const char *p) {
+ assert_return(p, -EINVAL);
+
+ return interface_name_is_valid(p);
+}
+
+_public_ int sd_bus_service_name_is_valid(const char *p) {
+ assert_return(p, -EINVAL);
+
+ return service_name_is_valid(p);
+}
+
+_public_ int sd_bus_member_name_is_valid(const char *p) {
+ assert_return(p, -EINVAL);
+
+ return member_name_is_valid(p);
+}
+
+_public_ int sd_bus_object_path_is_valid(const char *p) {
+ assert_return(p, -EINVAL);
+
+ return object_path_is_valid(p);
+}
diff --git a/src/libsystemd/sd-bus/bus-type.h b/src/libsystemd/sd-bus/bus-type.h
new file mode 100644
index 0000000..490108a
--- /dev/null
+++ b/src/libsystemd/sd-bus/bus-type.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+#include "macro.h"
+
+bool bus_type_is_valid(char c) _const_;
+bool bus_type_is_basic(char c) _const_;
+/* "trivial" is systemd's term for what the D-Bus Specification calls
+ * a "fixed type": that is, a basic type of fixed length */
+bool bus_type_is_trivial(char c) _const_;
+bool bus_type_is_container(char c) _const_;
+
+int bus_type_get_alignment(char c) _const_;
+int bus_type_get_size(char c) _const_;
diff --git a/src/libsystemd/sd-bus/fuzz-bus-match.c b/src/libsystemd/sd-bus/fuzz-bus-match.c
new file mode 100644
index 0000000..f74394b
--- /dev/null
+++ b/src/libsystemd/sd-bus/fuzz-bus-match.c
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "bus-internal.h"
+#include "bus-match.h"
+#include "env-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fuzz.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_free_ char *out = NULL; /* out should be freed after g */
+ size_t out_size;
+ _cleanup_fclose_ FILE *g = NULL;
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ if (outside_size_range(size, 0, 65536))
+ return 0;
+
+ /* We don't want to fill the logs with messages about parse errors.
+ * Disable most logging if not running standalone */
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ r = sd_bus_new(&bus);
+ assert_se(r >= 0);
+
+ struct bus_match_node root = {
+ .type = BUS_MATCH_ROOT,
+ };
+
+ /* Note that we use the pointer to match_callback substructure, but the code
+ * uses container_of() to access outside of the passed-in type. */
+ sd_bus_slot slot = {
+ .type = BUS_MATCH_CALLBACK,
+ .match_callback = {},
+ };
+
+ if (getenv_bool("SYSTEMD_FUZZ_OUTPUT") <= 0)
+ assert_se(g = open_memstream_unlocked(&out, &out_size));
+
+ for (size_t offset = 0; offset < size; ) {
+ _cleanup_free_ char *line = NULL;
+ char *end;
+
+ end = memchr((char*) data + offset, '\n', size - offset);
+
+ line = memdup_suffix0((char*) data + offset,
+ end ? end - (char*) data - offset : size - offset);
+ if (!line)
+ return log_oom_debug();
+
+ offset = end ? (size_t) (end - (char*) data + 1) : size;
+
+ struct bus_match_component *components;
+ unsigned n_components;
+ r = bus_match_parse(line, &components, &n_components);
+ if (IN_SET(r, -EINVAL, -ENOMEM)) {
+ log_debug_errno(r, "Failed to parse line: %m");
+ continue;
+ }
+ assert_se(r >= 0); /* We only expect EINVAL and ENOMEM errors, or success. */
+
+ log_debug("Parsed %u components.", n_components);
+
+ _cleanup_free_ char *again = bus_match_to_string(components, n_components);
+ if (!again) {
+ bus_match_parse_free(components, n_components);
+ log_oom();
+ break;
+ }
+
+ if (g)
+ fprintf(g, "%s\n", again);
+
+ r = bus_match_add(&root, components, n_components, &slot.match_callback);
+ bus_match_parse_free(components, n_components);
+ if (r < 0) {
+ log_error_errno(r, "Failed to add match: %m");
+ break;
+ }
+ }
+
+ bus_match_dump(g ?: stdout, &root, 0); /* We do this even on failure, to check consistency after error. */
+ bus_match_free(&root);
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/fuzz-bus-match.options b/src/libsystemd/sd-bus/fuzz-bus-match.options
new file mode 100644
index 0000000..678d526
--- /dev/null
+++ b/src/libsystemd/sd-bus/fuzz-bus-match.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/src/libsystemd/sd-bus/fuzz-bus-message.c b/src/libsystemd/sd-bus/fuzz-bus-message.c
new file mode 100644
index 0000000..af3dbf4
--- /dev/null
+++ b/src/libsystemd/sd-bus/fuzz-bus-message.c
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "bus-dump.h"
+#include "bus-message.h"
+#include "env-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fuzz.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_free_ char *out = NULL; /* out should be freed after g */
+ size_t out_size;
+ _cleanup_fclose_ FILE *g = NULL;
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ _cleanup_free_ void *buffer = NULL;
+ int r;
+
+ /* We don't want to fill the logs with messages about parse errors.
+ * Disable most logging if not running standalone */
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ r = sd_bus_new(&bus);
+ assert_se(r >= 0);
+
+ assert_se(buffer = memdup(data, size));
+
+ r = bus_message_from_malloc(bus, buffer, size, NULL, 0, NULL, &m);
+ if (r == -EBADMSG)
+ return 0;
+ assert_se(r >= 0);
+ TAKE_PTR(buffer);
+
+ if (getenv_bool("SYSTEMD_FUZZ_OUTPUT") <= 0)
+ assert_se(g = open_memstream_unlocked(&out, &out_size));
+
+ sd_bus_message_dump(m, g ?: stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ r = sd_bus_message_rewind(m, true);
+ assert_se(r >= 0);
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c
new file mode 100644
index 0000000..3c91dd3
--- /dev/null
+++ b/src/libsystemd/sd-bus/sd-bus.c
@@ -0,0 +1,4405 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <endian.h>
+#include <netdb.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "sd-bus.h"
+
+#include "af-list.h"
+#include "alloc-util.h"
+#include "bus-container.h"
+#include "bus-control.h"
+#include "bus-internal.h"
+#include "bus-kernel.h"
+#include "bus-label.h"
+#include "bus-message.h"
+#include "bus-objects.h"
+#include "bus-protocol.h"
+#include "bus-slot.h"
+#include "bus-socket.h"
+#include "bus-track.h"
+#include "bus-type.h"
+#include "cgroup-util.h"
+#include "def.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "glyph-util.h"
+#include "hexdecoct.h"
+#include "hostname-util.h"
+#include "io-util.h"
+#include "macro.h"
+#include "memory-util.h"
+#include "missing_syscall.h"
+#include "missing_threads.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "user-util.h"
+
+#define log_debug_bus_message(m) \
+ do { \
+ sd_bus_message *_mm = (m); \
+ log_debug("Got message type=%s sender=%s destination=%s path=%s interface=%s member=%s cookie=%" PRIu64 " reply_cookie=%" PRIu64 " signature=%s error-name=%s error-message=%s", \
+ strna(bus_message_type_to_string(_mm->header->type)), \
+ strna(sd_bus_message_get_sender(_mm)), \
+ strna(sd_bus_message_get_destination(_mm)), \
+ strna(sd_bus_message_get_path(_mm)), \
+ strna(sd_bus_message_get_interface(_mm)), \
+ strna(sd_bus_message_get_member(_mm)), \
+ BUS_MESSAGE_COOKIE(_mm), \
+ _mm->reply_cookie, \
+ strna(_mm->root_container.signature), \
+ strna(_mm->error.name), \
+ strna(_mm->error.message)); \
+ } while (false)
+
+static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec);
+static void bus_detach_io_events(sd_bus *b);
+
+static thread_local sd_bus *default_system_bus = NULL;
+static thread_local sd_bus *default_user_bus = NULL;
+static thread_local sd_bus *default_starter_bus = NULL;
+
+static sd_bus **bus_choose_default(int (**bus_open)(sd_bus **)) {
+ const char *e;
+
+ /* Let's try our best to reuse another cached connection. If
+ * the starter bus type is set, connect via our normal
+ * connection logic, ignoring $DBUS_STARTER_ADDRESS, so that
+ * we can share the connection with the user/system default
+ * bus. */
+
+ e = secure_getenv("DBUS_STARTER_BUS_TYPE");
+ if (e) {
+ if (streq(e, "system")) {
+ if (bus_open)
+ *bus_open = sd_bus_open_system;
+ return &default_system_bus;
+ } else if (STR_IN_SET(e, "user", "session")) {
+ if (bus_open)
+ *bus_open = sd_bus_open_user;
+ return &default_user_bus;
+ }
+ }
+
+ /* No type is specified, so we have not other option than to
+ * use the starter address if it is set. */
+ e = secure_getenv("DBUS_STARTER_ADDRESS");
+ if (e) {
+ if (bus_open)
+ *bus_open = sd_bus_open;
+ return &default_starter_bus;
+ }
+
+ /* Finally, if nothing is set use the cached connection for
+ * the right scope */
+
+ if (cg_pid_get_owner_uid(0, NULL) >= 0) {
+ if (bus_open)
+ *bus_open = sd_bus_open_user;
+ return &default_user_bus;
+ } else {
+ if (bus_open)
+ *bus_open = sd_bus_open_system;
+ return &default_system_bus;
+ }
+}
+
+sd_bus *bus_resolve(sd_bus *bus) {
+ switch ((uintptr_t) bus) {
+ case (uintptr_t) SD_BUS_DEFAULT:
+ return *(bus_choose_default(NULL));
+ case (uintptr_t) SD_BUS_DEFAULT_USER:
+ return default_user_bus;
+ case (uintptr_t) SD_BUS_DEFAULT_SYSTEM:
+ return default_system_bus;
+ default:
+ return bus;
+ }
+}
+
+void bus_close_io_fds(sd_bus *b) {
+ assert(b);
+
+ bus_detach_io_events(b);
+
+ if (b->input_fd != b->output_fd)
+ safe_close(b->output_fd);
+ b->output_fd = b->input_fd = safe_close(b->input_fd);
+}
+
+void bus_close_inotify_fd(sd_bus *b) {
+ assert(b);
+
+ b->inotify_event_source = sd_event_source_disable_unref(b->inotify_event_source);
+
+ b->inotify_fd = safe_close(b->inotify_fd);
+ b->inotify_watches = mfree(b->inotify_watches);
+ b->n_inotify_watches = 0;
+}
+
+static void bus_reset_queues(sd_bus *b) {
+ assert(b);
+
+ while (b->rqueue_size > 0)
+ bus_message_unref_queued(b->rqueue[--b->rqueue_size], b);
+
+ b->rqueue = mfree(b->rqueue);
+
+ while (b->wqueue_size > 0)
+ bus_message_unref_queued(b->wqueue[--b->wqueue_size], b);
+
+ b->wqueue = mfree(b->wqueue);
+}
+
+static sd_bus* bus_free(sd_bus *b) {
+ sd_bus_slot *s;
+
+ assert(b);
+ assert(!b->track_queue);
+ assert(!b->tracks);
+
+ b->state = BUS_CLOSED;
+
+ sd_bus_detach_event(b);
+
+ while ((s = b->slots)) {
+ /* At this point only floating slots can still be
+ * around, because the non-floating ones keep a
+ * reference to the bus, and we thus couldn't be
+ * destructing right now... We forcibly disconnect the
+ * slots here, so that they still can be referenced by
+ * apps, but are dead. */
+
+ assert(s->floating);
+ bus_slot_disconnect(s, true);
+ }
+
+ if (b->default_bus_ptr)
+ *b->default_bus_ptr = NULL;
+
+ bus_close_io_fds(b);
+ bus_close_inotify_fd(b);
+
+ free(b->label);
+ free(b->groups);
+ free(b->rbuffer);
+ free(b->unique_name);
+ free(b->auth_buffer);
+ free(b->address);
+ free(b->machine);
+ free(b->description);
+ free(b->patch_sender);
+
+ free(b->exec_path);
+ strv_free(b->exec_argv);
+
+ close_many(b->fds, b->n_fds);
+ free(b->fds);
+
+ bus_reset_queues(b);
+
+ ordered_hashmap_free_free(b->reply_callbacks);
+ prioq_free(b->reply_callbacks_prioq);
+
+ assert(b->match_callbacks.type == BUS_MATCH_ROOT);
+ bus_match_free(&b->match_callbacks);
+
+ hashmap_free_free(b->vtable_methods);
+ hashmap_free_free(b->vtable_properties);
+
+ assert(hashmap_isempty(b->nodes));
+ hashmap_free(b->nodes);
+
+ bus_flush_memfd(b);
+
+ assert_se(pthread_mutex_destroy(&b->memfd_cache_mutex) == 0);
+
+ return mfree(b);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(sd_bus*, bus_free);
+
+_public_ int sd_bus_new(sd_bus **ret) {
+ _cleanup_free_ sd_bus *b = NULL;
+
+ assert_return(ret, -EINVAL);
+
+ b = new(sd_bus, 1);
+ if (!b)
+ return -ENOMEM;
+
+ *b = (sd_bus) {
+ .n_ref = 1,
+ .input_fd = -1,
+ .output_fd = -1,
+ .inotify_fd = -1,
+ .message_version = 1,
+ .creds_mask = SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME,
+ .accept_fd = true,
+ .original_pid = getpid_cached(),
+ .n_groups = SIZE_MAX,
+ .close_on_exit = true,
+ .ucred = UCRED_INVALID,
+ };
+
+ /* We guarantee that wqueue always has space for at least one entry */
+ if (!GREEDY_REALLOC(b->wqueue, 1))
+ return -ENOMEM;
+
+ assert_se(pthread_mutex_init(&b->memfd_cache_mutex, NULL) == 0);
+
+ *ret = TAKE_PTR(b);
+ return 0;
+}
+
+_public_ int sd_bus_set_address(sd_bus *bus, const char *address) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(address, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return free_and_strdup(&bus->address, address);
+}
+
+_public_ int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(input_fd >= 0, -EBADF);
+ assert_return(output_fd >= 0, -EBADF);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus->input_fd = input_fd;
+ bus->output_fd = output_fd;
+ return 0;
+}
+
+_public_ int sd_bus_set_exec(sd_bus *bus, const char *path, char *const *argv) {
+ _cleanup_strv_free_ char **a = NULL;
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(path, -EINVAL);
+ assert_return(!strv_isempty(argv), -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ a = strv_copy(argv);
+ if (!a)
+ return -ENOMEM;
+
+ r = free_and_strdup(&bus->exec_path, path);
+ if (r < 0)
+ return r;
+
+ return strv_free_and_replace(bus->exec_argv, a);
+}
+
+_public_ int sd_bus_set_bus_client(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(!bus->patch_sender, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus->bus_client = !!b;
+ return 0;
+}
+
+_public_ int sd_bus_set_monitor(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus->is_monitor = !!b;
+ return 0;
+}
+
+_public_ int sd_bus_negotiate_fds(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus->accept_fd = !!b;
+ return 0;
+}
+
+_public_ int sd_bus_negotiate_timestamp(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ /* This is not actually supported by any of our transports these days, but we do honour it for synthetic
+ * replies, and maybe one day classic D-Bus learns this too */
+ bus->attach_timestamp = !!b;
+
+ return 0;
+}
+
+_public_ int sd_bus_negotiate_creds(sd_bus *bus, int b, uint64_t mask) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(mask <= _SD_BUS_CREDS_ALL, -EINVAL);
+ assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ SET_FLAG(bus->creds_mask, mask, b);
+
+ /* The well knowns we need unconditionally, so that matches can work */
+ bus->creds_mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME;
+
+ return 0;
+}
+
+_public_ int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t server_id) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(b || sd_id128_equal(server_id, SD_ID128_NULL), -EINVAL);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus->is_server = !!b;
+ bus->server_id = server_id;
+ return 0;
+}
+
+_public_ int sd_bus_set_anonymous(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus->anonymous_auth = !!b;
+ return 0;
+}
+
+_public_ int sd_bus_set_trusted(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus->trusted = !!b;
+ return 0;
+}
+
+_public_ int sd_bus_set_description(sd_bus *bus, const char *description) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return free_and_strdup(&bus->description, description);
+}
+
+_public_ int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus->allow_interactive_authorization = !!b;
+ return 0;
+}
+
+_public_ int sd_bus_get_allow_interactive_authorization(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return bus->allow_interactive_authorization;
+}
+
+_public_ int sd_bus_set_watch_bind(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus->watch_bind = !!b;
+ return 0;
+}
+
+_public_ int sd_bus_get_watch_bind(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return bus->watch_bind;
+}
+
+_public_ int sd_bus_set_connected_signal(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus->connected_signal = !!b;
+ return 0;
+}
+
+_public_ int sd_bus_get_connected_signal(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return bus->connected_signal;
+}
+
+static int synthesize_connected_signal(sd_bus *bus) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
+
+ assert(bus);
+
+ /* If enabled, synthesizes a local "Connected" signal mirroring the local "Disconnected" signal. This is called
+ * whenever we fully established a connection, i.e. after the authorization phase, and after receiving the
+ * Hello() reply. Or in other words, whenever we enter BUS_RUNNING state.
+ *
+ * This is useful so that clients can start doing stuff whenever the connection is fully established in a way
+ * that works independently from whether we connected to a full bus or just a direct connection. */
+
+ if (!bus->connected_signal)
+ return 0;
+
+ r = sd_bus_message_new_signal(
+ bus,
+ &m,
+ "/org/freedesktop/DBus/Local",
+ "org.freedesktop.DBus.Local",
+ "Connected");
+ if (r < 0)
+ return r;
+
+ bus_message_set_sender_local(bus, m);
+ m->read_counter = ++bus->read_counter;
+
+ r = bus_seal_synthetic_message(bus, m);
+ if (r < 0)
+ return r;
+
+ r = bus_rqueue_make_room(bus);
+ if (r < 0)
+ return r;
+
+ /* Insert at the very front */
+ memmove(bus->rqueue + 1, bus->rqueue, sizeof(sd_bus_message*) * bus->rqueue_size);
+ bus->rqueue[0] = bus_message_ref_queued(m, bus);
+ bus->rqueue_size++;
+
+ return 0;
+}
+
+void bus_set_state(sd_bus *bus, enum bus_state state) {
+ static const char* const table[_BUS_STATE_MAX] = {
+ [BUS_UNSET] = "UNSET",
+ [BUS_WATCH_BIND] = "WATCH_BIND",
+ [BUS_OPENING] = "OPENING",
+ [BUS_AUTHENTICATING] = "AUTHENTICATING",
+ [BUS_HELLO] = "HELLO",
+ [BUS_RUNNING] = "RUNNING",
+ [BUS_CLOSING] = "CLOSING",
+ [BUS_CLOSED] = "CLOSED",
+ };
+
+ assert(bus);
+ assert(state < _BUS_STATE_MAX);
+
+ if (state == bus->state)
+ return;
+
+ log_debug("Bus %s: changing state %s %s %s", strna(bus->description),
+ table[bus->state], special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), table[state]);
+ bus->state = state;
+}
+
+static int hello_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) {
+ const char *s;
+ sd_bus *bus;
+ int r;
+
+ assert(reply);
+ bus = reply->bus;
+ assert(bus);
+ assert(IN_SET(bus->state, BUS_HELLO, BUS_CLOSING));
+
+ r = sd_bus_message_get_errno(reply);
+ if (r > 0) {
+ r = -r;
+ goto fail;
+ }
+
+ r = sd_bus_message_read(reply, "s", &s);
+ if (r < 0)
+ goto fail;
+
+ if (!service_name_is_valid(s) || s[0] != ':') {
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ r = free_and_strdup(&bus->unique_name, s);
+ if (r < 0)
+ goto fail;
+
+ if (bus->state == BUS_HELLO) {
+ bus_set_state(bus, BUS_RUNNING);
+
+ r = synthesize_connected_signal(bus);
+ if (r < 0)
+ goto fail;
+ }
+
+ return 1;
+
+fail:
+ /* When Hello() failed, let's propagate this in two ways: first we return the error immediately here,
+ * which is the propagated up towards the event loop. Let's also invalidate the connection, so that
+ * if the user then calls back into us again we won't wait any longer. */
+
+ bus_set_state(bus, BUS_CLOSING);
+ return r;
+}
+
+static int bus_send_hello(sd_bus *bus) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
+
+ assert(bus);
+
+ if (!bus->bus_client)
+ return 0;
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &m,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "Hello");
+ if (r < 0)
+ return r;
+
+ return sd_bus_call_async(bus, NULL, m, hello_callback, NULL, 0);
+}
+
+int bus_start_running(sd_bus *bus) {
+ struct reply_callback *c;
+ usec_t n;
+ int r;
+
+ assert(bus);
+ assert(bus->state < BUS_HELLO);
+
+ /* We start all method call timeouts when we enter BUS_HELLO or BUS_RUNNING mode. At this point let's convert
+ * all relative to absolute timestamps. Note that we do not reshuffle the reply callback priority queue since
+ * adding a fixed value to all entries should not alter the internal order. */
+
+ n = now(CLOCK_MONOTONIC);
+ ORDERED_HASHMAP_FOREACH(c, bus->reply_callbacks) {
+ if (c->timeout_usec == 0)
+ continue;
+
+ c->timeout_usec = usec_add(n, c->timeout_usec);
+ }
+
+ if (bus->bus_client) {
+ bus_set_state(bus, BUS_HELLO);
+ return 1;
+ }
+
+ bus_set_state(bus, BUS_RUNNING);
+
+ r = synthesize_connected_signal(bus);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int parse_address_key(const char **p, const char *key, char **value) {
+ _cleanup_free_ char *r = NULL;
+ size_t l, n = 0;
+ const char *a;
+
+ assert(p);
+ assert(*p);
+ assert(value);
+
+ if (key) {
+ l = strlen(key);
+ if (strncmp(*p, key, l) != 0)
+ return 0;
+
+ if ((*p)[l] != '=')
+ return 0;
+
+ if (*value)
+ return -EINVAL;
+
+ a = *p + l + 1;
+ } else
+ a = *p;
+
+ while (!IN_SET(*a, ';', ',', 0)) {
+ char c;
+
+ if (*a == '%') {
+ int x, y;
+
+ x = unhexchar(a[1]);
+ if (x < 0)
+ return x;
+
+ y = unhexchar(a[2]);
+ if (y < 0)
+ return y;
+
+ c = (char) ((x << 4) | y);
+ a += 3;
+ } else {
+ c = *a;
+ a++;
+ }
+
+ if (!GREEDY_REALLOC(r, n + 2))
+ return -ENOMEM;
+
+ r[n++] = c;
+ }
+
+ if (!r) {
+ r = strdup("");
+ if (!r)
+ return -ENOMEM;
+ } else
+ r[n] = 0;
+
+ if (*a == ',')
+ a++;
+
+ *p = a;
+
+ free_and_replace(*value, r);
+
+ return 1;
+}
+
+static void skip_address_key(const char **p) {
+ assert(p);
+ assert(*p);
+
+ *p += strcspn(*p, ",");
+
+ if (**p == ',')
+ (*p)++;
+}
+
+static int parse_unix_address(sd_bus *b, const char **p, char **guid) {
+ _cleanup_free_ char *path = NULL, *abstract = NULL;
+ size_t l;
+ int r;
+
+ assert(b);
+ assert(p);
+ assert(*p);
+ assert(guid);
+
+ while (!IN_SET(**p, 0, ';')) {
+ r = parse_address_key(p, "guid", guid);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_address_key(p, "path", &path);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_address_key(p, "abstract", &abstract);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ skip_address_key(p);
+ }
+
+ if (!path && !abstract)
+ return -EINVAL;
+
+ if (path && abstract)
+ return -EINVAL;
+
+ if (path) {
+ l = strlen(path);
+ if (l >= sizeof(b->sockaddr.un.sun_path)) /* We insist on NUL termination */
+ return -E2BIG;
+
+ b->sockaddr.un = (struct sockaddr_un) {
+ .sun_family = AF_UNIX,
+ };
+
+ memcpy(b->sockaddr.un.sun_path, path, l);
+ b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + l + 1;
+
+ } else {
+ assert(abstract);
+
+ l = strlen(abstract);
+ if (l >= sizeof(b->sockaddr.un.sun_path) - 1) /* We insist on NUL termination */
+ return -E2BIG;
+
+ b->sockaddr.un = (struct sockaddr_un) {
+ .sun_family = AF_UNIX,
+ };
+
+ memcpy(b->sockaddr.un.sun_path+1, abstract, l);
+ b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l;
+ }
+
+ b->is_local = true;
+
+ return 0;
+}
+
+static int parse_tcp_address(sd_bus *b, const char **p, char **guid) {
+ _cleanup_free_ char *host = NULL, *port = NULL, *family = NULL;
+ int r;
+ struct addrinfo *result, hints = {
+ .ai_socktype = SOCK_STREAM,
+ };
+
+ assert(b);
+ assert(p);
+ assert(*p);
+ assert(guid);
+
+ while (!IN_SET(**p, 0, ';')) {
+ r = parse_address_key(p, "guid", guid);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_address_key(p, "host", &host);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_address_key(p, "port", &port);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_address_key(p, "family", &family);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ skip_address_key(p);
+ }
+
+ if (!host || !port)
+ return -EINVAL;
+
+ if (family) {
+ hints.ai_family = af_from_ipv4_ipv6(family);
+ if (hints.ai_family == AF_UNSPEC)
+ return -EINVAL;
+ }
+
+ r = getaddrinfo(host, port, &hints, &result);
+ if (r == EAI_SYSTEM)
+ return -errno;
+ else if (r != 0)
+ return -EADDRNOTAVAIL;
+
+ memcpy(&b->sockaddr, result->ai_addr, result->ai_addrlen);
+ b->sockaddr_size = result->ai_addrlen;
+
+ freeaddrinfo(result);
+
+ b->is_local = false;
+
+ return 0;
+}
+
+static int parse_exec_address(sd_bus *b, const char **p, char **guid) {
+ char *path = NULL;
+ unsigned n_argv = 0, j;
+ char **argv = NULL;
+ int r;
+
+ assert(b);
+ assert(p);
+ assert(*p);
+ assert(guid);
+
+ while (!IN_SET(**p, 0, ';')) {
+ r = parse_address_key(p, "guid", guid);
+ if (r < 0)
+ goto fail;
+ else if (r > 0)
+ continue;
+
+ r = parse_address_key(p, "path", &path);
+ if (r < 0)
+ goto fail;
+ else if (r > 0)
+ continue;
+
+ if (startswith(*p, "argv")) {
+ unsigned ul;
+
+ errno = 0;
+ ul = strtoul(*p + 4, (char**) p, 10);
+ if (errno > 0 || **p != '=' || ul > 256) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ (*p)++;
+
+ if (ul >= n_argv) {
+ if (!GREEDY_REALLOC0(argv, ul + 2)) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ n_argv = ul + 1;
+ }
+
+ r = parse_address_key(p, NULL, argv + ul);
+ if (r < 0)
+ goto fail;
+
+ continue;
+ }
+
+ skip_address_key(p);
+ }
+
+ if (!path) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ /* Make sure there are no holes in the array, with the
+ * exception of argv[0] */
+ for (j = 1; j < n_argv; j++)
+ if (!argv[j]) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ if (argv && argv[0] == NULL) {
+ argv[0] = strdup(path);
+ if (!argv[0]) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ b->exec_path = path;
+ b->exec_argv = argv;
+
+ b->is_local = false;
+
+ return 0;
+
+fail:
+ for (j = 0; j < n_argv; j++)
+ free(argv[j]);
+
+ free(argv);
+ free(path);
+ return r;
+}
+
+static int parse_container_unix_address(sd_bus *b, const char **p, char **guid) {
+ _cleanup_free_ char *machine = NULL, *pid = NULL;
+ int r;
+
+ assert(b);
+ assert(p);
+ assert(*p);
+ assert(guid);
+
+ while (!IN_SET(**p, 0, ';')) {
+ r = parse_address_key(p, "guid", guid);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_address_key(p, "machine", &machine);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_address_key(p, "pid", &pid);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ skip_address_key(p);
+ }
+
+ if (!machine == !pid)
+ return -EINVAL;
+
+ if (machine) {
+ if (!hostname_is_valid(machine, VALID_HOSTNAME_DOT_HOST))
+ return -EINVAL;
+
+ free_and_replace(b->machine, machine);
+ } else
+ b->machine = mfree(b->machine);
+
+ if (pid) {
+ r = parse_pid(pid, &b->nspid);
+ if (r < 0)
+ return r;
+ } else
+ b->nspid = 0;
+
+ b->sockaddr.un = (struct sockaddr_un) {
+ .sun_family = AF_UNIX,
+ /* Note that we use the old /var/run prefix here, to increase compatibility with really old containers */
+ .sun_path = "/var/run/dbus/system_bus_socket",
+ };
+ b->sockaddr_size = SOCKADDR_UN_LEN(b->sockaddr.un);
+ b->is_local = false;
+
+ return 0;
+}
+
+static void bus_reset_parsed_address(sd_bus *b) {
+ assert(b);
+
+ zero(b->sockaddr);
+ b->sockaddr_size = 0;
+ b->exec_argv = strv_free(b->exec_argv);
+ b->exec_path = mfree(b->exec_path);
+ b->server_id = SD_ID128_NULL;
+ b->machine = mfree(b->machine);
+ b->nspid = 0;
+}
+
+static int bus_parse_next_address(sd_bus *b) {
+ _cleanup_free_ char *guid = NULL;
+ const char *a;
+ int r;
+
+ assert(b);
+
+ if (!b->address)
+ return 0;
+ if (b->address[b->address_index] == 0)
+ return 0;
+
+ bus_reset_parsed_address(b);
+
+ a = b->address + b->address_index;
+
+ while (*a != 0) {
+
+ if (*a == ';') {
+ a++;
+ continue;
+ }
+
+ if (startswith(a, "unix:")) {
+ a += 5;
+
+ r = parse_unix_address(b, &a, &guid);
+ if (r < 0)
+ return r;
+ break;
+
+ } else if (startswith(a, "tcp:")) {
+
+ a += 4;
+ r = parse_tcp_address(b, &a, &guid);
+ if (r < 0)
+ return r;
+
+ break;
+
+ } else if (startswith(a, "unixexec:")) {
+
+ a += 9;
+ r = parse_exec_address(b, &a, &guid);
+ if (r < 0)
+ return r;
+
+ break;
+
+ } else if (startswith(a, "x-machine-unix:")) {
+
+ a += 15;
+ r = parse_container_unix_address(b, &a, &guid);
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ a = strchr(a, ';');
+ if (!a)
+ return 0;
+ }
+
+ if (guid) {
+ r = sd_id128_from_string(guid, &b->server_id);
+ if (r < 0)
+ return r;
+ }
+
+ b->address_index = a - b->address;
+ return 1;
+}
+
+static void bus_kill_exec(sd_bus *bus) {
+ if (!pid_is_valid(bus->busexec_pid))
+ return;
+
+ sigterm_wait(TAKE_PID(bus->busexec_pid));
+}
+
+static int bus_start_address(sd_bus *b) {
+ int r;
+
+ assert(b);
+
+ for (;;) {
+ bus_close_io_fds(b);
+ bus_close_inotify_fd(b);
+
+ bus_kill_exec(b);
+
+ /* If you provide multiple different bus-addresses, we
+ * try all of them in order and use the first one that
+ * succeeds. */
+
+ if (b->exec_path)
+ r = bus_socket_exec(b);
+ else if ((b->nspid > 0 || b->machine) && b->sockaddr.sa.sa_family != AF_UNSPEC)
+ r = bus_container_connect_socket(b);
+ else if (b->sockaddr.sa.sa_family != AF_UNSPEC)
+ r = bus_socket_connect(b);
+ else
+ goto next;
+
+ if (r >= 0) {
+ int q;
+
+ q = bus_attach_io_events(b);
+ if (q < 0)
+ return q;
+
+ q = bus_attach_inotify_event(b);
+ if (q < 0)
+ return q;
+
+ return r;
+ }
+
+ b->last_connect_error = -r;
+
+ next:
+ r = bus_parse_next_address(b);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return b->last_connect_error > 0 ? -b->last_connect_error : -ECONNREFUSED;
+ }
+}
+
+int bus_next_address(sd_bus *b) {
+ assert(b);
+
+ bus_reset_parsed_address(b);
+ return bus_start_address(b);
+}
+
+static int bus_start_fd(sd_bus *b) {
+ struct stat st;
+ int r;
+
+ assert(b);
+ assert(b->input_fd >= 0);
+ assert(b->output_fd >= 0);
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *pi = NULL, *po = NULL;
+ (void) fd_get_path(b->input_fd, &pi);
+ (void) fd_get_path(b->output_fd, &po);
+ log_debug("sd-bus: starting bus%s%s on fds %d/%d (%s, %s)...",
+ b->description ? " " : "", strempty(b->description),
+ b->input_fd, b->output_fd,
+ pi ?: "???", po ?: "???");
+ }
+
+ r = fd_nonblock(b->input_fd, true);
+ if (r < 0)
+ return r;
+
+ r = fd_cloexec(b->input_fd, true);
+ if (r < 0)
+ return r;
+
+ if (b->input_fd != b->output_fd) {
+ r = fd_nonblock(b->output_fd, true);
+ if (r < 0)
+ return r;
+
+ r = fd_cloexec(b->output_fd, true);
+ if (r < 0)
+ return r;
+ }
+
+ if (fstat(b->input_fd, &st) < 0)
+ return -errno;
+
+ return bus_socket_take_fd(b);
+}
+
+_public_ int sd_bus_start(sd_bus *bus) {
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus_set_state(bus, BUS_OPENING);
+
+ if (bus->is_server && bus->bus_client)
+ return -EINVAL;
+
+ if (bus->input_fd >= 0)
+ r = bus_start_fd(bus);
+ else if (bus->address || bus->sockaddr.sa.sa_family != AF_UNSPEC || bus->exec_path || bus->machine)
+ r = bus_start_address(bus);
+ else
+ return -EINVAL;
+
+ if (r < 0) {
+ sd_bus_close(bus);
+ return r;
+ }
+
+ return bus_send_hello(bus);
+}
+
+_public_ int sd_bus_open_with_description(sd_bus **ret, const char *description) {
+ const char *e;
+ _cleanup_(bus_freep) sd_bus *b = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ /* Let's connect to the starter bus if it is set, and
+ * otherwise to the bus that is appropriate for the scope
+ * we are running in */
+
+ e = secure_getenv("DBUS_STARTER_BUS_TYPE");
+ if (e) {
+ if (streq(e, "system"))
+ return sd_bus_open_system_with_description(ret, description);
+ else if (STR_IN_SET(e, "session", "user"))
+ return sd_bus_open_user_with_description(ret, description);
+ }
+
+ e = secure_getenv("DBUS_STARTER_ADDRESS");
+ if (!e) {
+ if (cg_pid_get_owner_uid(0, NULL) >= 0)
+ return sd_bus_open_user_with_description(ret, description);
+ else
+ return sd_bus_open_system_with_description(ret, description);
+ }
+
+ r = sd_bus_new(&b);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_set_address(b, e);
+ if (r < 0)
+ return r;
+
+ b->bus_client = true;
+
+ /* We don't know whether the bus is trusted or not, so better
+ * be safe, and authenticate everything */
+ b->trusted = false;
+ b->is_local = false;
+ b->creds_mask |= SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_EFFECTIVE_CAPS;
+
+ r = sd_bus_start(b);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(b);
+ return 0;
+}
+
+_public_ int sd_bus_open(sd_bus **ret) {
+ return sd_bus_open_with_description(ret, NULL);
+}
+
+int bus_set_address_system(sd_bus *b) {
+ const char *e;
+ int r;
+
+ assert(b);
+
+ e = secure_getenv("DBUS_SYSTEM_BUS_ADDRESS");
+
+ r = sd_bus_set_address(b, e ?: DEFAULT_SYSTEM_BUS_ADDRESS);
+ if (r >= 0)
+ b->is_system = true;
+ return r;
+}
+
+_public_ int sd_bus_open_system_with_description(sd_bus **ret, const char *description) {
+ _cleanup_(bus_freep) sd_bus *b = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ r = sd_bus_new(&b);
+ if (r < 0)
+ return r;
+
+ if (description) {
+ r = sd_bus_set_description(b, description);
+ if (r < 0)
+ return r;
+ }
+
+ r = bus_set_address_system(b);
+ if (r < 0)
+ return r;
+
+ b->bus_client = true;
+
+ /* Let's do per-method access control on the system bus. We
+ * need the caller's UID and capability set for that. */
+ b->trusted = false;
+ b->creds_mask |= SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_EFFECTIVE_CAPS;
+ b->is_local = true;
+
+ r = sd_bus_start(b);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(b);
+ return 0;
+}
+
+_public_ int sd_bus_open_system(sd_bus **ret) {
+ return sd_bus_open_system_with_description(ret, NULL);
+}
+
+int bus_set_address_user(sd_bus *b) {
+ const char *a;
+ _cleanup_free_ char *_a = NULL;
+ int r;
+
+ assert(b);
+
+ a = secure_getenv("DBUS_SESSION_BUS_ADDRESS");
+ if (!a) {
+ const char *e;
+ _cleanup_free_ char *ee = NULL;
+
+ e = secure_getenv("XDG_RUNTIME_DIR");
+ if (!e)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
+ "sd-bus: $XDG_RUNTIME_DIR not set, cannot connect to user bus.");
+
+ ee = bus_address_escape(e);
+ if (!ee)
+ return -ENOMEM;
+
+ if (asprintf(&_a, DEFAULT_USER_BUS_ADDRESS_FMT, ee) < 0)
+ return -ENOMEM;
+ a = _a;
+ }
+
+ r = sd_bus_set_address(b, a);
+ if (r >= 0)
+ b->is_user = true;
+ return r;
+}
+
+_public_ int sd_bus_open_user_with_description(sd_bus **ret, const char *description) {
+ _cleanup_(bus_freep) sd_bus *b = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ r = sd_bus_new(&b);
+ if (r < 0)
+ return r;
+
+ if (description) {
+ r = sd_bus_set_description(b, description);
+ if (r < 0)
+ return r;
+ }
+
+ r = bus_set_address_user(b);
+ if (r < 0)
+ return r;
+
+ b->bus_client = true;
+
+ /* We don't do any per-method access control on the user bus. */
+ b->trusted = true;
+ b->is_local = true;
+
+ r = sd_bus_start(b);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(b);
+ return 0;
+}
+
+_public_ int sd_bus_open_user(sd_bus **ret) {
+ return sd_bus_open_user_with_description(ret, NULL);
+}
+
+int bus_set_address_system_remote(sd_bus *b, const char *host) {
+ _cleanup_free_ char *e = NULL;
+ char *m = NULL, *c = NULL, *a, *rbracket = NULL, *p = NULL;
+
+ assert(b);
+ assert(host);
+
+ /* Skip ":"s in ipv6 addresses */
+ if (*host == '[') {
+ char *t;
+
+ rbracket = strchr(host, ']');
+ if (!rbracket)
+ return -EINVAL;
+ t = strndupa_safe(host + 1, rbracket - host - 1);
+ e = bus_address_escape(t);
+ if (!e)
+ return -ENOMEM;
+ } else if ((a = strchr(host, '@'))) {
+ if (*(a + 1) == '[') {
+ _cleanup_free_ char *t = NULL;
+
+ rbracket = strchr(a + 1, ']');
+ if (!rbracket)
+ return -EINVAL;
+ t = new0(char, strlen(host));
+ if (!t)
+ return -ENOMEM;
+ strncat(t, host, a - host + 1);
+ strncat(t, a + 2, rbracket - a - 2);
+ e = bus_address_escape(t);
+ if (!e)
+ return -ENOMEM;
+ } else if (*(a + 1) == '\0' || strchr(a + 1, '@'))
+ return -EINVAL;
+ }
+
+ /* Let's see if a port was given */
+ m = strchr(rbracket ? rbracket + 1 : host, ':');
+ if (m) {
+ char *t;
+ bool got_forward_slash = false;
+
+ p = m + 1;
+
+ t = strchr(p, '/');
+ if (t) {
+ p = strndupa_safe(p, t - p);
+ got_forward_slash = true;
+ }
+
+ if (!in_charset(p, "0123456789") || *p == '\0') {
+ if (!hostname_is_valid(p, 0) || got_forward_slash)
+ return -EINVAL;
+
+ m = TAKE_PTR(p);
+ goto interpret_port_as_machine_old_syntax;
+ }
+ }
+
+ /* Let's see if a machine was given */
+ m = strchr(rbracket ? rbracket + 1 : host, '/');
+ if (m) {
+ m++;
+interpret_port_as_machine_old_syntax:
+ /* Let's make sure this is not a port of some kind,
+ * and is a valid machine name. */
+ if (!in_charset(m, "0123456789") && hostname_is_valid(m, 0))
+ c = strjoina(",argv", p ? "7" : "5", "=--machine=", m);
+ }
+
+ if (!e) {
+ char *t;
+
+ t = strndupa_safe(host, strcspn(host, ":/"));
+
+ e = bus_address_escape(t);
+ if (!e)
+ return -ENOMEM;
+ }
+
+ a = strjoin("unixexec:path=ssh,argv1=-xT", p ? ",argv2=-p,argv3=" : "", strempty(p),
+ ",argv", p ? "4" : "2", "=--,argv", p ? "5" : "3", "=", e,
+ ",argv", p ? "6" : "4", "=systemd-stdio-bridge", c);
+ if (!a)
+ return -ENOMEM;
+
+ return free_and_replace(b->address, a);
+}
+
+_public_ int sd_bus_open_system_remote(sd_bus **ret, const char *host) {
+ _cleanup_(bus_freep) sd_bus *b = NULL;
+ int r;
+
+ assert_return(host, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = sd_bus_new(&b);
+ if (r < 0)
+ return r;
+
+ r = bus_set_address_system_remote(b, host);
+ if (r < 0)
+ return r;
+
+ b->bus_client = true;
+ b->trusted = false;
+ b->is_system = true;
+ b->is_local = false;
+
+ r = sd_bus_start(b);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(b);
+ return 0;
+}
+
+int bus_set_address_machine(sd_bus *b, bool user, const char *machine) {
+ _cleanup_free_ char *a = NULL;
+ const char *rhs;
+
+ assert(b);
+ assert(machine);
+
+ rhs = strchr(machine, '@');
+ if (rhs || user) {
+ _cleanup_free_ char *u = NULL, *eu = NULL, *erhs = NULL;
+
+ /* If there's an "@" in the container specification, we'll connect as a user specified at its
+ * left hand side, which is useful in combination with user=true. This isn't as trivial as it
+ * might sound: it's not sufficient to enter the container and connect to some socket there,
+ * since the --user socket path depends on $XDG_RUNTIME_DIR which is set via PAM. Thus, to be
+ * able to connect, we need to have a PAM session. Our way out? We use systemd-run to get
+ * into the container and acquire a PAM session there, and then invoke systemd-stdio-bridge
+ * in it, which propagates the bus transport to us. */
+
+ if (rhs) {
+ if (rhs > machine)
+ u = strndup(machine, rhs - machine);
+ else
+ u = getusername_malloc(); /* Empty user name, let's use the local one */
+ if (!u)
+ return -ENOMEM;
+
+ eu = bus_address_escape(u);
+ if (!eu)
+ return -ENOMEM;
+
+ rhs++;
+ } else {
+ /* No "@" specified but we shall connect to the user instance? Then assume root (and
+ * not a user named identically to the calling one). This means:
+ *
+ * --machine=foobar --user → connect to user bus of root user in container "foobar"
+ * --machine=@foobar --user → connect to user bus of user named like the calling user in container "foobar"
+ *
+ * Why? so that behaviour for "--machine=foobar --system" is roughly similar to
+ * "--machine=foobar --user": both times we unconditionally connect as root user
+ * regardless what the calling user is. */
+
+ rhs = machine;
+ }
+
+ if (!isempty(rhs)) {
+ erhs = bus_address_escape(rhs);
+ if (!erhs)
+ return -ENOMEM;
+ }
+
+ /* systemd-run -M… -PGq --wait -pUser=… -pPAMName=login systemd-stdio-bridge */
+
+ a = strjoin("unixexec:path=systemd-run,"
+ "argv1=-M", erhs ?: ".host", ","
+ "argv2=-PGq,"
+ "argv3=--wait,"
+ "argv4=-pUser%3d", eu ?: "root", ",",
+ "argv5=-pPAMName%3dlogin,"
+ "argv6=systemd-stdio-bridge");
+ if (!a)
+ return -ENOMEM;
+
+ if (user) {
+ /* Ideally we'd use the "--user" switch to systemd-stdio-bridge here, but it's only
+ * available in recent systemd versions. Using the "-p" switch with the explicit path
+ * is a working alternative, and is compatible with older versions, hence that's what
+ * we use here. */
+ if (!strextend(&a, ",argv7=-punix:path%3d%24%7bXDG_RUNTIME_DIR%7d/bus"))
+ return -ENOMEM;
+ }
+ } else {
+ _cleanup_free_ char *e = NULL;
+
+ /* Just a container name, we can go the simple way, and just join the container, and connect
+ * to the well-known path of the system bus there. */
+
+ e = bus_address_escape(machine);
+ if (!e)
+ return -ENOMEM;
+
+ a = strjoin("x-machine-unix:machine=", e);
+ if (!a)
+ return -ENOMEM;
+ }
+
+ return free_and_replace(b->address, a);
+}
+
+static int user_and_machine_valid(const char *user_and_machine) {
+ const char *h;
+
+ /* Checks if a container specification in the form "user@container" or just "container" is valid.
+ *
+ * If the "@" syntax is used we'll allow either the "user" or the "container" part to be omitted, but
+ * not both. */
+
+ h = strchr(user_and_machine, '@');
+ if (!h)
+ h = user_and_machine;
+ else {
+ _cleanup_free_ char *user = NULL;
+
+ user = strndup(user_and_machine, h - user_and_machine);
+ if (!user)
+ return -ENOMEM;
+
+ if (!isempty(user) && !valid_user_group_name(user, VALID_USER_RELAX | VALID_USER_ALLOW_NUMERIC))
+ return false;
+
+ h++;
+
+ if (isempty(h))
+ return !isempty(user);
+ }
+
+ return hostname_is_valid(h, VALID_HOSTNAME_DOT_HOST);
+}
+
+static int user_and_machine_equivalent(const char *user_and_machine) {
+ _cleanup_free_ char *un = NULL;
+ const char *f;
+
+ /* Returns true if the specified user+machine name are actually equivalent to our own identity and
+ * our own host. If so we can shortcut things. Why bother? Because that way we don't have to fork
+ * off short-lived worker processes that are then unavailable for authentication and logging in the
+ * peer. Moreover joining a namespace requires privileges. If we are in the right namespace anyway,
+ * we can avoid permission problems thus. */
+
+ assert(user_and_machine);
+
+ /* Omitting the user name means that we shall use the same user name as we run as locally, which
+ * means we'll end up on the same host, let's shortcut */
+ if (streq(user_and_machine, "@.host"))
+ return true;
+
+ /* Otherwise, if we are root, then we can also allow the ".host" syntax, as that's the user this
+ * would connect to. */
+ uid_t uid = geteuid();
+
+ if (uid == 0 && STR_IN_SET(user_and_machine, ".host", "root@.host", "0@.host"))
+ return true;
+
+ /* Otherwise, we have to figure out our user id and name, and compare things with that. */
+ char buf[DECIMAL_STR_MAX(uid_t)];
+ xsprintf(buf, UID_FMT, uid);
+
+ f = startswith(user_and_machine, buf);
+ if (!f) {
+ un = getusername_malloc();
+ if (!un)
+ return -ENOMEM;
+
+ f = startswith(user_and_machine, un);
+ if (!f)
+ return false;
+ }
+
+ return STR_IN_SET(f, "@", "@.host");
+}
+
+_public_ int sd_bus_open_system_machine(sd_bus **ret, const char *user_and_machine) {
+ _cleanup_(bus_freep) sd_bus *b = NULL;
+ int r;
+
+ assert_return(user_and_machine, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (user_and_machine_equivalent(user_and_machine))
+ return sd_bus_open_system(ret);
+
+ r = user_and_machine_valid(user_and_machine);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+
+ r = sd_bus_new(&b);
+ if (r < 0)
+ return r;
+
+ r = bus_set_address_machine(b, false, user_and_machine);
+ if (r < 0)
+ return r;
+
+ b->bus_client = true;
+ b->is_system = true;
+
+ r = sd_bus_start(b);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(b);
+ return 0;
+}
+
+_public_ int sd_bus_open_user_machine(sd_bus **ret, const char *user_and_machine) {
+ _cleanup_(bus_freep) sd_bus *b = NULL;
+ int r;
+
+ assert_return(user_and_machine, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ /* Shortcut things if we'd end up on this host and as the same user. */
+ if (user_and_machine_equivalent(user_and_machine))
+ return sd_bus_open_user(ret);
+
+ r = user_and_machine_valid(user_and_machine);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+
+ r = sd_bus_new(&b);
+ if (r < 0)
+ return r;
+
+ r = bus_set_address_machine(b, true, user_and_machine);
+ if (r < 0)
+ return r;
+
+ b->bus_client = true;
+ b->trusted = true;
+
+ r = sd_bus_start(b);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(b);
+ return 0;
+}
+
+_public_ void sd_bus_close(sd_bus *bus) {
+ if (!bus)
+ return;
+ if (bus->state == BUS_CLOSED)
+ return;
+ if (bus_pid_changed(bus))
+ return;
+
+ /* Don't leave ssh hanging around */
+ bus_kill_exec(bus);
+
+ bus_set_state(bus, BUS_CLOSED);
+
+ sd_bus_detach_event(bus);
+
+ /* Drop all queued messages so that they drop references to
+ * the bus object and the bus may be freed */
+ bus_reset_queues(bus);
+
+ bus_close_io_fds(bus);
+ bus_close_inotify_fd(bus);
+}
+
+_public_ sd_bus *sd_bus_close_unref(sd_bus *bus) {
+ if (!bus)
+ return NULL;
+
+ sd_bus_close(bus);
+
+ return sd_bus_unref(bus);
+}
+
+_public_ sd_bus* sd_bus_flush_close_unref(sd_bus *bus) {
+ if (!bus)
+ return NULL;
+
+ /* Have to do this before flush() to prevent hang */
+ bus_kill_exec(bus);
+ sd_bus_flush(bus);
+
+ return sd_bus_close_unref(bus);
+}
+
+void bus_enter_closing(sd_bus *bus) {
+ assert(bus);
+
+ if (!IN_SET(bus->state, BUS_WATCH_BIND, BUS_OPENING, BUS_AUTHENTICATING, BUS_HELLO, BUS_RUNNING))
+ return;
+
+ bus_set_state(bus, BUS_CLOSING);
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_bus, sd_bus, bus_free);
+
+_public_ int sd_bus_is_open(sd_bus *bus) {
+ if (!bus)
+ return 0;
+
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return BUS_IS_OPEN(bus->state);
+}
+
+_public_ int sd_bus_is_ready(sd_bus *bus) {
+ if (!bus)
+ return 0;
+
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return bus->state == BUS_RUNNING;
+}
+
+_public_ int sd_bus_can_send(sd_bus *bus, char type) {
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->state != BUS_UNSET, -ENOTCONN);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (bus->is_monitor)
+ return 0;
+
+ if (type == SD_BUS_TYPE_UNIX_FD) {
+ if (!bus->accept_fd)
+ return 0;
+
+ r = bus_ensure_running(bus);
+ if (r < 0)
+ return r;
+
+ return bus->can_fds;
+ }
+
+ return bus_type_is_valid(type);
+}
+
+_public_ int sd_bus_get_bus_id(sd_bus *bus, sd_id128_t *id) {
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(id, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ r = bus_ensure_running(bus);
+ if (r < 0)
+ return r;
+
+ *id = bus->server_id;
+ return 0;
+}
+
+#define COOKIE_CYCLED (UINT32_C(1) << 31)
+
+static uint64_t cookie_inc(uint64_t cookie) {
+
+ /* Stay within the 32bit range, since classic D-Bus can't deal with more */
+ if (cookie >= UINT32_MAX)
+ return COOKIE_CYCLED; /* Don't go back to zero, but use the highest bit for checking
+ * whether we are looping. */
+
+ return cookie + 1;
+}
+
+static int next_cookie(sd_bus *b) {
+ uint64_t new_cookie;
+
+ assert(b);
+
+ new_cookie = cookie_inc(b->cookie);
+
+ /* Small optimization: don't bother with checking for cookie reuse until we overran cookiespace at
+ * least once, but then do it thorougly. */
+ if (FLAGS_SET(new_cookie, COOKIE_CYCLED)) {
+ uint32_t i;
+
+ /* Check if the cookie is currently in use. If so, pick the next one */
+ for (i = 0; i < COOKIE_CYCLED; i++) {
+ if (!ordered_hashmap_contains(b->reply_callbacks, &new_cookie))
+ goto good;
+
+ new_cookie = cookie_inc(new_cookie);
+ }
+
+ /* Can't fulfill request */
+ return -EBUSY;
+ }
+
+good:
+ b->cookie = new_cookie;
+ return 0;
+}
+
+static int bus_seal_message(sd_bus *b, sd_bus_message *m, usec_t timeout) {
+ int r;
+
+ assert(b);
+ assert(m);
+
+ if (m->sealed) {
+ /* If we copy the same message to multiple
+ * destinations, avoid using the same cookie
+ * numbers. */
+ b->cookie = MAX(b->cookie, BUS_MESSAGE_COOKIE(m));
+ return 0;
+ }
+
+ if (timeout == 0) {
+ r = sd_bus_get_method_call_timeout(b, &timeout);
+ if (r < 0)
+ return r;
+ }
+
+ if (!m->sender && b->patch_sender) {
+ r = sd_bus_message_set_sender(m, b->patch_sender);
+ if (r < 0)
+ return r;
+ }
+
+ r = next_cookie(b);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_seal(m, b->cookie, timeout);
+}
+
+static int bus_remarshal_message(sd_bus *b, sd_bus_message **m) {
+ bool remarshal = false;
+
+ assert(b);
+
+ /* wrong packet version */
+ if (b->message_version != 0 && b->message_version != (*m)->header->version)
+ remarshal = true;
+
+ /* wrong packet endianness */
+ if (b->message_endian != 0 && b->message_endian != (*m)->header->endian)
+ remarshal = true;
+
+ return remarshal ? bus_message_remarshal(b, m) : 0;
+}
+
+int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m) {
+ assert(b);
+ assert(m);
+
+ /* Fake some timestamps, if they were requested, and not
+ * already initialized */
+ if (b->attach_timestamp) {
+ if (m->realtime <= 0)
+ m->realtime = now(CLOCK_REALTIME);
+
+ if (m->monotonic <= 0)
+ m->monotonic = now(CLOCK_MONOTONIC);
+ }
+
+ /* The bus specification says the serial number cannot be 0,
+ * hence let's fill something in for synthetic messages. Since
+ * synthetic messages might have a fake sender and we don't
+ * want to interfere with the real sender's serial numbers we
+ * pick a fixed, artificial one. */
+ return sd_bus_message_seal(m, UINT32_MAX, 0);
+}
+
+static int bus_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) {
+ int r;
+
+ assert(bus);
+ assert(m);
+
+ r = bus_socket_write_message(bus, m, idx);
+ if (r <= 0)
+ return r;
+
+ if (*idx >= BUS_MESSAGE_SIZE(m))
+ log_debug("Sent message type=%s sender=%s destination=%s path=%s interface=%s member=%s cookie=%" PRIu64 " reply_cookie=%" PRIu64 " signature=%s error-name=%s error-message=%s",
+ bus_message_type_to_string(m->header->type),
+ strna(sd_bus_message_get_sender(m)),
+ strna(sd_bus_message_get_destination(m)),
+ strna(sd_bus_message_get_path(m)),
+ strna(sd_bus_message_get_interface(m)),
+ strna(sd_bus_message_get_member(m)),
+ BUS_MESSAGE_COOKIE(m),
+ m->reply_cookie,
+ strna(m->root_container.signature),
+ strna(m->error.name),
+ strna(m->error.message));
+
+ return r;
+}
+
+static int dispatch_wqueue(sd_bus *bus) {
+ int r, ret = 0;
+
+ assert(bus);
+ assert(IN_SET(bus->state, BUS_RUNNING, BUS_HELLO));
+
+ while (bus->wqueue_size > 0) {
+
+ r = bus_write_message(bus, bus->wqueue[0], &bus->windex);
+ if (r < 0)
+ return r;
+ else if (r == 0)
+ /* Didn't do anything this time */
+ return ret;
+ else if (bus->windex >= BUS_MESSAGE_SIZE(bus->wqueue[0])) {
+ /* Fully written. Let's drop the entry from
+ * the queue.
+ *
+ * This isn't particularly optimized, but
+ * well, this is supposed to be our worst-case
+ * buffer only, and the socket buffer is
+ * supposed to be our primary buffer, and if
+ * it got full, then all bets are off
+ * anyway. */
+
+ bus->wqueue_size--;
+ bus_message_unref_queued(bus->wqueue[0], bus);
+ memmove(bus->wqueue, bus->wqueue + 1, sizeof(sd_bus_message*) * bus->wqueue_size);
+ bus->windex = 0;
+
+ ret = 1;
+ }
+ }
+
+ return ret;
+}
+
+static int bus_read_message(sd_bus *bus) {
+ assert(bus);
+
+ return bus_socket_read_message(bus);
+}
+
+int bus_rqueue_make_room(sd_bus *bus) {
+ assert(bus);
+
+ if (bus->rqueue_size >= BUS_RQUEUE_MAX)
+ return -ENOBUFS;
+
+ if (!GREEDY_REALLOC(bus->rqueue, bus->rqueue_size + 1))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void rqueue_drop_one(sd_bus *bus, size_t i) {
+ assert(bus);
+ assert(i < bus->rqueue_size);
+
+ bus_message_unref_queued(bus->rqueue[i], bus);
+ memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1));
+ bus->rqueue_size--;
+}
+
+static int dispatch_rqueue(sd_bus *bus, sd_bus_message **m) {
+ int r, ret = 0;
+
+ assert(bus);
+ assert(m);
+ assert(IN_SET(bus->state, BUS_RUNNING, BUS_HELLO));
+
+ for (;;) {
+ if (bus->rqueue_size > 0) {
+ /* Dispatch a queued message */
+ *m = sd_bus_message_ref(bus->rqueue[0]);
+ rqueue_drop_one(bus, 0);
+ return 1;
+ }
+
+ /* Try to read a new message */
+ r = bus_read_message(bus);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ *m = NULL;
+ return ret;
+ }
+
+ ret = 1;
+ }
+}
+
+_public_ int sd_bus_send(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m);
+ int r;
+
+ assert_return(m, -EINVAL);
+
+ if (bus)
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ else
+ assert_return(bus = m->bus, -ENOTCONN);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ if (m->n_fds > 0) {
+ r = sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EOPNOTSUPP;
+ }
+
+ /* If the cookie number isn't kept, then we know that no reply
+ * is expected */
+ if (!cookie && !m->sealed)
+ m->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED;
+
+ r = bus_seal_message(bus, m, 0);
+ if (r < 0)
+ return r;
+
+ /* Remarshall if we have to. This will possibly unref the
+ * message and place a replacement in m */
+ r = bus_remarshal_message(bus, &m);
+ if (r < 0)
+ return r;
+
+ /* If this is a reply and no reply was requested, then let's
+ * suppress this, if we can */
+ if (m->dont_send)
+ goto finish;
+
+ if (IN_SET(bus->state, BUS_RUNNING, BUS_HELLO) && bus->wqueue_size <= 0) {
+ size_t idx = 0;
+
+ r = bus_write_message(bus, m, &idx);
+ if (r < 0) {
+ if (ERRNO_IS_DISCONNECT(r)) {
+ bus_enter_closing(bus);
+ return -ECONNRESET;
+ }
+
+ return r;
+ }
+
+ if (idx < BUS_MESSAGE_SIZE(m)) {
+ /* Wasn't fully written. So let's remember how
+ * much was written. Note that the first entry
+ * of the wqueue array is always allocated so
+ * that we always can remember how much was
+ * written. */
+ bus->wqueue[0] = bus_message_ref_queued(m, bus);
+ bus->wqueue_size = 1;
+ bus->windex = idx;
+ }
+
+ } else {
+ /* Just append it to the queue. */
+
+ if (bus->wqueue_size >= BUS_WQUEUE_MAX)
+ return -ENOBUFS;
+
+ if (!GREEDY_REALLOC(bus->wqueue, bus->wqueue_size + 1))
+ return -ENOMEM;
+
+ bus->wqueue[bus->wqueue_size++] = bus_message_ref_queued(m, bus);
+ }
+
+finish:
+ if (cookie)
+ *cookie = BUS_MESSAGE_COOKIE(m);
+
+ return 1;
+}
+
+_public_ int sd_bus_send_to(sd_bus *bus, sd_bus_message *m, const char *destination, uint64_t *cookie) {
+ int r;
+
+ assert_return(m, -EINVAL);
+
+ if (bus)
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ else
+ assert_return(bus = m->bus, -ENOTCONN);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ if (!streq_ptr(m->destination, destination)) {
+
+ if (!destination)
+ return -EEXIST;
+
+ r = sd_bus_message_set_destination(m, destination);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_send(bus, m, cookie);
+}
+
+static usec_t calc_elapse(sd_bus *bus, uint64_t usec) {
+ assert(bus);
+
+ assert_cc(sizeof(usec_t) == sizeof(uint64_t));
+
+ if (usec == USEC_INFINITY)
+ return 0;
+
+ /* We start all timeouts the instant we enter BUS_HELLO/BUS_RUNNING state, so that the don't run in parallel
+ * with any connection setup states. Hence, if a method callback is started earlier than that we just store the
+ * relative timestamp, and afterwards the absolute one. */
+
+ if (IN_SET(bus->state, BUS_WATCH_BIND, BUS_OPENING, BUS_AUTHENTICATING))
+ return usec;
+ else
+ return usec_add(now(CLOCK_MONOTONIC), usec);
+}
+
+static int timeout_compare(const void *a, const void *b) {
+ const struct reply_callback *x = a, *y = b;
+
+ if (x->timeout_usec != 0 && y->timeout_usec == 0)
+ return -1;
+
+ if (x->timeout_usec == 0 && y->timeout_usec != 0)
+ return 1;
+
+ return CMP(x->timeout_usec, y->timeout_usec);
+}
+
+_public_ int sd_bus_call_async(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ sd_bus_message *_m,
+ sd_bus_message_handler_t callback,
+ void *userdata,
+ uint64_t usec) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m);
+ _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *s = NULL;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
+ assert_return(!m->sealed || (!!callback == !(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)), -EINVAL);
+
+ if (bus)
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ else
+ assert_return(bus = m->bus, -ENOTCONN);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ /* If no callback is specified and there's no interest in a slot, then there's no reason to ask for a reply */
+ if (!callback && !slot && !m->sealed)
+ m->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED;
+
+ r = ordered_hashmap_ensure_allocated(&bus->reply_callbacks, &uint64_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = prioq_ensure_allocated(&bus->reply_callbacks_prioq, timeout_compare);
+ if (r < 0)
+ return r;
+
+ r = bus_seal_message(bus, m, usec);
+ if (r < 0)
+ return r;
+
+ r = bus_remarshal_message(bus, &m);
+ if (r < 0)
+ return r;
+
+ if (slot || callback) {
+ s = bus_slot_allocate(bus, !slot, BUS_REPLY_CALLBACK, sizeof(struct reply_callback), userdata);
+ if (!s)
+ return -ENOMEM;
+
+ s->reply_callback.callback = callback;
+
+ s->reply_callback.cookie = BUS_MESSAGE_COOKIE(m);
+ r = ordered_hashmap_put(bus->reply_callbacks, &s->reply_callback.cookie, &s->reply_callback);
+ if (r < 0) {
+ s->reply_callback.cookie = 0;
+ return r;
+ }
+
+ s->reply_callback.timeout_usec = calc_elapse(bus, m->timeout);
+ if (s->reply_callback.timeout_usec != 0) {
+ r = prioq_put(bus->reply_callbacks_prioq, &s->reply_callback, &s->reply_callback.prioq_idx);
+ if (r < 0) {
+ s->reply_callback.timeout_usec = 0;
+ return r;
+ }
+ }
+ }
+
+ r = sd_bus_send(bus, m, s ? &s->reply_callback.cookie : NULL);
+ if (r < 0)
+ return r;
+
+ if (slot)
+ *slot = s;
+ s = NULL;
+
+ return r;
+}
+
+int bus_ensure_running(sd_bus *bus) {
+ int r;
+
+ assert(bus);
+
+ if (bus->state == BUS_RUNNING)
+ return 1;
+
+ for (;;) {
+ if (IN_SET(bus->state, BUS_UNSET, BUS_CLOSED, BUS_CLOSING))
+ return -ENOTCONN;
+
+ r = sd_bus_process(bus, NULL);
+ if (r < 0)
+ return r;
+ if (bus->state == BUS_RUNNING)
+ return 1;
+ if (r > 0)
+ continue;
+
+ r = sd_bus_wait(bus, UINT64_MAX);
+ if (r < 0)
+ return r;
+ }
+}
+
+_public_ int sd_bus_call(
+ sd_bus *bus,
+ sd_bus_message *_m,
+ uint64_t usec,
+ sd_bus_error *error,
+ sd_bus_message **reply) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m);
+ usec_t timeout;
+ uint64_t cookie;
+ size_t i;
+ int r;
+
+ bus_assert_return(m, -EINVAL, error);
+ bus_assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL, error);
+ bus_assert_return(!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL, error);
+ bus_assert_return(!bus_error_is_dirty(error), -EINVAL, error);
+
+ if (bus)
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ else
+ assert_return(bus = m->bus, -ENOTCONN);
+ bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+
+ if (!BUS_IS_OPEN(bus->state)) {
+ r = -ENOTCONN;
+ goto fail;
+ }
+
+ r = bus_ensure_running(bus);
+ if (r < 0)
+ goto fail;
+
+ i = bus->rqueue_size;
+
+ r = bus_seal_message(bus, m, usec);
+ if (r < 0)
+ goto fail;
+
+ r = bus_remarshal_message(bus, &m);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_send(bus, m, &cookie);
+ if (r < 0)
+ goto fail;
+
+ timeout = calc_elapse(bus, m->timeout);
+
+ for (;;) {
+ usec_t left;
+
+ while (i < bus->rqueue_size) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *incoming = NULL;
+
+ incoming = sd_bus_message_ref(bus->rqueue[i]);
+
+ if (incoming->reply_cookie == cookie) {
+ /* Found a match! */
+
+ rqueue_drop_one(bus, i);
+ log_debug_bus_message(incoming);
+
+ if (incoming->header->type == SD_BUS_MESSAGE_METHOD_RETURN) {
+
+ if (incoming->n_fds <= 0 || bus->accept_fd) {
+ if (reply)
+ *reply = TAKE_PTR(incoming);
+
+ return 1;
+ }
+
+ return sd_bus_error_set(error, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptors which I couldn't accept. Sorry.");
+
+ } else if (incoming->header->type == SD_BUS_MESSAGE_METHOD_ERROR)
+ return sd_bus_error_copy(error, &incoming->error);
+ else {
+ r = -EIO;
+ goto fail;
+ }
+
+ } else if (BUS_MESSAGE_COOKIE(incoming) == cookie &&
+ bus->unique_name &&
+ incoming->sender &&
+ streq(bus->unique_name, incoming->sender)) {
+
+ rqueue_drop_one(bus, i);
+
+ /* Our own message? Somebody is trying to send its own client a message,
+ * let's not dead-lock, let's fail immediately. */
+
+ r = -ELOOP;
+ goto fail;
+ }
+
+ /* Try to read more, right-away */
+ i++;
+ }
+
+ r = bus_read_message(bus);
+ if (r < 0) {
+ if (ERRNO_IS_DISCONNECT(r)) {
+ bus_enter_closing(bus);
+ r = -ECONNRESET;
+ }
+
+ goto fail;
+ }
+ if (r > 0)
+ continue;
+
+ if (timeout > 0) {
+ usec_t n;
+
+ n = now(CLOCK_MONOTONIC);
+ if (n >= timeout) {
+ r = -ETIMEDOUT;
+ goto fail;
+ }
+
+ left = timeout - n;
+ } else
+ left = UINT64_MAX;
+
+ r = bus_poll(bus, true, left);
+ if (r < 0)
+ goto fail;
+ if (r == 0) {
+ r = -ETIMEDOUT;
+ goto fail;
+ }
+
+ r = dispatch_wqueue(bus);
+ if (r < 0) {
+ if (ERRNO_IS_DISCONNECT(r)) {
+ bus_enter_closing(bus);
+ r = -ECONNRESET;
+ }
+
+ goto fail;
+ }
+ }
+
+fail:
+ return sd_bus_error_set_errno(error, r);
+}
+
+_public_ int sd_bus_get_fd(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(bus->input_fd == bus->output_fd, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (bus->state == BUS_CLOSED)
+ return -ENOTCONN;
+
+ if (bus->inotify_fd >= 0)
+ return bus->inotify_fd;
+
+ if (bus->input_fd >= 0)
+ return bus->input_fd;
+
+ return -ENOTCONN;
+}
+
+_public_ int sd_bus_get_events(sd_bus *bus) {
+ int flags = 0;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ switch (bus->state) {
+
+ case BUS_UNSET:
+ case BUS_CLOSED:
+ return -ENOTCONN;
+
+ case BUS_WATCH_BIND:
+ flags |= POLLIN;
+ break;
+
+ case BUS_OPENING:
+ flags |= POLLOUT;
+ break;
+
+ case BUS_AUTHENTICATING:
+ if (bus_socket_auth_needs_write(bus))
+ flags |= POLLOUT;
+
+ flags |= POLLIN;
+ break;
+
+ case BUS_RUNNING:
+ case BUS_HELLO:
+ if (bus->rqueue_size <= 0)
+ flags |= POLLIN;
+ if (bus->wqueue_size > 0)
+ flags |= POLLOUT;
+ break;
+
+ case BUS_CLOSING:
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ return flags;
+}
+
+_public_ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) {
+ struct reply_callback *c;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(timeout_usec, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state) && bus->state != BUS_CLOSING)
+ return -ENOTCONN;
+
+ if (bus->track_queue) {
+ *timeout_usec = 0;
+ return 1;
+ }
+
+ switch (bus->state) {
+
+ case BUS_AUTHENTICATING:
+ *timeout_usec = bus->auth_timeout;
+ return 1;
+
+ case BUS_RUNNING:
+ case BUS_HELLO:
+ if (bus->rqueue_size > 0) {
+ *timeout_usec = 0;
+ return 1;
+ }
+
+ c = prioq_peek(bus->reply_callbacks_prioq);
+ if (!c) {
+ *timeout_usec = UINT64_MAX;
+ return 0;
+ }
+
+ if (c->timeout_usec == 0) {
+ *timeout_usec = UINT64_MAX;
+ return 0;
+ }
+
+ *timeout_usec = c->timeout_usec;
+ return 1;
+
+ case BUS_CLOSING:
+ *timeout_usec = 0;
+ return 1;
+
+ case BUS_WATCH_BIND:
+ case BUS_OPENING:
+ *timeout_usec = UINT64_MAX;
+ return 0;
+
+ default:
+ assert_not_reached();
+ }
+}
+
+static int process_timeout(sd_bus *bus) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message* m = NULL;
+ struct reply_callback *c;
+ sd_bus_slot *slot;
+ bool is_hello;
+ usec_t n;
+ int r;
+
+ assert(bus);
+ assert(IN_SET(bus->state, BUS_RUNNING, BUS_HELLO));
+
+ c = prioq_peek(bus->reply_callbacks_prioq);
+ if (!c)
+ return 0;
+
+ n = now(CLOCK_MONOTONIC);
+ if (c->timeout_usec > n)
+ return 0;
+
+ r = bus_message_new_synthetic_error(
+ bus,
+ c->cookie,
+ &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call timed out"),
+ &m);
+ if (r < 0)
+ return r;
+
+ m->read_counter = ++bus->read_counter;
+
+ r = bus_seal_synthetic_message(bus, m);
+ if (r < 0)
+ return r;
+
+ assert_se(prioq_pop(bus->reply_callbacks_prioq) == c);
+ c->timeout_usec = 0;
+
+ ordered_hashmap_remove(bus->reply_callbacks, &c->cookie);
+ c->cookie = 0;
+
+ slot = container_of(c, sd_bus_slot, reply_callback);
+
+ bus->iteration_counter++;
+
+ is_hello = bus->state == BUS_HELLO && c->callback == hello_callback;
+
+ bus->current_message = m;
+ bus->current_slot = sd_bus_slot_ref(slot);
+ bus->current_handler = c->callback;
+ bus->current_userdata = slot->userdata;
+ r = c->callback(m, slot->userdata, &error_buffer);
+ bus->current_userdata = NULL;
+ bus->current_handler = NULL;
+ bus->current_slot = NULL;
+ bus->current_message = NULL;
+
+ if (slot->floating)
+ bus_slot_disconnect(slot, true);
+
+ sd_bus_slot_unref(slot);
+
+ /* When this is the hello message and it timed out, then make sure to propagate the error up, don't just log
+ * and ignore the callback handler's return value. */
+ if (is_hello)
+ return r;
+
+ return bus_maybe_reply_error(m, r, &error_buffer);
+}
+
+static int process_hello(sd_bus *bus, sd_bus_message *m) {
+ assert(bus);
+ assert(m);
+
+ if (bus->state != BUS_HELLO)
+ return 0;
+
+ /* Let's make sure the first message on the bus is the HELLO
+ * reply. But note that we don't actually parse the message
+ * here (we leave that to the usual handling), we just verify
+ * we don't let any earlier msg through. */
+
+ if (!IN_SET(m->header->type, SD_BUS_MESSAGE_METHOD_RETURN, SD_BUS_MESSAGE_METHOD_ERROR))
+ return -EIO;
+
+ if (m->reply_cookie != 1)
+ return -EIO;
+
+ return 0;
+}
+
+static int process_reply(sd_bus *bus, sd_bus_message *m) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *synthetic_reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
+ struct reply_callback *c;
+ sd_bus_slot *slot;
+ bool is_hello;
+ int r;
+
+ assert(bus);
+ assert(m);
+
+ if (!IN_SET(m->header->type, SD_BUS_MESSAGE_METHOD_RETURN, SD_BUS_MESSAGE_METHOD_ERROR))
+ return 0;
+
+ if (m->destination && bus->unique_name && !streq_ptr(m->destination, bus->unique_name))
+ return 0;
+
+ c = ordered_hashmap_remove(bus->reply_callbacks, &m->reply_cookie);
+ if (!c)
+ return 0;
+
+ c->cookie = 0;
+
+ slot = container_of(c, sd_bus_slot, reply_callback);
+
+ if (m->n_fds > 0 && !bus->accept_fd) {
+
+ /* If the reply contained a file descriptor which we
+ * didn't want we pass an error instead. */
+
+ r = bus_message_new_synthetic_error(
+ bus,
+ m->reply_cookie,
+ &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptor"),
+ &synthetic_reply);
+ if (r < 0)
+ return r;
+
+ /* Copy over original timestamp */
+ synthetic_reply->realtime = m->realtime;
+ synthetic_reply->monotonic = m->monotonic;
+ synthetic_reply->seqnum = m->seqnum;
+ synthetic_reply->read_counter = m->read_counter;
+
+ r = bus_seal_synthetic_message(bus, synthetic_reply);
+ if (r < 0)
+ return r;
+
+ m = synthetic_reply;
+ } else {
+ r = sd_bus_message_rewind(m, true);
+ if (r < 0)
+ return r;
+ }
+
+ if (c->timeout_usec != 0) {
+ prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx);
+ c->timeout_usec = 0;
+ }
+
+ is_hello = bus->state == BUS_HELLO && c->callback == hello_callback;
+
+ bus->current_slot = sd_bus_slot_ref(slot);
+ bus->current_handler = c->callback;
+ bus->current_userdata = slot->userdata;
+ r = c->callback(m, slot->userdata, &error_buffer);
+ bus->current_userdata = NULL;
+ bus->current_handler = NULL;
+ bus->current_slot = NULL;
+
+ if (slot->floating)
+ bus_slot_disconnect(slot, true);
+
+ sd_bus_slot_unref(slot);
+
+ /* When this is the hello message and it failed, then make sure to propagate the error up, don't just log and
+ * ignore the callback handler's return value. */
+ if (is_hello)
+ return r;
+
+ return bus_maybe_reply_error(m, r, &error_buffer);
+}
+
+static int process_filter(sd_bus *bus, sd_bus_message *m) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(bus);
+ assert(m);
+
+ do {
+ bus->filter_callbacks_modified = false;
+
+ LIST_FOREACH(callbacks, l, bus->filter_callbacks) {
+ sd_bus_slot *slot;
+
+ if (bus->filter_callbacks_modified)
+ break;
+
+ /* Don't run this more than once per iteration */
+ if (l->last_iteration == bus->iteration_counter)
+ continue;
+
+ l->last_iteration = bus->iteration_counter;
+
+ r = sd_bus_message_rewind(m, true);
+ if (r < 0)
+ return r;
+
+ slot = container_of(l, sd_bus_slot, filter_callback);
+
+ bus->current_slot = sd_bus_slot_ref(slot);
+ bus->current_handler = l->callback;
+ bus->current_userdata = slot->userdata;
+ r = l->callback(m, slot->userdata, &error_buffer);
+ bus->current_userdata = NULL;
+ bus->current_handler = NULL;
+ bus->current_slot = sd_bus_slot_unref(slot);
+
+ r = bus_maybe_reply_error(m, r, &error_buffer);
+ if (r != 0)
+ return r;
+
+ }
+
+ } while (bus->filter_callbacks_modified);
+
+ return 0;
+}
+
+static int process_match(sd_bus *bus, sd_bus_message *m) {
+ int r;
+
+ assert(bus);
+ assert(m);
+
+ do {
+ bus->match_callbacks_modified = false;
+
+ r = bus_match_run(bus, &bus->match_callbacks, m);
+ if (r != 0)
+ return r;
+
+ } while (bus->match_callbacks_modified);
+
+ return 0;
+}
+
+static int process_builtin(sd_bus *bus, sd_bus_message *m) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+
+ assert(bus);
+ assert(m);
+
+ if (bus->is_monitor)
+ return 0;
+
+ if (bus->manual_peer_interface)
+ return 0;
+
+ if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL)
+ return 0;
+
+ if (!streq_ptr(m->interface, "org.freedesktop.DBus.Peer"))
+ return 0;
+
+ if (m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)
+ return 1;
+
+ if (streq_ptr(m->member, "Ping"))
+ r = sd_bus_message_new_method_return(m, &reply);
+ else if (streq_ptr(m->member, "GetMachineId")) {
+ sd_id128_t id;
+
+ r = sd_id128_get_machine(&id);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "s", SD_ID128_TO_STRING(id));
+ } else {
+ r = sd_bus_message_new_method_errorf(
+ m, &reply,
+ SD_BUS_ERROR_UNKNOWN_METHOD,
+ "Unknown method '%s' on interface '%s'.", m->member, m->interface);
+ }
+ if (r < 0)
+ return r;
+
+ r = sd_bus_send(bus, reply, NULL);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int process_fd_check(sd_bus *bus, sd_bus_message *m) {
+ assert(bus);
+ assert(m);
+
+ /* If we got a message with a file descriptor which we didn't
+ * want to accept, then let's drop it. How can this even
+ * happen? For example, when the kernel queues a message into
+ * an activatable names's queue which allows fds, and then is
+ * delivered to us later even though we ourselves did not
+ * negotiate it. */
+
+ if (bus->is_monitor)
+ return 0;
+
+ if (m->n_fds <= 0)
+ return 0;
+
+ if (bus->accept_fd)
+ return 0;
+
+ if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL)
+ return 1; /* just eat it up */
+
+ return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Message contains file descriptors, which I cannot accept. Sorry.");
+}
+
+static int process_message(sd_bus *bus, sd_bus_message *m) {
+ int r;
+
+ assert(bus);
+ assert(m);
+
+ bus->current_message = m;
+ bus->iteration_counter++;
+
+ log_debug_bus_message(m);
+
+ r = process_hello(bus, m);
+ if (r != 0)
+ goto finish;
+
+ r = process_reply(bus, m);
+ if (r != 0)
+ goto finish;
+
+ r = process_fd_check(bus, m);
+ if (r != 0)
+ goto finish;
+
+ r = process_filter(bus, m);
+ if (r != 0)
+ goto finish;
+
+ r = process_match(bus, m);
+ if (r != 0)
+ goto finish;
+
+ r = process_builtin(bus, m);
+ if (r != 0)
+ goto finish;
+
+ r = bus_process_object(bus, m);
+
+finish:
+ bus->current_message = NULL;
+ return r;
+}
+
+static int dispatch_track(sd_bus *bus) {
+ assert(bus);
+
+ if (!bus->track_queue)
+ return 0;
+
+ bus_track_dispatch(bus->track_queue);
+ return 1;
+}
+
+static int process_running(sd_bus *bus, sd_bus_message **ret) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
+
+ assert(bus);
+ assert(IN_SET(bus->state, BUS_RUNNING, BUS_HELLO));
+
+ r = process_timeout(bus);
+ if (r != 0)
+ goto null_message;
+
+ r = dispatch_wqueue(bus);
+ if (r != 0)
+ goto null_message;
+
+ r = dispatch_track(bus);
+ if (r != 0)
+ goto null_message;
+
+ r = dispatch_rqueue(bus, &m);
+ if (r < 0)
+ return r;
+ if (!m)
+ goto null_message;
+
+ r = process_message(bus, m);
+ if (r != 0)
+ goto null_message;
+
+ if (ret) {
+ r = sd_bus_message_rewind(m, true);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(m);
+ return 1;
+ }
+
+ if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) {
+
+ log_debug("Unprocessed message call sender=%s object=%s interface=%s member=%s",
+ strna(sd_bus_message_get_sender(m)),
+ strna(sd_bus_message_get_path(m)),
+ strna(sd_bus_message_get_interface(m)),
+ strna(sd_bus_message_get_member(m)));
+
+ r = sd_bus_reply_method_errorf(
+ m,
+ SD_BUS_ERROR_UNKNOWN_OBJECT,
+ "Unknown object '%s'.", m->path);
+ if (r < 0)
+ return r;
+ }
+
+ return 1;
+
+null_message:
+ if (r >= 0 && ret)
+ *ret = NULL;
+
+ return r;
+}
+
+static int bus_exit_now(sd_bus *bus) {
+ assert(bus);
+
+ /* Exit due to close, if this is requested. If this is bus object is attached to an event source, invokes
+ * sd_event_exit(), otherwise invokes libc exit(). */
+
+ if (bus->exited) /* did we already exit? */
+ return 0;
+ if (!bus->exit_triggered) /* was the exit condition triggered? */
+ return 0;
+ if (!bus->exit_on_disconnect) /* Shall we actually exit on disconnection? */
+ return 0;
+
+ bus->exited = true; /* never exit more than once */
+
+ log_debug("Bus connection disconnected, exiting.");
+
+ if (bus->event)
+ return sd_event_exit(bus->event, EXIT_FAILURE);
+ else
+ exit(EXIT_FAILURE);
+
+ assert_not_reached();
+}
+
+static int process_closing_reply_callback(sd_bus *bus, struct reply_callback *c) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ sd_bus_slot *slot;
+ int r;
+
+ assert(bus);
+ assert(c);
+
+ r = bus_message_new_synthetic_error(
+ bus,
+ c->cookie,
+ &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Connection terminated"),
+ &m);
+ if (r < 0)
+ return r;
+
+ m->read_counter = ++bus->read_counter;
+
+ r = bus_seal_synthetic_message(bus, m);
+ if (r < 0)
+ return r;
+
+ if (c->timeout_usec != 0) {
+ prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx);
+ c->timeout_usec = 0;
+ }
+
+ ordered_hashmap_remove(bus->reply_callbacks, &c->cookie);
+ c->cookie = 0;
+
+ slot = container_of(c, sd_bus_slot, reply_callback);
+
+ bus->iteration_counter++;
+
+ bus->current_message = m;
+ bus->current_slot = sd_bus_slot_ref(slot);
+ bus->current_handler = c->callback;
+ bus->current_userdata = slot->userdata;
+ r = c->callback(m, slot->userdata, &error_buffer);
+ bus->current_userdata = NULL;
+ bus->current_handler = NULL;
+ bus->current_slot = NULL;
+ bus->current_message = NULL;
+
+ if (slot->floating)
+ bus_slot_disconnect(slot, true);
+
+ sd_bus_slot_unref(slot);
+
+ return bus_maybe_reply_error(m, r, &error_buffer);
+}
+
+static int process_closing(sd_bus *bus, sd_bus_message **ret) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ struct reply_callback *c;
+ int r;
+
+ assert(bus);
+ assert(bus->state == BUS_CLOSING);
+
+ /* First, fail all outstanding method calls */
+ c = ordered_hashmap_first(bus->reply_callbacks);
+ if (c)
+ return process_closing_reply_callback(bus, c);
+
+ /* Then, fake-drop all remaining bus tracking references */
+ if (bus->tracks) {
+ bus_track_close(bus->tracks);
+ return 1;
+ }
+
+ /* Then, synthesize a Disconnected message */
+ r = sd_bus_message_new_signal(
+ bus,
+ &m,
+ "/org/freedesktop/DBus/Local",
+ "org.freedesktop.DBus.Local",
+ "Disconnected");
+ if (r < 0)
+ return r;
+
+ bus_message_set_sender_local(bus, m);
+ m->read_counter = ++bus->read_counter;
+
+ r = bus_seal_synthetic_message(bus, m);
+ if (r < 0)
+ return r;
+
+ sd_bus_close(bus);
+
+ bus->current_message = m;
+ bus->iteration_counter++;
+
+ r = process_filter(bus, m);
+ if (r != 0)
+ goto finish;
+
+ r = process_match(bus, m);
+ if (r != 0)
+ goto finish;
+
+ /* Nothing else to do, exit now, if the condition holds */
+ bus->exit_triggered = true;
+ (void) bus_exit_now(bus);
+
+ if (ret)
+ *ret = TAKE_PTR(m);
+
+ r = 1;
+
+finish:
+ bus->current_message = NULL;
+
+ return r;
+}
+
+static int bus_process_internal(sd_bus *bus, sd_bus_message **ret) {
+ int r;
+
+ /* Returns 0 when we didn't do anything. This should cause the
+ * caller to invoke sd_bus_wait() before returning the next
+ * time. Returns > 0 when we did something, which possibly
+ * means *ret is filled in with an unprocessed message. */
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ /* We don't allow recursively invoking sd_bus_process(). */
+ assert_return(!bus->current_message, -EBUSY);
+ assert(!bus->current_slot); /* This should be NULL whenever bus->current_message is */
+
+ BUS_DONT_DESTROY(bus);
+
+ switch (bus->state) {
+
+ case BUS_UNSET:
+ return -ENOTCONN;
+
+ case BUS_CLOSED:
+ return -ECONNRESET;
+
+ case BUS_WATCH_BIND:
+ r = bus_socket_process_watch_bind(bus);
+ break;
+
+ case BUS_OPENING:
+ r = bus_socket_process_opening(bus);
+ break;
+
+ case BUS_AUTHENTICATING:
+ r = bus_socket_process_authenticating(bus);
+ break;
+
+ case BUS_RUNNING:
+ case BUS_HELLO:
+ r = process_running(bus, ret);
+ if (r >= 0)
+ return r;
+
+ /* This branch initializes *ret, hence we don't use the generic error checking below */
+ break;
+
+ case BUS_CLOSING:
+ return process_closing(bus, ret);
+
+ default:
+ assert_not_reached();
+ }
+
+ if (r < 0) {
+ if (ERRNO_IS_DISCONNECT(r)) {
+ bus_enter_closing(bus);
+ r = 1;
+ } else
+ return r;
+ }
+
+ if (ret)
+ *ret = NULL;
+
+ return r;
+}
+
+_public_ int sd_bus_process(sd_bus *bus, sd_bus_message **ret) {
+ return bus_process_internal(bus, ret);
+}
+
+_public_ int sd_bus_process_priority(sd_bus *bus, int64_t priority, sd_bus_message **ret) {
+ return bus_process_internal(bus, ret);
+}
+
+static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec) {
+ struct pollfd p[2] = {};
+ usec_t m = USEC_INFINITY;
+ int r, n;
+
+ assert(bus);
+
+ if (bus->state == BUS_CLOSING)
+ return 1;
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ if (bus->state == BUS_WATCH_BIND) {
+ assert(bus->inotify_fd >= 0);
+
+ p[0].events = POLLIN;
+ p[0].fd = bus->inotify_fd;
+ n = 1;
+ } else {
+ int e;
+
+ e = sd_bus_get_events(bus);
+ if (e < 0)
+ return e;
+
+ if (need_more)
+ /* The caller really needs some more data, they don't
+ * care about what's already read, or any timeouts
+ * except its own. */
+ e |= POLLIN;
+ else {
+ usec_t until;
+ /* The caller wants to process if there's something to
+ * process, but doesn't care otherwise */
+
+ r = sd_bus_get_timeout(bus, &until);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ m = usec_sub_unsigned(until, now(CLOCK_MONOTONIC));
+ }
+
+ p[0].fd = bus->input_fd;
+ if (bus->output_fd == bus->input_fd) {
+ p[0].events = e;
+ n = 1;
+ } else {
+ p[0].events = e & POLLIN;
+ p[1].fd = bus->output_fd;
+ p[1].events = e & POLLOUT;
+ n = 2;
+ }
+ }
+
+ if (timeout_usec != UINT64_MAX && (m == USEC_INFINITY || timeout_usec < m))
+ m = timeout_usec;
+
+ r = ppoll_usec(p, n, m);
+ if (r <= 0)
+ return r;
+
+ return 1;
+}
+
+_public_ int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) {
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (bus->state == BUS_CLOSING)
+ return 0;
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ if (bus->rqueue_size > 0)
+ return 0;
+
+ return bus_poll(bus, false, timeout_usec);
+}
+
+_public_ int sd_bus_flush(sd_bus *bus) {
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (bus->state == BUS_CLOSING)
+ return 0;
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ /* We never were connected? Don't hang in inotify for good, as there's no timeout set for it */
+ if (bus->state == BUS_WATCH_BIND)
+ return -EUNATCH;
+
+ r = bus_ensure_running(bus);
+ if (r < 0)
+ return r;
+
+ if (bus->wqueue_size <= 0)
+ return 0;
+
+ for (;;) {
+ r = dispatch_wqueue(bus);
+ if (r < 0) {
+ if (ERRNO_IS_DISCONNECT(r)) {
+ bus_enter_closing(bus);
+ return -ECONNRESET;
+ }
+
+ return r;
+ }
+
+ if (bus->wqueue_size <= 0)
+ return 0;
+
+ r = bus_poll(bus, false, UINT64_MAX);
+ if (r < 0)
+ return r;
+ }
+}
+
+_public_ int sd_bus_add_filter(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ sd_bus_message_handler_t callback,
+ void *userdata) {
+
+ sd_bus_slot *s;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(callback, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ s = bus_slot_allocate(bus, !slot, BUS_FILTER_CALLBACK, sizeof(struct filter_callback), userdata);
+ if (!s)
+ return -ENOMEM;
+
+ s->filter_callback.callback = callback;
+
+ bus->filter_callbacks_modified = true;
+ LIST_PREPEND(callbacks, bus->filter_callbacks, &s->filter_callback);
+
+ if (slot)
+ *slot = s;
+
+ return 0;
+}
+
+static int add_match_callback(
+ sd_bus_message *m,
+ void *userdata,
+ sd_bus_error *ret_error) {
+
+ sd_bus_slot *match_slot = ASSERT_PTR(userdata);
+ bool failed = false;
+ int r;
+
+ assert(m);
+
+ sd_bus_slot_ref(match_slot);
+
+ if (sd_bus_message_is_method_error(m, NULL)) {
+ log_debug_errno(sd_bus_message_get_errno(m),
+ "Unable to add match %s, failing connection: %s",
+ match_slot->match_callback.match_string,
+ sd_bus_message_get_error(m)->message);
+
+ failed = true;
+ } else
+ log_debug("Match %s successfully installed.", match_slot->match_callback.match_string);
+
+ if (match_slot->match_callback.install_callback) {
+ sd_bus *bus;
+
+ bus = sd_bus_message_get_bus(m);
+
+ /* This function has been called as slot handler, and we want to call another slot handler. Let's
+ * update the slot callback metadata temporarily with our own data, and then revert back to the old
+ * values. */
+
+ assert(bus->current_slot == match_slot->match_callback.install_slot);
+ assert(bus->current_handler == add_match_callback);
+ assert(bus->current_userdata == userdata);
+
+ bus->current_slot = match_slot;
+ bus->current_handler = match_slot->match_callback.install_callback;
+ bus->current_userdata = match_slot->userdata;
+
+ r = match_slot->match_callback.install_callback(m, match_slot->userdata, ret_error);
+
+ bus->current_slot = match_slot->match_callback.install_slot;
+ bus->current_handler = add_match_callback;
+ bus->current_userdata = userdata;
+ } else {
+ if (failed) /* Generic failure handling: destroy the connection */
+ bus_enter_closing(sd_bus_message_get_bus(m));
+
+ r = 1;
+ }
+
+ /* We don't need the install method reply slot anymore, let's free it */
+ match_slot->match_callback.install_slot = sd_bus_slot_unref(match_slot->match_callback.install_slot);
+
+ if (failed && match_slot->floating)
+ bus_slot_disconnect(match_slot, true);
+
+ sd_bus_slot_unref(match_slot);
+
+ return r;
+}
+
+static int bus_add_match_full(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ bool asynchronous,
+ const char *match,
+ sd_bus_message_handler_t callback,
+ sd_bus_message_handler_t install_callback,
+ void *userdata) {
+
+ struct bus_match_component *components = NULL;
+ unsigned n_components = 0;
+ sd_bus_slot *s = NULL;
+ int r = 0;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(match, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ r = bus_match_parse(match, &components, &n_components);
+ if (r < 0)
+ goto finish;
+
+ s = bus_slot_allocate(bus, !slot, BUS_MATCH_CALLBACK, sizeof(struct match_callback), userdata);
+ if (!s) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ s->match_callback.callback = callback;
+ s->match_callback.install_callback = install_callback;
+
+ if (bus->bus_client) {
+ enum bus_match_scope scope;
+
+ scope = bus_match_get_scope(components, n_components);
+
+ /* Do not install server-side matches for matches against the local service, interface or bus path. */
+ if (scope != BUS_MATCH_LOCAL) {
+
+ /* We store the original match string, so that we can use it to remove the match again. */
+
+ s->match_callback.match_string = strdup(match);
+ if (!s->match_callback.match_string) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (asynchronous) {
+ r = bus_add_match_internal_async(bus,
+ &s->match_callback.install_slot,
+ s->match_callback.match_string,
+ add_match_callback,
+ s);
+
+ if (r < 0)
+ goto finish;
+
+ /* Make the slot of the match call floating now. We need the reference, but we don't
+ * want that this match pins the bus object, hence we first create it non-floating, but
+ * then make it floating. */
+ r = sd_bus_slot_set_floating(s->match_callback.install_slot, true);
+ } else
+ r = bus_add_match_internal(bus, s->match_callback.match_string, &s->match_callback.after);
+ if (r < 0)
+ goto finish;
+
+ s->match_added = true;
+ }
+ }
+
+ bus->match_callbacks_modified = true;
+ r = bus_match_add(&bus->match_callbacks, components, n_components, &s->match_callback);
+ if (r < 0)
+ goto finish;
+
+ if (slot)
+ *slot = s;
+ s = NULL;
+
+finish:
+ bus_match_parse_free(components, n_components);
+ sd_bus_slot_unref(s);
+
+ return r;
+}
+
+_public_ int sd_bus_add_match(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ const char *match,
+ sd_bus_message_handler_t callback,
+ void *userdata) {
+
+ return bus_add_match_full(bus, slot, false, match, callback, NULL, userdata);
+}
+
+_public_ int sd_bus_add_match_async(
+ sd_bus *bus,
+ sd_bus_slot **slot,
+ const char *match,
+ sd_bus_message_handler_t callback,
+ sd_bus_message_handler_t install_callback,
+ void *userdata) {
+
+ return bus_add_match_full(bus, slot, true, match, callback, install_callback, userdata);
+}
+
+bool bus_pid_changed(sd_bus *bus) {
+ assert(bus);
+
+ /* We don't support people creating a bus connection and
+ * keeping it around over a fork(). Let's complain. */
+
+ return bus->original_pid != getpid_cached();
+}
+
+static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ /* Note that this is called both on input_fd, output_fd as well as inotify_fd events */
+
+ r = sd_bus_process(bus, NULL);
+ if (r < 0) {
+ log_debug_errno(r, "Processing of bus failed, closing down: %m");
+ bus_enter_closing(bus);
+ }
+
+ return 1;
+}
+
+static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r;
+
+ r = sd_bus_process(bus, NULL);
+ if (r < 0) {
+ log_debug_errno(r, "Processing of bus failed, closing down: %m");
+ bus_enter_closing(bus);
+ }
+
+ return 1;
+}
+
+static int prepare_callback(sd_event_source *s, void *userdata) {
+ sd_bus *bus = ASSERT_PTR(userdata);
+ int r, e;
+ usec_t until;
+
+ assert(s);
+
+ e = sd_bus_get_events(bus);
+ if (e < 0) {
+ r = e;
+ goto fail;
+ }
+
+ if (bus->output_fd != bus->input_fd) {
+
+ r = sd_event_source_set_io_events(bus->input_io_event_source, e & POLLIN);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_io_events(bus->output_io_event_source, e & POLLOUT);
+ } else
+ r = sd_event_source_set_io_events(bus->input_io_event_source, e);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_get_timeout(bus, &until);
+ if (r < 0)
+ goto fail;
+ if (r > 0) {
+ int j;
+
+ j = sd_event_source_set_time(bus->time_event_source, until);
+ if (j < 0) {
+ r = j;
+ goto fail;
+ }
+ }
+
+ r = sd_event_source_set_enabled(bus->time_event_source, r > 0 ? SD_EVENT_ONESHOT : SD_EVENT_OFF);
+ if (r < 0)
+ goto fail;
+
+ return 1;
+
+fail:
+ log_debug_errno(r, "Preparing of bus events failed, closing down: %m");
+ bus_enter_closing(bus);
+
+ return 1;
+}
+
+static int quit_callback(sd_event_source *event, void *userdata) {
+ sd_bus *bus = userdata;
+
+ assert(event);
+
+ if (bus->close_on_exit) {
+ sd_bus_flush(bus);
+ sd_bus_close(bus);
+ }
+
+ return 1;
+}
+
+int bus_attach_io_events(sd_bus *bus) {
+ int r;
+
+ assert(bus);
+
+ if (bus->input_fd < 0)
+ return 0;
+
+ if (!bus->event)
+ return 0;
+
+ if (!bus->input_io_event_source) {
+ r = sd_event_add_io(bus->event, &bus->input_io_event_source, bus->input_fd, 0, io_callback, bus);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_prepare(bus->input_io_event_source, prepare_callback);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(bus->input_io_event_source, bus->event_priority);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_description(bus->input_io_event_source, "bus-input");
+ } else
+ r = sd_event_source_set_io_fd(bus->input_io_event_source, bus->input_fd);
+
+ if (r < 0)
+ return r;
+
+ if (bus->output_fd != bus->input_fd) {
+ assert(bus->output_fd >= 0);
+
+ if (!bus->output_io_event_source) {
+ r = sd_event_add_io(bus->event, &bus->output_io_event_source, bus->output_fd, 0, io_callback, bus);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(bus->output_io_event_source, bus->event_priority);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_description(bus->input_io_event_source, "bus-output");
+ } else
+ r = sd_event_source_set_io_fd(bus->output_io_event_source, bus->output_fd);
+
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static void bus_detach_io_events(sd_bus *bus) {
+ assert(bus);
+
+ bus->input_io_event_source = sd_event_source_disable_unref(bus->input_io_event_source);
+ bus->output_io_event_source = sd_event_source_disable_unref(bus->output_io_event_source);
+}
+
+int bus_attach_inotify_event(sd_bus *bus) {
+ int r;
+
+ assert(bus);
+
+ if (bus->inotify_fd < 0)
+ return 0;
+
+ if (!bus->event)
+ return 0;
+
+ if (!bus->inotify_event_source) {
+ r = sd_event_add_io(bus->event, &bus->inotify_event_source, bus->inotify_fd, EPOLLIN, io_callback, bus);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(bus->inotify_event_source, bus->event_priority);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_description(bus->inotify_event_source, "bus-inotify");
+ } else
+ r = sd_event_source_set_io_fd(bus->inotify_event_source, bus->inotify_fd);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+_public_ int sd_bus_attach_event(sd_bus *bus, sd_event *event, int priority) {
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus->event, -EBUSY);
+
+ assert(!bus->input_io_event_source);
+ assert(!bus->output_io_event_source);
+ assert(!bus->time_event_source);
+
+ if (event)
+ bus->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&bus->event);
+ if (r < 0)
+ return r;
+ }
+
+ bus->event_priority = priority;
+
+ r = sd_event_add_time(bus->event, &bus->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, bus);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_priority(bus->time_event_source, priority);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_description(bus->time_event_source, "bus-time");
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_add_exit(bus->event, &bus->quit_event_source, quit_callback, bus);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_description(bus->quit_event_source, "bus-exit");
+ if (r < 0)
+ goto fail;
+
+ r = bus_attach_io_events(bus);
+ if (r < 0)
+ goto fail;
+
+ r = bus_attach_inotify_event(bus);
+ if (r < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ sd_bus_detach_event(bus);
+ return r;
+}
+
+_public_ int sd_bus_detach_event(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+
+ if (!bus->event)
+ return 0;
+
+ bus_detach_io_events(bus);
+ bus->inotify_event_source = sd_event_source_disable_unref(bus->inotify_event_source);
+ bus->time_event_source = sd_event_source_disable_unref(bus->time_event_source);
+ bus->quit_event_source = sd_event_source_disable_unref(bus->quit_event_source);
+
+ bus->event = sd_event_unref(bus->event);
+ return 1;
+}
+
+_public_ sd_event* sd_bus_get_event(sd_bus *bus) {
+ assert_return(bus = bus_resolve(bus), NULL);
+
+ return bus->event;
+}
+
+_public_ sd_bus_message* sd_bus_get_current_message(sd_bus *bus) {
+ assert_return(bus = bus_resolve(bus), NULL);
+
+ return bus->current_message;
+}
+
+_public_ sd_bus_slot* sd_bus_get_current_slot(sd_bus *bus) {
+ assert_return(bus = bus_resolve(bus), NULL);
+
+ return bus->current_slot;
+}
+
+_public_ sd_bus_message_handler_t sd_bus_get_current_handler(sd_bus *bus) {
+ assert_return(bus = bus_resolve(bus), NULL);
+
+ return bus->current_handler;
+}
+
+_public_ void* sd_bus_get_current_userdata(sd_bus *bus) {
+ assert_return(bus = bus_resolve(bus), NULL);
+
+ return bus->current_userdata;
+}
+
+static int bus_default(int (*bus_open)(sd_bus **), sd_bus **default_bus, sd_bus **ret) {
+ sd_bus *b = NULL;
+ int r;
+
+ assert(bus_open);
+ assert(default_bus);
+
+ if (!ret)
+ return !!*default_bus;
+
+ if (*default_bus) {
+ *ret = sd_bus_ref(*default_bus);
+ return 0;
+ }
+
+ r = bus_open(&b);
+ if (r < 0)
+ return r;
+
+ b->default_bus_ptr = default_bus;
+ b->tid = gettid();
+ *default_bus = b;
+
+ *ret = b;
+ return 1;
+}
+
+_public_ int sd_bus_default_system(sd_bus **ret) {
+ return bus_default(sd_bus_open_system, &default_system_bus, ret);
+}
+
+_public_ int sd_bus_default_user(sd_bus **ret) {
+ return bus_default(sd_bus_open_user, &default_user_bus, ret);
+}
+
+_public_ int sd_bus_default(sd_bus **ret) {
+ int (*bus_open)(sd_bus **) = NULL;
+ sd_bus **busp;
+
+ busp = bus_choose_default(&bus_open);
+ return bus_default(bus_open, busp, ret);
+}
+
+_public_ int sd_bus_get_tid(sd_bus *b, pid_t *tid) {
+ assert_return(b, -EINVAL);
+ assert_return(tid, -EINVAL);
+ assert_return(!bus_pid_changed(b), -ECHILD);
+
+ if (b->tid != 0) {
+ *tid = b->tid;
+ return 0;
+ }
+
+ if (b->event)
+ return sd_event_get_tid(b->event, tid);
+
+ return -ENXIO;
+}
+
+_public_ int sd_bus_path_encode(const char *prefix, const char *external_id, char **ret_path) {
+ _cleanup_free_ char *e = NULL;
+ char *ret;
+
+ assert_return(object_path_is_valid(prefix), -EINVAL);
+ assert_return(external_id, -EINVAL);
+ assert_return(ret_path, -EINVAL);
+
+ e = bus_label_escape(external_id);
+ if (!e)
+ return -ENOMEM;
+
+ ret = path_join(prefix, e);
+ if (!ret)
+ return -ENOMEM;
+
+ *ret_path = ret;
+ return 0;
+}
+
+_public_ int sd_bus_path_decode(const char *path, const char *prefix, char **external_id) {
+ const char *e;
+ char *ret;
+
+ assert_return(object_path_is_valid(path), -EINVAL);
+ assert_return(object_path_is_valid(prefix), -EINVAL);
+ assert_return(external_id, -EINVAL);
+
+ e = object_path_startswith(path, prefix);
+ if (!e) {
+ *external_id = NULL;
+ return 0;
+ }
+
+ /* Note that 'e' might be an empty string here. That's expected. E.g. a case where the subtree
+ * corresponds to a subtree on a disk, and we want to return something that represents the root
+ * of the filesystem. */
+
+ ret = bus_label_unescape(e);
+ if (!ret)
+ return -ENOMEM;
+
+ *external_id = ret;
+ return 1;
+}
+
+_public_ int sd_bus_path_encode_many(char **out, const char *path_template, ...) {
+ _cleanup_strv_free_ char **labels = NULL;
+ char *path, *path_pos, **label_pos;
+ const char *sep, *template_pos;
+ size_t path_length;
+ va_list list;
+ int r;
+
+ assert_return(out, -EINVAL);
+ assert_return(path_template, -EINVAL);
+
+ path_length = strlen(path_template);
+
+ va_start(list, path_template);
+ for (sep = strchr(path_template, '%'); sep; sep = strchr(sep + 1, '%')) {
+ const char *arg;
+ char *label;
+
+ arg = va_arg(list, const char *);
+ if (!arg) {
+ va_end(list);
+ return -EINVAL;
+ }
+
+ label = bus_label_escape(arg);
+ if (!label) {
+ va_end(list);
+ return -ENOMEM;
+ }
+
+ r = strv_consume(&labels, label);
+ if (r < 0) {
+ va_end(list);
+ return r;
+ }
+
+ /* add label length, but account for the format character */
+ path_length += strlen(label) - 1;
+ }
+ va_end(list);
+
+ path = malloc(path_length + 1);
+ if (!path)
+ return -ENOMEM;
+
+ path_pos = path;
+ label_pos = labels;
+
+ for (template_pos = path_template; *template_pos; ) {
+ sep = strchrnul(template_pos, '%');
+ path_pos = mempcpy(path_pos, template_pos, sep - template_pos);
+ if (!*sep)
+ break;
+
+ path_pos = stpcpy(path_pos, *label_pos++);
+ template_pos = sep + 1;
+ }
+
+ *path_pos = 0;
+ *out = path;
+ return 0;
+}
+
+_public_ int sd_bus_path_decode_many(const char *path, const char *path_template, ...) {
+ _cleanup_strv_free_ char **labels = NULL;
+ const char *template_pos, *path_pos;
+ char **label_pos;
+ va_list list;
+ int r;
+
+ /*
+ * This decodes an object-path based on a template argument. The
+ * template consists of a verbatim path, optionally including special
+ * directives:
+ *
+ * - Each occurrence of '%' in the template matches an arbitrary
+ * substring of a label in the given path. At most one such
+ * directive is allowed per label. For each such directive, the
+ * caller must provide an output parameter (char **) via va_arg. If
+ * NULL is passed, the given label is verified, but not returned.
+ * For each matched label, the *decoded* label is stored in the
+ * passed output argument, and the caller is responsible to free
+ * it. Note that the output arguments are only modified if the
+ * actually path matched the template. Otherwise, they're left
+ * untouched.
+ *
+ * This function returns <0 on error, 0 if the path does not match the
+ * template, 1 if it matched.
+ */
+
+ assert_return(path, -EINVAL);
+ assert_return(path_template, -EINVAL);
+
+ path_pos = path;
+
+ for (template_pos = path_template; *template_pos; ) {
+ const char *sep;
+ size_t length;
+ char *label;
+
+ /* verify everything until the next '%' matches verbatim */
+ sep = strchrnul(template_pos, '%');
+ length = sep - template_pos;
+ if (strncmp(path_pos, template_pos, length))
+ return 0;
+
+ path_pos += length;
+ template_pos += length;
+
+ if (!*template_pos)
+ break;
+
+ /* We found the next '%' character. Everything up until here
+ * matched. We now skip ahead to the end of this label and make
+ * sure it matches the tail of the label in the path. Then we
+ * decode the string in-between and save it for later use. */
+
+ ++template_pos; /* skip over '%' */
+
+ sep = strchrnul(template_pos, '/');
+ length = sep - template_pos; /* length of suffix to match verbatim */
+
+ /* verify the suffixes match */
+ sep = strchrnul(path_pos, '/');
+ if (sep - path_pos < (ssize_t)length ||
+ strncmp(sep - length, template_pos, length))
+ return 0;
+
+ template_pos += length; /* skip over matched label */
+ length = sep - path_pos - length; /* length of sub-label to decode */
+
+ /* store unescaped label for later use */
+ label = bus_label_unescape_n(path_pos, length);
+ if (!label)
+ return -ENOMEM;
+
+ r = strv_consume(&labels, label);
+ if (r < 0)
+ return r;
+
+ path_pos = sep; /* skip decoded label and suffix */
+ }
+
+ /* end of template must match end of path */
+ if (*path_pos)
+ return 0;
+
+ /* copy the labels over to the caller */
+ va_start(list, path_template);
+ for (label_pos = labels; label_pos && *label_pos; ++label_pos) {
+ char **arg;
+
+ arg = va_arg(list, char **);
+ if (arg)
+ *arg = *label_pos;
+ else
+ free(*label_pos);
+ }
+ va_end(list);
+
+ labels = mfree(labels);
+ return 1;
+}
+
+_public_ int sd_bus_try_close(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return -EOPNOTSUPP;
+}
+
+_public_ int sd_bus_get_description(sd_bus *bus, const char **description) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(description, -EINVAL);
+
+ if (bus->description)
+ *description = bus->description;
+ else if (bus->is_system)
+ *description = "system";
+ else if (bus->is_user)
+ *description = "user";
+ else
+ return -ENXIO;
+
+ return 0;
+}
+
+_public_ int sd_bus_get_scope(sd_bus *bus, const char **scope) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(scope, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (bus->is_user) {
+ *scope = "user";
+ return 0;
+ }
+
+ if (bus->is_system) {
+ *scope = "system";
+ return 0;
+ }
+
+ return -ENODATA;
+}
+
+_public_ int sd_bus_get_address(sd_bus *bus, const char **address) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(address, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (bus->address) {
+ *address = bus->address;
+ return 0;
+ }
+
+ return -ENODATA;
+}
+
+_public_ int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *mask) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(mask, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ *mask = bus->creds_mask;
+ return 0;
+}
+
+_public_ int sd_bus_is_bus_client(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return bus->bus_client;
+}
+
+_public_ int sd_bus_is_server(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return bus->is_server;
+}
+
+_public_ int sd_bus_is_anonymous(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return bus->anonymous_auth;
+}
+
+_public_ int sd_bus_is_trusted(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return bus->trusted;
+}
+
+_public_ int sd_bus_is_monitor(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ return bus->is_monitor;
+}
+
+static void flush_close(sd_bus *bus) {
+ if (!bus)
+ return;
+
+ /* Flushes and closes the specified bus. We take a ref before,
+ * to ensure the flushing does not cause the bus to be
+ * unreferenced. */
+
+ sd_bus_flush_close_unref(sd_bus_ref(bus));
+}
+
+_public_ void sd_bus_default_flush_close(void) {
+ flush_close(default_starter_bus);
+ flush_close(default_user_bus);
+ flush_close(default_system_bus);
+}
+
+_public_ int sd_bus_set_exit_on_disconnect(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+
+ /* Turns on exit-on-disconnect, and triggers it immediately if the bus connection was already
+ * disconnected. Note that this is triggered exclusively on disconnections triggered by the server side, never
+ * from the client side. */
+ bus->exit_on_disconnect = b;
+
+ /* If the exit condition was triggered already, exit immediately. */
+ return bus_exit_now(bus);
+}
+
+_public_ int sd_bus_get_exit_on_disconnect(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+
+ return bus->exit_on_disconnect;
+}
+
+_public_ int sd_bus_set_sender(sd_bus *bus, const char *sender) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus->bus_client, -EPERM);
+ assert_return(!sender || service_name_is_valid(sender), -EINVAL);
+
+ return free_and_strdup(&bus->patch_sender, sender);
+}
+
+_public_ int sd_bus_get_sender(sd_bus *bus, const char **ret) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(ret, -EINVAL);
+
+ if (!bus->patch_sender)
+ return -ENODATA;
+
+ *ret = bus->patch_sender;
+ return 0;
+}
+
+_public_ int sd_bus_get_n_queued_read(sd_bus *bus, uint64_t *ret) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(ret, -EINVAL);
+
+ *ret = bus->rqueue_size;
+ return 0;
+}
+
+_public_ int sd_bus_get_n_queued_write(sd_bus *bus, uint64_t *ret) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(ret, -EINVAL);
+
+ *ret = bus->wqueue_size;
+ return 0;
+}
+
+_public_ int sd_bus_set_method_call_timeout(sd_bus *bus, uint64_t usec) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+
+ bus->method_call_timeout = usec;
+ return 0;
+}
+
+_public_ int sd_bus_get_method_call_timeout(sd_bus *bus, uint64_t *ret) {
+ const char *e;
+ usec_t usec;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(ret, -EINVAL);
+
+ if (bus->method_call_timeout != 0) {
+ *ret = bus->method_call_timeout;
+ return 0;
+ }
+
+ e = secure_getenv("SYSTEMD_BUS_TIMEOUT");
+ if (e && parse_sec(e, &usec) >= 0 && usec != 0) {
+ /* Save the parsed value to avoid multiple parsing. To change the timeout value,
+ * use sd_bus_set_method_call_timeout() instead of setenv(). */
+ *ret = bus->method_call_timeout = usec;
+ return 0;
+ }
+
+ *ret = bus->method_call_timeout = BUS_DEFAULT_TIMEOUT;
+ return 0;
+}
+
+_public_ int sd_bus_set_close_on_exit(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+
+ bus->close_on_exit = b;
+ return 0;
+}
+
+_public_ int sd_bus_get_close_on_exit(sd_bus *bus) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+
+ return bus->close_on_exit;
+}
+
+_public_ int sd_bus_enqueue_for_read(sd_bus *bus, sd_bus_message *m) {
+ int r;
+
+ assert_return(bus, -EINVAL);
+ assert_return(bus = bus_resolve(bus), -ENOPKG);
+ assert_return(m, -EINVAL);
+ assert_return(m->sealed, -EINVAL);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ if (!BUS_IS_OPEN(bus->state))
+ return -ENOTCONN;
+
+ /* Re-enqueue a message for reading. This is primarily useful for PolicyKit-style authentication,
+ * where we accept a message, then determine we need to interactively authenticate the user, and then
+ * we want to process the message again. */
+
+ r = bus_rqueue_make_room(bus);
+ if (r < 0)
+ return r;
+
+ bus->rqueue[bus->rqueue_size++] = bus_message_ref_queued(m, bus);
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-address.c b/src/libsystemd/sd-bus/test-bus-address.c
new file mode 100644
index 0000000..347ba1a
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-address.c
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-bus.h"
+
+#include "bus-internal.h"
+#include "log.h"
+#include "string-util.h"
+#include "strv.h"
+#include "tests.h"
+
+static void test_one_address(sd_bus *b,
+ const char *host,
+ int result, const char *expected) {
+ int r;
+
+ r = bus_set_address_system_remote(b, host);
+ log_info("\"%s\" → %d, \"%s\"", host, r, strna(r >= 0 ? b->address : NULL));
+ assert_se(r == result);
+ if (r >= 0)
+ assert_se(streq_ptr(b->address, expected));
+}
+
+TEST(bus_set_address_system_remote) {
+ _cleanup_(sd_bus_unrefp) sd_bus *b = NULL;
+
+ assert_se(sd_bus_new(&b) >= 0);
+ if (!strv_isempty(saved_argv + 1)) {
+ STRV_FOREACH(a, saved_argv + 1)
+ test_one_address(b, *a, 0, NULL);
+ return;
+ };
+
+ test_one_address(b, "host",
+ 0, "unixexec:path=ssh,argv1=-xT,argv2=--,argv3=host,argv4=systemd-stdio-bridge");
+ test_one_address(b, "host:123",
+ 0, "unixexec:path=ssh,argv1=-xT,argv2=-p,argv3=123,argv4=--,argv5=host,argv6=systemd-stdio-bridge");
+ test_one_address(b, "host:123:123",
+ -EINVAL, NULL);
+ test_one_address(b, "host:",
+ -EINVAL, NULL);
+ test_one_address(b, "user@host",
+ 0, "unixexec:path=ssh,argv1=-xT,argv2=--,argv3=user%40host,argv4=systemd-stdio-bridge");
+ test_one_address(b, "user@host@host",
+ -EINVAL, NULL);
+ test_one_address(b, "[::1]",
+ 0, "unixexec:path=ssh,argv1=-xT,argv2=--,argv3=%3a%3a1,argv4=systemd-stdio-bridge");
+ test_one_address(b, "user@[::1]",
+ 0, "unixexec:path=ssh,argv1=-xT,argv2=--,argv3=user%40%3a%3a1,argv4=systemd-stdio-bridge");
+ test_one_address(b, "user@[::1]:99",
+ 0, "unixexec:path=ssh,argv1=-xT,argv2=-p,argv3=99,argv4=--,argv5=user%40%3a%3a1,argv6=systemd-stdio-bridge");
+ test_one_address(b, "user@[::1]:",
+ -EINVAL, NULL);
+ test_one_address(b, "user@[::1:",
+ -EINVAL, NULL);
+ test_one_address(b, "user@",
+ -EINVAL, NULL);
+ test_one_address(b, "user@@",
+ -EINVAL, NULL);
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
diff --git a/src/libsystemd/sd-bus/test-bus-benchmark.c b/src/libsystemd/sd-bus/test-bus-benchmark.c
new file mode 100644
index 0000000..317653b
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-benchmark.c
@@ -0,0 +1,324 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-internal.h"
+#include "bus-kernel.h"
+#include "def.h"
+#include "fd-util.h"
+#include "missing_resource.h"
+#include "string-util.h"
+#include "time-util.h"
+#include "util.h"
+
+#define MAX_SIZE (2*1024*1024)
+
+static usec_t arg_loop_usec = 100 * USEC_PER_MSEC;
+
+typedef enum Type {
+ TYPE_LEGACY,
+ TYPE_DIRECT,
+} Type;
+
+static void server(sd_bus *b, size_t *result) {
+ int r;
+
+ for (;;) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+ r = sd_bus_process(b, &m);
+ assert_se(r >= 0);
+
+ if (r == 0)
+ assert_se(sd_bus_wait(b, USEC_INFINITY) >= 0);
+ if (!m)
+ continue;
+
+ if (sd_bus_message_is_method_call(m, "benchmark.server", "Ping"))
+ assert_se(sd_bus_reply_method_return(m, NULL) >= 0);
+ else if (sd_bus_message_is_method_call(m, "benchmark.server", "Work")) {
+ const void *p;
+ size_t sz;
+
+ /* Make sure the mmap is mapped */
+ assert_se(sd_bus_message_read_array(m, 'y', &p, &sz) > 0);
+
+ r = sd_bus_reply_method_return(m, NULL);
+ assert_se(r >= 0);
+ } else if (sd_bus_message_is_method_call(m, "benchmark.server", "Exit")) {
+ uint64_t res;
+ assert_se(sd_bus_message_read(m, "t", &res) > 0);
+
+ *result = res;
+ return;
+
+ } else if (!sd_bus_message_is_signal(m, NULL, NULL))
+ assert_not_reached();
+ }
+}
+
+static void transaction(sd_bus *b, size_t sz, const char *server_name) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ uint8_t *p;
+
+ assert_se(sd_bus_message_new_method_call(b, &m, server_name, "/", "benchmark.server", "Work") >= 0);
+ assert_se(sd_bus_message_append_array_space(m, 'y', sz, (void**) &p) >= 0);
+
+ memset(p, 0x80, sz);
+
+ assert_se(sd_bus_call(b, m, 0, NULL, &reply) >= 0);
+}
+
+static void client_bisect(const char *address, const char *server_name) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL;
+ size_t lsize, rsize, csize;
+ sd_bus *b;
+ int r;
+
+ r = sd_bus_new(&b);
+ assert_se(r >= 0);
+
+ r = sd_bus_set_address(b, address);
+ assert_se(r >= 0);
+
+ r = sd_bus_start(b);
+ assert_se(r >= 0);
+
+ r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL);
+ assert_se(r >= 0);
+
+ lsize = 1;
+ rsize = MAX_SIZE;
+
+ printf("SIZE\tCOPY\tMEMFD\n");
+
+ for (;;) {
+ usec_t t;
+ unsigned n_copying, n_memfd;
+
+ csize = (lsize + rsize) / 2;
+
+ if (csize <= lsize)
+ break;
+
+ if (csize <= 0)
+ break;
+
+ printf("%zu\t", csize);
+
+ b->use_memfd = 0;
+
+ t = now(CLOCK_MONOTONIC);
+ for (n_copying = 0;; n_copying++) {
+ transaction(b, csize, server_name);
+ if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec)
+ break;
+ }
+ printf("%u\t", (unsigned) ((n_copying * USEC_PER_SEC) / arg_loop_usec));
+
+ b->use_memfd = -1;
+
+ t = now(CLOCK_MONOTONIC);
+ for (n_memfd = 0;; n_memfd++) {
+ transaction(b, csize, server_name);
+ if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec)
+ break;
+ }
+ printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec));
+
+ if (n_copying == n_memfd)
+ break;
+
+ if (n_copying > n_memfd)
+ lsize = csize;
+ else
+ rsize = csize;
+ }
+
+ b->use_memfd = 1;
+ assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0);
+ assert_se(sd_bus_message_append(x, "t", csize) >= 0);
+ assert_se(sd_bus_send(b, x, NULL) >= 0);
+
+ sd_bus_unref(b);
+}
+
+static void client_chart(Type type, const char *address, const char *server_name, int fd) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL;
+ size_t csize;
+ sd_bus *b;
+ int r;
+
+ r = sd_bus_new(&b);
+ assert_se(r >= 0);
+
+ if (type == TYPE_DIRECT) {
+ r = sd_bus_set_fd(b, fd, fd);
+ assert_se(r >= 0);
+ } else {
+ r = sd_bus_set_address(b, address);
+ assert_se(r >= 0);
+
+ r = sd_bus_set_bus_client(b, true);
+ assert_se(r >= 0);
+ }
+
+ r = sd_bus_start(b);
+ assert_se(r >= 0);
+
+ r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL);
+ assert_se(r >= 0);
+
+ switch (type) {
+ case TYPE_LEGACY:
+ printf("SIZE\tLEGACY\n");
+ break;
+ case TYPE_DIRECT:
+ printf("SIZE\tDIRECT\n");
+ break;
+ }
+
+ for (csize = 1; csize <= MAX_SIZE; csize *= 2) {
+ usec_t t;
+ unsigned n_memfd;
+
+ printf("%zu\t", csize);
+
+ t = now(CLOCK_MONOTONIC);
+ for (n_memfd = 0;; n_memfd++) {
+ transaction(b, csize, server_name);
+ if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec)
+ break;
+ }
+
+ printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec));
+ }
+
+ b->use_memfd = 1;
+ assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0);
+ assert_se(sd_bus_message_append(x, "t", csize) >= 0);
+ assert_se(sd_bus_send(b, x, NULL) >= 0);
+
+ sd_bus_unref(b);
+}
+
+int main(int argc, char *argv[]) {
+ enum {
+ MODE_BISECT,
+ MODE_CHART,
+ } mode = MODE_BISECT;
+ Type type = TYPE_LEGACY;
+ int i, pair[2] = { -1, -1 };
+ _cleanup_free_ char *address = NULL, *server_name = NULL;
+ _cleanup_close_ int bus_ref = -1;
+ const char *unique;
+ cpu_set_t cpuset;
+ size_t result;
+ sd_bus *b;
+ pid_t pid;
+ int r;
+
+ for (i = 1; i < argc; i++) {
+ if (streq(argv[i], "chart")) {
+ mode = MODE_CHART;
+ continue;
+ } else if (streq(argv[i], "legacy")) {
+ type = TYPE_LEGACY;
+ continue;
+ } else if (streq(argv[i], "direct")) {
+ type = TYPE_DIRECT;
+ continue;
+ }
+
+ assert_se(parse_sec(argv[i], &arg_loop_usec) >= 0);
+ }
+
+ assert_se(arg_loop_usec > 0);
+
+ if (type == TYPE_LEGACY) {
+ const char *e;
+
+ e = secure_getenv("DBUS_SESSION_BUS_ADDRESS");
+ assert_se(e);
+
+ address = strdup(e);
+ assert_se(address);
+ }
+
+ r = sd_bus_new(&b);
+ assert_se(r >= 0);
+
+ if (type == TYPE_DIRECT) {
+ assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) >= 0);
+
+ r = sd_bus_set_fd(b, pair[0], pair[0]);
+ assert_se(r >= 0);
+
+ r = sd_bus_set_server(b, true, SD_ID128_NULL);
+ assert_se(r >= 0);
+ } else {
+ r = sd_bus_set_address(b, address);
+ assert_se(r >= 0);
+
+ r = sd_bus_set_bus_client(b, true);
+ assert_se(r >= 0);
+ }
+
+ r = sd_bus_start(b);
+ assert_se(r >= 0);
+
+ if (type != TYPE_DIRECT) {
+ r = sd_bus_get_unique_name(b, &unique);
+ assert_se(r >= 0);
+
+ server_name = strdup(unique);
+ assert_se(server_name);
+ }
+
+ sync();
+ setpriority(PRIO_PROCESS, 0, -19);
+
+ pid = fork();
+ assert_se(pid >= 0);
+
+ if (pid == 0) {
+ CPU_ZERO(&cpuset);
+ CPU_SET(0, &cpuset);
+ pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
+
+ safe_close(bus_ref);
+ sd_bus_unref(b);
+
+ switch (mode) {
+ case MODE_BISECT:
+ client_bisect(address, server_name);
+ break;
+
+ case MODE_CHART:
+ client_chart(type, address, server_name, pair[1]);
+ break;
+ }
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(1, &cpuset);
+ pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
+
+ server(b, &result);
+
+ if (mode == MODE_BISECT)
+ printf("Copying/memfd are equally fast at %zu bytes\n", result);
+
+ assert_se(waitpid(pid, NULL, 0) == pid);
+
+ safe_close(pair[1]);
+ sd_bus_unref(b);
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-chat.c b/src/libsystemd/sd-bus/test-bus-chat.c
new file mode 100644
index 0000000..15c5028
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-chat.c
@@ -0,0 +1,560 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-error.h"
+#include "bus-internal.h"
+#include "bus-match.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "format-util.h"
+#include "log.h"
+#include "macro.h"
+#include "string-util.h"
+#include "tests.h"
+#include "util.h"
+
+static int match_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ log_info("Match triggered! interface=%s member=%s", strna(sd_bus_message_get_interface(m)), strna(sd_bus_message_get_member(m)));
+ return 0;
+}
+
+static int object_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ int r;
+
+ if (sd_bus_message_is_method_error(m, NULL))
+ return 0;
+
+ if (sd_bus_message_is_method_call(m, "org.object.test", "Foobar")) {
+ log_info("Invoked Foobar() on %s", sd_bus_message_get_path(m));
+
+ r = sd_bus_reply_method_return(m, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to send reply: %m");
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static int server_init(sd_bus **_bus) {
+ sd_bus *bus = NULL;
+ sd_id128_t id;
+ int r;
+ const char *unique, *desc;
+
+ assert_se(_bus);
+
+ r = sd_bus_open_user_with_description(&bus, "my bus!");
+ if (r < 0) {
+ log_error_errno(r, "Failed to connect to user bus: %m");
+ goto fail;
+ }
+
+ r = sd_bus_get_bus_id(bus, &id);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get server ID: %m");
+ goto fail;
+ }
+
+ r = sd_bus_get_unique_name(bus, &unique);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get unique name: %m");
+ goto fail;
+ }
+
+ assert_se(sd_bus_get_description(bus, &desc) >= 0);
+ assert_se(streq(desc, "my bus!"));
+
+ log_info("Peer ID is " SD_ID128_FORMAT_STR ".", SD_ID128_FORMAT_VAL(id));
+ log_info("Unique ID: %s", unique);
+ log_info("Can send file handles: %i", sd_bus_can_send(bus, 'h'));
+
+ r = sd_bus_request_name(bus, "org.freedesktop.systemd.test", 0);
+ if (r < 0) {
+ log_error_errno(r, "Failed to acquire name: %m");
+ goto fail;
+ }
+
+ r = sd_bus_add_fallback(bus, NULL, "/foo/bar", object_callback, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to add object: %m");
+ goto fail;
+ }
+
+ r = sd_bus_match_signal(bus, NULL, NULL, NULL, "foo.bar", "Notify", match_callback, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to request match: %m");
+ goto fail;
+ }
+
+ r = sd_bus_add_match(bus, NULL, "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged'", match_callback, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to add match: %m");
+ goto fail;
+ }
+
+ bus_match_dump(stdout, &bus->match_callbacks, 0);
+
+ *_bus = bus;
+ return 0;
+
+fail:
+ sd_bus_unref(bus);
+ return r;
+}
+
+static int server(sd_bus *bus) {
+ int r;
+ bool client1_gone = false, client2_gone = false;
+
+ while (!client1_gone || !client2_gone) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ pid_t pid = 0;
+ const char *label = NULL;
+
+ r = sd_bus_process(bus, &m);
+ if (r < 0) {
+ log_error_errno(r, "Failed to process requests: %m");
+ goto fail;
+ }
+
+ if (r == 0) {
+ r = sd_bus_wait(bus, UINT64_MAX);
+ if (r < 0) {
+ log_error_errno(r, "Failed to wait: %m");
+ goto fail;
+ }
+
+ continue;
+ }
+
+ if (!m)
+ continue;
+
+ r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_AUGMENT | SD_BUS_CREDS_PID | SD_BUS_CREDS_SELINUX_CONTEXT, &creds);
+ if (r < 0)
+ log_debug_errno(r, "Failed to query sender credentials, ignoring: %m");
+ else {
+ r = sd_bus_creds_get_pid(creds, &pid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get sender pid: %m");
+
+ (void) sd_bus_creds_get_selinux_context(creds, &label);
+ }
+
+ log_info("Got message! member=%s pid="PID_FMT" label=%s",
+ strna(sd_bus_message_get_member(m)),
+ pid,
+ strna(label));
+ /* sd_bus_message_dump(m); */
+ /* sd_bus_message_rewind(m, true); */
+
+ if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "LowerCase")) {
+ const char *hello;
+ _cleanup_free_ char *lowercase = NULL;
+
+ r = sd_bus_message_read(m, "s", &hello);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get parameter: %m");
+ goto fail;
+ }
+
+ lowercase = strdup(hello);
+ if (!lowercase) {
+ r = log_oom();
+ goto fail;
+ }
+
+ ascii_strlower(lowercase);
+
+ r = sd_bus_reply_method_return(m, "s", lowercase);
+ if (r < 0) {
+ log_error_errno(r, "Failed to send reply: %m");
+ goto fail;
+ }
+ } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient1")) {
+
+ r = sd_bus_reply_method_return(m, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to send reply: %m");
+ goto fail;
+ }
+
+ client1_gone = true;
+ } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient2")) {
+
+ r = sd_bus_reply_method_return(m, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to send reply: %m");
+ goto fail;
+ }
+
+ client2_gone = true;
+ } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Slow")) {
+
+ sleep(1);
+
+ r = sd_bus_reply_method_return(m, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to send reply: %m");
+ goto fail;
+ }
+
+ } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "FileDescriptor")) {
+ int fd;
+ static const char x = 'X';
+
+ r = sd_bus_message_read(m, "h", &fd);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get parameter: %m");
+ goto fail;
+ }
+
+ log_info("Received fd=%d", fd);
+
+ if (write(fd, &x, 1) < 0) {
+ log_error_errno(errno, "Failed to write to fd: %m");
+ safe_close(fd);
+ goto fail;
+ }
+
+ r = sd_bus_reply_method_return(m, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to send reply: %m");
+ goto fail;
+ }
+
+ } else if (sd_bus_message_is_method_call(m, NULL, NULL)) {
+
+ r = sd_bus_reply_method_error(
+ m,
+ &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNKNOWN_METHOD, "Unknown method."));
+ if (r < 0) {
+ log_error_errno(r, "Failed to send reply: %m");
+ goto fail;
+ }
+ }
+ }
+
+ r = 0;
+
+fail:
+ if (bus) {
+ sd_bus_flush(bus);
+ sd_bus_unref(bus);
+ }
+
+ return r;
+}
+
+static void* client1(void *p) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ const char *hello;
+ int r;
+ _cleanup_close_pair_ int pp[2] = { -1, -1 };
+ char x;
+
+ r = sd_bus_open_user(&bus);
+ if (r < 0) {
+ log_error_errno(r, "Failed to connect to user bus: %m");
+ goto finish;
+ }
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd.test",
+ "/",
+ "org.freedesktop.systemd.test",
+ "LowerCase",
+ &error,
+ &reply,
+ "s",
+ "HELLO");
+ if (r < 0) {
+ log_error_errno(r, "Failed to issue method call: %m");
+ goto finish;
+ }
+
+ r = sd_bus_message_read(reply, "s", &hello);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get string: %m");
+ goto finish;
+ }
+
+ assert_se(streq(hello, "hello"));
+
+ if (pipe2(pp, O_CLOEXEC|O_NONBLOCK) < 0) {
+ r = log_error_errno(errno, "Failed to allocate pipe: %m");
+ goto finish;
+ }
+
+ log_info("Sending fd=%d", pp[1]);
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd.test",
+ "/",
+ "org.freedesktop.systemd.test",
+ "FileDescriptor",
+ &error,
+ NULL,
+ "h",
+ pp[1]);
+ if (r < 0) {
+ log_error_errno(r, "Failed to issue method call: %m");
+ goto finish;
+ }
+
+ errno = 0;
+ if (read(pp[0], &x, 1) <= 0) {
+ log_error("Failed to read from pipe: %s", STRERROR_OR_EOF(errno));
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ if (bus) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *q = NULL;
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &q,
+ "org.freedesktop.systemd.test",
+ "/",
+ "org.freedesktop.systemd.test",
+ "ExitClient1");
+ if (r < 0)
+ log_error_errno(r, "Failed to allocate method call: %m");
+ else
+ sd_bus_send(bus, q, NULL);
+
+ }
+
+ return INT_TO_PTR(r);
+}
+
+static int quit_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ bool *x = userdata;
+
+ log_error_errno(sd_bus_message_get_errno(m), "Quit callback: %m");
+
+ *x = 1;
+ return 1;
+}
+
+static void* client2(void *p) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ bool quit = false;
+ const char *mid;
+ int r;
+
+ r = sd_bus_open_user(&bus);
+ if (r < 0) {
+ log_error_errno(r, "Failed to connect to user bus: %m");
+ goto finish;
+ }
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &m,
+ "org.freedesktop.systemd.test",
+ "/foo/bar/waldo/piep",
+ "org.object.test",
+ "Foobar");
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate method call: %m");
+ goto finish;
+ }
+
+ r = sd_bus_send(bus, m, NULL);
+ if (r < 0) {
+ log_error("Failed to issue method call: %s", bus_error_message(&error, r));
+ goto finish;
+ }
+
+ m = sd_bus_message_unref(m);
+
+ r = sd_bus_message_new_signal(
+ bus,
+ &m,
+ "/foobar",
+ "foo.bar",
+ "Notify");
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate signal: %m");
+ goto finish;
+ }
+
+ r = sd_bus_send(bus, m, NULL);
+ if (r < 0) {
+ log_error("Failed to issue signal: %s", bus_error_message(&error, r));
+ goto finish;
+ }
+
+ m = sd_bus_message_unref(m);
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &m,
+ "org.freedesktop.systemd.test",
+ "/",
+ "org.freedesktop.DBus.Peer",
+ "GetMachineId");
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate method call: %m");
+ goto finish;
+ }
+
+ r = sd_bus_call(bus, m, 0, &error, &reply);
+ if (r < 0) {
+ log_error("Failed to issue method call: %s", bus_error_message(&error, r));
+ goto finish;
+ }
+
+ r = sd_bus_message_read(reply, "s", &mid);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse machine ID: %m");
+ goto finish;
+ }
+
+ log_info("Machine ID is %s.", mid);
+
+ m = sd_bus_message_unref(m);
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &m,
+ "org.freedesktop.systemd.test",
+ "/",
+ "org.freedesktop.systemd.test",
+ "Slow");
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate method call: %m");
+ goto finish;
+ }
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call(bus, m, 200 * USEC_PER_MSEC, &error, &reply);
+ if (r < 0)
+ log_debug("Failed to issue method call: %s", bus_error_message(&error, r));
+ else {
+ log_error("Slow call unexpectedly succeed.");
+ r = -ENOANO;
+ goto finish;
+ }
+
+ m = sd_bus_message_unref(m);
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &m,
+ "org.freedesktop.systemd.test",
+ "/",
+ "org.freedesktop.systemd.test",
+ "Slow");
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate method call: %m");
+ goto finish;
+ }
+
+ r = sd_bus_call_async(bus, NULL, m, quit_callback, &quit, 200 * USEC_PER_MSEC);
+ if (r < 0) {
+ log_info("Failed to issue method call: %s", bus_error_message(&error, r));
+ goto finish;
+ }
+
+ while (!quit) {
+ r = sd_bus_process(bus, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to process requests: %m");
+ goto finish;
+ }
+ if (r == 0) {
+ r = sd_bus_wait(bus, UINT64_MAX);
+ if (r < 0) {
+ log_error_errno(r, "Failed to wait: %m");
+ goto finish;
+ }
+ }
+ }
+
+ r = 0;
+
+finish:
+ if (bus) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *q = NULL;
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &q,
+ "org.freedesktop.systemd.test",
+ "/",
+ "org.freedesktop.systemd.test",
+ "ExitClient2");
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate method call: %m");
+ goto finish;
+ }
+
+ (void) sd_bus_send(bus, q, NULL);
+ }
+
+ return INT_TO_PTR(r);
+}
+
+int main(int argc, char *argv[]) {
+ pthread_t c1, c2;
+ sd_bus *bus;
+ void *p;
+ int q, r;
+
+ test_setup_logging(LOG_INFO);
+
+ r = server_init(&bus);
+ if (r < 0)
+ return log_tests_skipped("Failed to connect to bus");
+
+ log_info("Initialized...");
+
+ r = pthread_create(&c1, NULL, client1, bus);
+ if (r != 0)
+ return EXIT_FAILURE;
+
+ r = pthread_create(&c2, NULL, client2, bus);
+ if (r != 0)
+ return EXIT_FAILURE;
+
+ r = server(bus);
+
+ q = pthread_join(c1, &p);
+ if (q != 0)
+ return EXIT_FAILURE;
+ if (PTR_TO_INT(p) < 0)
+ return EXIT_FAILURE;
+
+ q = pthread_join(c2, &p);
+ if (q != 0)
+ return EXIT_FAILURE;
+ if (PTR_TO_INT(p) < 0)
+ return EXIT_FAILURE;
+
+ if (r < 0)
+ return EXIT_FAILURE;
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-cleanup.c b/src/libsystemd/sd-bus/test-bus-cleanup.c
new file mode 100644
index 0000000..f563db7
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-cleanup.c
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stdio.h>
+
+#include "sd-bus.h"
+
+#include "bus-internal.h"
+#include "bus-message.h"
+#include "tests.h"
+
+static bool use_system_bus = false;
+
+static void test_bus_new(void) {
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+
+ assert_se(sd_bus_new(&bus) == 0);
+ assert_se(bus->n_ref == 1);
+}
+
+static int test_bus_open(void) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ r = sd_bus_open_user(&bus);
+ if (IN_SET(r, -ECONNREFUSED, -ENOENT, -ENOMEDIUM)) {
+ r = sd_bus_open_system(&bus);
+ if (IN_SET(r, -ECONNREFUSED, -ENOENT))
+ return r;
+ use_system_bus = true;
+ }
+
+ assert_se(r >= 0);
+ assert_se(bus->n_ref >= 1); /* we send a hello message when opening, so the count is above 1 */
+
+ return 0;
+}
+
+static void test_bus_new_method_call(void) {
+ sd_bus *bus = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+ assert_se(use_system_bus ? sd_bus_open_system(&bus) >= 0 : sd_bus_open_user(&bus) >= 0);
+
+ assert_se(sd_bus_message_new_method_call(bus, &m, "a.service.name", "/an/object/path", "an.interface.name", "AMethodName") >= 0);
+
+ assert_se(m->n_ref == 1); /* We hold the only reference to the message */
+ assert_se(bus->n_ref >= 2);
+ sd_bus_flush_close_unref(bus);
+ assert_se(m->n_ref == 1);
+}
+
+static void test_bus_new_signal(void) {
+ sd_bus *bus = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+ assert_se(use_system_bus ? sd_bus_open_system(&bus) >= 0 : sd_bus_open_user(&bus) >= 0);
+
+ assert_se(sd_bus_message_new_signal(bus, &m, "/an/object/path", "an.interface.name", "Name") >= 0);
+
+ assert_se(m->n_ref == 1); /* We hold the only reference to the message */
+ assert_se(bus->n_ref >= 2);
+ sd_bus_flush_close_unref(bus);
+ assert_se(m->n_ref == 1);
+}
+
+int main(int argc, char **argv) {
+ test_setup_logging(LOG_INFO);
+
+ test_bus_new();
+
+ if (test_bus_open() < 0)
+ return log_tests_skipped("Failed to connect to bus");
+
+ test_bus_new_method_call();
+ test_bus_new_signal();
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-creds.c b/src/libsystemd/sd-bus/test-bus-creds.c
new file mode 100644
index 0000000..13801be
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-creds.c
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-bus.h"
+
+#include "bus-dump.h"
+#include "cgroup-util.h"
+#include "tests.h"
+
+int main(int argc, char *argv[]) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ int r;
+
+ test_setup_logging(LOG_DEBUG);
+
+ if (cg_unified() == -ENOMEDIUM)
+ return log_tests_skipped("/sys/fs/cgroup/ not available");
+
+ r = sd_bus_creds_new_from_pid(&creds, 0, _SD_BUS_CREDS_ALL);
+ log_full_errno(r < 0 ? LOG_ERR : LOG_DEBUG, r, "sd_bus_creds_new_from_pid: %m");
+ assert_se(r >= 0);
+
+ bus_creds_dump(creds, NULL, true);
+
+ creds = sd_bus_creds_unref(creds);
+
+ r = sd_bus_creds_new_from_pid(&creds, 1, _SD_BUS_CREDS_ALL);
+ if (r != -EACCES) {
+ assert_se(r >= 0);
+ putchar('\n');
+ bus_creds_dump(creds, NULL, true);
+ }
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c
new file mode 100644
index 0000000..a55f3f9
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-error.c
@@ -0,0 +1,294 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-common-errors.h"
+#include "bus-error.h"
+#include "errno-list.h"
+#include "errno-util.h"
+#include "string-util.h"
+#include "tests.h"
+
+TEST(error) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL, second = SD_BUS_ERROR_NULL;
+ const sd_bus_error const_error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_EXISTS, "const error");
+ const sd_bus_error temporarily_const_error = {
+ .name = SD_BUS_ERROR_ACCESS_DENIED,
+ .message = "oh! no",
+ ._need_free = -1,
+ };
+
+ assert_se(!sd_bus_error_is_set(&error));
+ assert_se(sd_bus_error_set(&error, SD_BUS_ERROR_NOT_SUPPORTED, "xxx") == -EOPNOTSUPP);
+ assert_se(streq(error.name, SD_BUS_ERROR_NOT_SUPPORTED));
+ assert_se(streq(error.message, "xxx"));
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_NOT_SUPPORTED));
+ assert_se(sd_bus_error_has_names_sentinel(&error, SD_BUS_ERROR_NOT_SUPPORTED, NULL));
+ assert_se(sd_bus_error_has_names(&error, SD_BUS_ERROR_NOT_SUPPORTED));
+ assert_se(sd_bus_error_has_names(&error, SD_BUS_ERROR_NOT_SUPPORTED, SD_BUS_ERROR_FILE_NOT_FOUND));
+ assert_se(sd_bus_error_has_names(&error, SD_BUS_ERROR_FILE_NOT_FOUND, SD_BUS_ERROR_NOT_SUPPORTED, NULL));
+ assert_se(!sd_bus_error_has_names(&error, SD_BUS_ERROR_FILE_NOT_FOUND));
+ assert_se(sd_bus_error_get_errno(&error) == EOPNOTSUPP);
+ assert_se(sd_bus_error_is_set(&error));
+ sd_bus_error_free(&error);
+
+ /* Check with no error */
+ assert_se(!sd_bus_error_is_set(&error));
+ assert_se(sd_bus_error_setf(&error, NULL, "yyy %i", -1) == 0);
+ assert_se(error.name == NULL);
+ assert_se(error.message == NULL);
+ assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND));
+ assert_se(!sd_bus_error_has_names(&error, SD_BUS_ERROR_FILE_NOT_FOUND));
+ assert_se(sd_bus_error_get_errno(&error) == 0);
+ assert_se(!sd_bus_error_is_set(&error));
+
+ assert_se(sd_bus_error_setf(&error, SD_BUS_ERROR_FILE_NOT_FOUND, "yyy %i", -1) == -ENOENT);
+ assert_se(streq(error.name, SD_BUS_ERROR_FILE_NOT_FOUND));
+ assert_se(streq(error.message, "yyy -1"));
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND));
+ assert_se(sd_bus_error_has_names(&error, SD_BUS_ERROR_FILE_NOT_FOUND));
+ assert_se(sd_bus_error_get_errno(&error) == ENOENT);
+ assert_se(sd_bus_error_is_set(&error));
+
+ assert_se(!sd_bus_error_is_set(&second));
+ assert_se(second._need_free == 0);
+ assert_se(error._need_free > 0);
+ assert_se(sd_bus_error_copy(&second, &error) == -ENOENT);
+ assert_se(second._need_free > 0);
+ assert_se(streq(error.name, second.name));
+ assert_se(streq(error.message, second.message));
+ assert_se(sd_bus_error_get_errno(&second) == ENOENT);
+ assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_FILE_NOT_FOUND));
+ assert_se(sd_bus_error_has_names(&second, SD_BUS_ERROR_FILE_NOT_FOUND));
+ assert_se(sd_bus_error_is_set(&second));
+
+ sd_bus_error_free(&error);
+ sd_bus_error_free(&second);
+
+ assert_se(!sd_bus_error_is_set(&second));
+ assert_se(const_error._need_free == 0);
+ assert_se(sd_bus_error_copy(&second, &const_error) == -EEXIST);
+ assert_se(second._need_free == 0);
+ assert_se(streq(const_error.name, second.name));
+ assert_se(streq(const_error.message, second.message));
+ assert_se(sd_bus_error_get_errno(&second) == EEXIST);
+ assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_FILE_EXISTS));
+ assert_se(sd_bus_error_is_set(&second));
+ sd_bus_error_free(&second);
+
+ assert_se(!sd_bus_error_is_set(&second));
+ assert_se(temporarily_const_error._need_free < 0);
+ assert_se(sd_bus_error_copy(&second, &temporarily_const_error) == -EACCES);
+ assert_se(second._need_free > 0);
+ assert_se(streq(temporarily_const_error.name, second.name));
+ assert_se(streq(temporarily_const_error.message, second.message));
+ assert_se(sd_bus_error_get_errno(&second) == EACCES);
+ assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_ACCESS_DENIED));
+ assert_se(sd_bus_error_is_set(&second));
+
+ assert_se(!sd_bus_error_is_set(&error));
+ assert_se(sd_bus_error_set_const(&error, "System.Error.EUCLEAN", "Hallo") == -EUCLEAN);
+ assert_se(streq(error.name, "System.Error.EUCLEAN"));
+ assert_se(streq(error.message, "Hallo"));
+ assert_se(sd_bus_error_has_name(&error, "System.Error.EUCLEAN"));
+ assert_se(sd_bus_error_get_errno(&error) == EUCLEAN);
+ assert_se(sd_bus_error_is_set(&error));
+ sd_bus_error_free(&error);
+
+ assert_se(!sd_bus_error_is_set(&error));
+ assert_se(sd_bus_error_set_errno(&error, EBUSY) == -EBUSY);
+ assert_se(streq(error.name, "System.Error.EBUSY"));
+ assert_se(streq(error.message, STRERROR(EBUSY)));
+ assert_se(sd_bus_error_has_name(&error, "System.Error.EBUSY"));
+ assert_se(sd_bus_error_get_errno(&error) == EBUSY);
+ assert_se(sd_bus_error_is_set(&error));
+ sd_bus_error_free(&error);
+
+ assert_se(!sd_bus_error_is_set(&error));
+ assert_se(sd_bus_error_set_errnof(&error, EIO, "Waldi %c", 'X') == -EIO);
+ assert_se(streq(error.name, SD_BUS_ERROR_IO_ERROR));
+ assert_se(streq(error.message, "Waldi X"));
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR));
+ assert_se(sd_bus_error_get_errno(&error) == EIO);
+ assert_se(sd_bus_error_is_set(&error));
+ sd_bus_error_free(&error);
+
+ /* Check with no error */
+ assert_se(!sd_bus_error_is_set(&error));
+ assert_se(sd_bus_error_set_errnof(&error, 0, "Waldi %c", 'X') == 0);
+ assert_se(error.name == NULL);
+ assert_se(error.message == NULL);
+ assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR));
+ assert_se(sd_bus_error_get_errno(&error) == 0);
+ assert_se(!sd_bus_error_is_set(&error));
+}
+
+extern const sd_bus_error_map __start_SYSTEMD_BUS_ERROR_MAP[];
+extern const sd_bus_error_map __stop_SYSTEMD_BUS_ERROR_MAP[];
+
+static int dump_mapping_table(void) {
+ const sd_bus_error_map *m;
+
+ printf("----- errno mappings ------\n");
+ m = ALIGN_PTR(__start_SYSTEMD_BUS_ERROR_MAP);
+ while (m < __stop_SYSTEMD_BUS_ERROR_MAP) {
+
+ if (m->code == BUS_ERROR_MAP_END_MARKER) {
+ m = ALIGN_PTR(m + 1);
+ continue;
+ }
+
+ printf("%s -> %i/%s\n", strna(m->name), m->code, strna(errno_to_name(m->code)));
+ m++;
+ }
+ printf("---------------------------\n");
+
+ return EXIT_SUCCESS;
+}
+
+TEST(errno_mapping_standard) {
+ assert_se(sd_bus_error_set(NULL, "System.Error.EUCLEAN", NULL) == -EUCLEAN);
+ assert_se(sd_bus_error_set(NULL, "System.Error.EBUSY", NULL) == -EBUSY);
+ assert_se(sd_bus_error_set(NULL, "System.Error.EINVAL", NULL) == -EINVAL);
+ assert_se(sd_bus_error_set(NULL, "System.Error.WHATSIT", NULL) == -EIO);
+}
+
+BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors[] = {
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error", 5),
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-2", 52),
+ SD_BUS_ERROR_MAP_END
+};
+
+BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors2[] = {
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-3", 33),
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-4", 44),
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-33", 333),
+ SD_BUS_ERROR_MAP_END
+};
+
+static const sd_bus_error_map test_errors3[] = {
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-88", 888),
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-99", 999),
+ SD_BUS_ERROR_MAP_END
+};
+
+static const sd_bus_error_map test_errors4[] = {
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-77", 777),
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-78", 778),
+ SD_BUS_ERROR_MAP_END
+};
+
+static const sd_bus_error_map test_errors_bad1[] = {
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", 0),
+ SD_BUS_ERROR_MAP_END
+};
+
+static const sd_bus_error_map test_errors_bad2[] = {
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", -1),
+ SD_BUS_ERROR_MAP_END
+};
+
+TEST(errno_mapping_custom) {
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error", NULL) == -5);
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52);
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-x", NULL) == -EIO);
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-33", NULL) == -333);
+
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-88", NULL) == -EIO);
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-99", NULL) == -EIO);
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-77", NULL) == -EIO);
+
+ assert_se(sd_bus_error_add_map(test_errors3) > 0);
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-88", NULL) == -888);
+ assert_se(sd_bus_error_add_map(test_errors4) > 0);
+ assert_se(sd_bus_error_add_map(test_errors4) == 0);
+ assert_se(sd_bus_error_add_map(test_errors3) == 0);
+
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-99", NULL) == -999);
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-77", NULL) == -777);
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-78", NULL) == -778);
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52);
+ assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-y", NULL) == -EIO);
+
+ assert_se(sd_bus_error_set(NULL, BUS_ERROR_NO_SUCH_UNIT, NULL) == -ENOENT);
+
+ assert_se(sd_bus_error_add_map(test_errors_bad1) == -EINVAL);
+ assert_se(sd_bus_error_add_map(test_errors_bad2) == -EINVAL);
+}
+
+TEST(sd_bus_error_set_errnof) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *str = NULL;
+
+ assert_se(sd_bus_error_set_errnof(NULL, 0, NULL) == 0);
+ assert_se(sd_bus_error_set_errnof(NULL, ENOANO, NULL) == -ENOANO);
+
+ assert_se(sd_bus_error_set_errnof(&error, 0, NULL) == 0);
+ assert_se(!bus_error_is_dirty(&error));
+
+ assert_se(sd_bus_error_set_errnof(&error, EACCES, NULL) == -EACCES);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_ACCESS_DENIED));
+ errno = EACCES;
+ assert_se(asprintf(&str, "%m") >= 0);
+ assert_se(streq(error.message, str));
+ assert_se(error._need_free == 0);
+
+ str = mfree(str);
+ sd_bus_error_free(&error);
+
+ assert_se(sd_bus_error_set_errnof(&error, ENOANO, NULL) == -ENOANO);
+ assert_se(sd_bus_error_has_name(&error, "System.Error.ENOANO"));
+ errno = ENOANO;
+ assert_se(asprintf(&str, "%m") >= 0);
+ assert_se(streq(error.message, str));
+ assert_se(error._need_free == 1);
+
+ str = mfree(str);
+ sd_bus_error_free(&error);
+
+ assert_se(sd_bus_error_set_errnof(&error, 100000, NULL) == -100000);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_FAILED));
+ errno = 100000;
+ assert_se(asprintf(&str, "%m") >= 0);
+ assert_se(streq(error.message, str));
+ assert_se(error._need_free == 1);
+
+ str = mfree(str);
+ sd_bus_error_free(&error);
+
+ assert_se(sd_bus_error_set_errnof(NULL, 0, "hoge %s: %m", "foo") == 0);
+ assert_se(sd_bus_error_set_errnof(NULL, ENOANO, "hoge %s: %m", "foo") == -ENOANO);
+
+ assert_se(sd_bus_error_set_errnof(&error, 0, "hoge %s: %m", "foo") == 0);
+ assert_se(!bus_error_is_dirty(&error));
+
+ assert_se(sd_bus_error_set_errnof(&error, EACCES, "hoge %s: %m", "foo") == -EACCES);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_ACCESS_DENIED));
+ errno = EACCES;
+ assert_se(asprintf(&str, "hoge %s: %m", "foo") >= 0);
+ assert_se(streq(error.message, str));
+ assert_se(error._need_free == 1);
+
+ str = mfree(str);
+ sd_bus_error_free(&error);
+
+ assert_se(sd_bus_error_set_errnof(&error, ENOANO, "hoge %s: %m", "foo") == -ENOANO);
+ assert_se(sd_bus_error_has_name(&error, "System.Error.ENOANO"));
+ errno = ENOANO;
+ assert_se(asprintf(&str, "hoge %s: %m", "foo") >= 0);
+ assert_se(streq(error.message, str));
+ assert_se(error._need_free == 1);
+
+ str = mfree(str);
+ sd_bus_error_free(&error);
+
+ assert_se(sd_bus_error_set_errnof(&error, 100000, "hoge %s: %m", "foo") == -100000);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_FAILED));
+ errno = 100000;
+ assert_se(asprintf(&str, "hoge %s: %m", "foo") >= 0);
+ assert_se(streq(error.message, str));
+ assert_se(error._need_free == 1);
+}
+
+DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, dump_mapping_table);
diff --git a/src/libsystemd/sd-bus/test-bus-introspect.c b/src/libsystemd/sd-bus/test-bus-introspect.c
new file mode 100644
index 0000000..3c026ae
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-introspect.c
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bus-introspect.h"
+#include "log.h"
+#include "tests.h"
+
+#include "test-vtable-data.h"
+
+static void test_manual_introspection_one(const sd_bus_vtable vtable[]) {
+ struct introspect intro = {};
+ _cleanup_free_ char *s = NULL;
+
+ log_info("/* %s */", __func__);
+
+ assert_se(introspect_begin(&intro, false) >= 0);
+
+ assert_se(introspect_write_interface(&intro, "org.foo", vtable) >= 0);
+ /* write again to check if output looks OK for a different interface */
+ assert_se(introspect_write_interface(&intro, "org.foo.bar", vtable) >= 0);
+ assert_se(introspect_finish(&intro, &s) == 0);
+
+ fputs(s, stdout);
+ fputs("\n", stdout);
+}
+
+TEST(manual_introspection) {
+ test_manual_introspection_one(test_vtable_1);
+ test_manual_introspection_one(test_vtable_2);
+ test_manual_introspection_one(test_vtable_deprecated);
+ test_manual_introspection_one((const sd_bus_vtable *) vtable_format_221);
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c
new file mode 100644
index 0000000..e1afbc2
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-marshal.c
@@ -0,0 +1,424 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <math.h>
+#include <stdlib.h>
+
+#if HAVE_GLIB
+#include <gio/gio.h>
+#endif
+
+#if HAVE_DBUS
+#include <dbus/dbus.h>
+#endif
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-dump.h"
+#include "bus-label.h"
+#include "bus-message.h"
+#include "bus-util.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "log.h"
+#include "tests.h"
+#include "util.h"
+
+static void test_bus_path_encode_unique(void) {
+ _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL;
+
+ assert_se(bus_path_encode_unique(NULL, "/foo/bar", "some.sender", "a.suffix", &a) >= 0 && streq_ptr(a, "/foo/bar/some_2esender/a_2esuffix"));
+ assert_se(bus_path_decode_unique(a, "/foo/bar", &b, &c) > 0 && streq_ptr(b, "some.sender") && streq_ptr(c, "a.suffix"));
+ assert_se(bus_path_decode_unique(a, "/bar/foo", &d, &d) == 0 && !d);
+ assert_se(bus_path_decode_unique("/foo/bar/onlyOneSuffix", "/foo/bar", &d, &d) == 0 && !d);
+ assert_se(bus_path_decode_unique("/foo/bar/_/_", "/foo/bar", &d, &e) > 0 && streq_ptr(d, "") && streq_ptr(e, ""));
+}
+
+static void test_bus_path_encode(void) {
+ _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL, *g = NULL;
+
+ assert_se(sd_bus_path_encode("/foo/bar", "waldo", &a) >= 0 && streq(a, "/foo/bar/waldo"));
+ assert_se(sd_bus_path_decode(a, "/waldo", &b) == 0 && b == NULL);
+ assert_se(sd_bus_path_decode(a, "/foo/bar", &b) > 0 && streq(b, "waldo"));
+
+ assert_se(sd_bus_path_encode("xxxx", "waldo", &c) < 0);
+ assert_se(sd_bus_path_encode("/foo/", "waldo", &c) < 0);
+
+ assert_se(sd_bus_path_encode("/foo/bar", "", &c) >= 0 && streq(c, "/foo/bar/_"));
+ assert_se(sd_bus_path_decode(c, "/foo/bar", &d) > 0 && streq(d, ""));
+
+ assert_se(sd_bus_path_encode("/foo/bar", "foo.bar", &e) >= 0 && streq(e, "/foo/bar/foo_2ebar"));
+ assert_se(sd_bus_path_decode(e, "/foo/bar", &f) > 0 && streq(f, "foo.bar"));
+
+ assert_se(sd_bus_path_decode("/waldo", "/waldo", &g) > 0 && streq(g, ""));
+}
+
+static void test_bus_path_encode_many(void) {
+ _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL;
+
+ assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%", NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/prefix/bar", "/prefix/%bar", NULL) == 1);
+ assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%/suffix", NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/prefix/foobar/suffix", "/prefix/%/suffix", &a) == 1 && streq_ptr(a, "foobar"));
+ assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", &b, &c) == 1 && streq_ptr(b, "foo") && streq_ptr(c, "bar"));
+ assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", NULL, &d) == 1 && streq_ptr(d, "bar"));
+
+ assert_se(sd_bus_path_decode_many("/foo/bar", "/foo/bar/%", NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar%", NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/bar", NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%bar", NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar/suffix") == 1);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%%/suffix", NULL, NULL) == 0); /* multiple '%' are treated verbatim */
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffi", NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffix", &e) == 1 && streq_ptr(e, "bar"));
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/%", NULL, NULL) == 1);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/%", NULL, NULL, NULL) == 1);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%/%/%", NULL, NULL, NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%", NULL, NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/", NULL, NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/", NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%", NULL) == 0);
+ assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%", NULL) == 0);
+
+ assert_se(sd_bus_path_encode_many(&f, "/prefix/one_%_two/mid/three_%_four/suffix", "foo", "bar") >= 0 && streq_ptr(f, "/prefix/one_foo_two/mid/three_bar_four/suffix"));
+}
+
+static void test_bus_label_escape_one(const char *a, const char *b) {
+ _cleanup_free_ char *t = NULL, *x = NULL, *y = NULL;
+
+ assert_se(t = bus_label_escape(a));
+ assert_se(streq(t, b));
+
+ assert_se(x = bus_label_unescape(t));
+ assert_se(streq(a, x));
+
+ assert_se(y = bus_label_unescape(b));
+ assert_se(streq(a, y));
+}
+
+static void test_bus_label_escape(void) {
+ test_bus_label_escape_one("foo123bar", "foo123bar");
+ test_bus_label_escape_one("foo.bar", "foo_2ebar");
+ test_bus_label_escape_one("foo_2ebar", "foo_5f2ebar");
+ test_bus_label_escape_one("", "_");
+ test_bus_label_escape_one("_", "_5f");
+ test_bus_label_escape_one("1", "_31");
+ test_bus_label_escape_one(":1", "_3a1");
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *copy = NULL;
+ int r, boolean;
+ const char *x, *x2, *y, *z, *a, *b, *c, *d, *a_signature;
+ uint8_t u, v;
+ void *buffer = NULL;
+ size_t sz;
+ _cleanup_free_ char *h = NULL;
+ const int32_t integer_array[] = { -1, -2, 0, 1, 2 }, *return_array;
+ char *s;
+ _cleanup_free_ char *first = NULL, *second = NULL, *third = NULL;
+ _cleanup_fclose_ FILE *ms = NULL;
+ size_t first_size = 0, second_size = 0, third_size = 0;
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ double dbl;
+ uint64_t u64;
+
+ test_setup_logging(LOG_INFO);
+
+ r = sd_bus_default_user(&bus);
+ if (r < 0)
+ r = sd_bus_default_system(&bus);
+ if (r < 0)
+ return log_tests_skipped("Failed to connect to bus");
+
+ r = sd_bus_message_new_method_call(bus, &m, "foobar.waldo", "/", "foobar.waldo", "Piep");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append(m, "");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append(m, "s", "a string");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append(m, "s", NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append(m, "asg", 2, "string #1", "string #2", "sba(tt)ss");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append(m, "sass", "foobar", 5, "foo", "bar", "waldo", "piep", "pap", "after");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append(m, "a{yv}", 2, 3, "s", "foo", 5, "s", "waldo");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append(m, "y(ty)y(yt)y", 8, 777ULL, 7, 9, 77, 7777ULL, 10);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append(m, "()");
+ assert_se(r == -EINVAL);
+
+ r = sd_bus_message_append(m, "ba(ss)", 255, 3, "aaa", "1", "bbb", "2", "ccc", "3");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_open_container(m, 'a', "s");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append_basic(m, 's', "foobar");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append_basic(m, 's', "waldo");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_close_container(m);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append_string_space(m, 5, &s);
+ assert_se(r >= 0);
+ strcpy(s, "hallo");
+
+ r = sd_bus_message_append_array(m, 'i', integer_array, sizeof(integer_array));
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append_array(m, 'u', NULL, 0);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_append(m, "a(stdo)", 1, "foo", 815ULL, 47.0, "/");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_seal(m, 4711, 0);
+ assert_se(r >= 0);
+
+ sd_bus_message_dump(m, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ ms = open_memstream_unlocked(&first, &first_size);
+ sd_bus_message_dump(m, ms, 0);
+ fflush(ms);
+ assert_se(!ferror(ms));
+
+ r = bus_message_get_blob(m, &buffer, &sz);
+ assert_se(r >= 0);
+
+ h = cescape_length(buffer, sz);
+ assert_se(h);
+ log_info("message size = %zu, contents =\n%s", sz, h);
+
+#if HAVE_GLIB
+ /* Work-around for asan bug. See c8d980a3e962aba2ea3a4cedf75fa94890a6d746. */
+#if !HAS_FEATURE_ADDRESS_SANITIZER
+ {
+ GDBusMessage *g;
+ char *p;
+
+#if !defined(GLIB_VERSION_2_36)
+ g_type_init();
+#endif
+
+ g = g_dbus_message_new_from_blob(buffer, sz, 0, NULL);
+ p = g_dbus_message_print(g, 0);
+ log_info("%s", p);
+ g_free(p);
+ g_object_unref(g);
+ }
+#endif
+#endif
+
+#if HAVE_DBUS
+ {
+ DBusMessage *w;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ w = dbus_message_demarshal(buffer, sz, &error);
+ if (!w)
+ log_error("%s", error.message);
+ else
+ dbus_message_unref(w);
+
+ dbus_error_free(&error);
+ }
+#endif
+
+ m = sd_bus_message_unref(m);
+
+ r = bus_message_from_malloc(bus, buffer, sz, NULL, 0, NULL, &m);
+ assert_se(r >= 0);
+
+ sd_bus_message_dump(m, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ fclose(ms);
+ ms = open_memstream_unlocked(&second, &second_size);
+ sd_bus_message_dump(m, ms, 0);
+ fflush(ms);
+ assert_se(!ferror(ms));
+ assert_se(first_size == second_size);
+ assert_se(memcmp(first, second, first_size) == 0);
+
+ assert_se(sd_bus_message_rewind(m, true) >= 0);
+
+ r = sd_bus_message_read(m, "ssasg", &x, &x2, 2, &y, &z, &a_signature);
+ assert_se(r > 0);
+ assert_se(streq(x, "a string"));
+ assert_se(streq(x2, ""));
+ assert_se(streq(y, "string #1"));
+ assert_se(streq(z, "string #2"));
+ assert_se(streq(a_signature, "sba(tt)ss"));
+
+ r = sd_bus_message_read(m, "sass", &x, 5, &y, &z, &a, &b, &c, &d);
+ assert_se(r > 0);
+ assert_se(streq(x, "foobar"));
+ assert_se(streq(y, "foo"));
+ assert_se(streq(z, "bar"));
+ assert_se(streq(a, "waldo"));
+ assert_se(streq(b, "piep"));
+ assert_se(streq(c, "pap"));
+ assert_se(streq(d, "after"));
+
+ r = sd_bus_message_read(m, "a{yv}", 2, &u, "s", &x, &v, "s", &y);
+ assert_se(r > 0);
+ assert_se(u == 3);
+ assert_se(streq(x, "foo"));
+ assert_se(v == 5);
+ assert_se(streq(y, "waldo"));
+
+ r = sd_bus_message_read(m, "y(ty)", &v, &u64, &u);
+ assert_se(r > 0);
+ assert_se(v == 8);
+ assert_se(u64 == 777);
+ assert_se(u == 7);
+
+ r = sd_bus_message_read(m, "y(yt)", &v, &u, &u64);
+ assert_se(r > 0);
+ assert_se(v == 9);
+ assert_se(u == 77);
+ assert_se(u64 == 7777);
+
+ r = sd_bus_message_read(m, "y", &v);
+ assert_se(r > 0);
+ assert_se(v == 10);
+
+ r = sd_bus_message_read(m, "()");
+ assert_se(r < 0);
+
+ r = sd_bus_message_read(m, "ba(ss)", &boolean, 3, &x, &y, &a, &b, &c, &d);
+ assert_se(r > 0);
+ assert_se(boolean);
+ assert_se(streq(x, "aaa"));
+ assert_se(streq(y, "1"));
+ assert_se(streq(a, "bbb"));
+ assert_se(streq(b, "2"));
+ assert_se(streq(c, "ccc"));
+ assert_se(streq(d, "3"));
+
+ assert_se(sd_bus_message_verify_type(m, 'a', "s") > 0);
+
+ r = sd_bus_message_read(m, "as", 2, &x, &y);
+ assert_se(r > 0);
+ assert_se(streq(x, "foobar"));
+ assert_se(streq(y, "waldo"));
+
+ r = sd_bus_message_read_basic(m, 's', &s);
+ assert_se(r > 0);
+ assert_se(streq(s, "hallo"));
+
+ r = sd_bus_message_read_array(m, 'i', (const void**) &return_array, &sz);
+ assert_se(r > 0);
+ assert_se(sz == sizeof(integer_array));
+ assert_se(memcmp(integer_array, return_array, sz) == 0);
+
+ r = sd_bus_message_read_array(m, 'u', (const void**) &return_array, &sz);
+ assert_se(r > 0);
+ assert_se(sz == 0);
+
+ r = sd_bus_message_read(m, "a(stdo)", 1, &x, &u64, &dbl, &y);
+ assert_se(r > 0);
+ assert_se(streq(x, "foo"));
+ assert_se(u64 == 815ULL);
+ assert_se(fabs(dbl - 47.0) < 0.1);
+ assert_se(streq(y, "/"));
+
+ r = sd_bus_message_peek_type(m, NULL, NULL);
+ assert_se(r == 0);
+
+ r = sd_bus_message_new_method_call(bus, &copy, "foobar.waldo", "/", "foobar.waldo", "Piep");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_rewind(m, true);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_copy(copy, m, true);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_seal(copy, 4712, 0);
+ assert_se(r >= 0);
+
+ fclose(ms);
+ ms = open_memstream_unlocked(&third, &third_size);
+ sd_bus_message_dump(copy, ms, 0);
+ fflush(ms);
+ assert_se(!ferror(ms));
+
+ printf("<%.*s>\n", (int) first_size, first);
+ printf("<%.*s>\n", (int) third_size, third);
+
+ assert_se(first_size == third_size);
+ assert_se(memcmp(first, third, third_size) == 0);
+
+ r = sd_bus_message_rewind(m, true);
+ assert_se(r >= 0);
+
+ assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0);
+
+ r = sd_bus_message_skip(m, "ssasg");
+ assert_se(r > 0);
+
+ assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0);
+
+ r = sd_bus_message_skip(m, "sass");
+ assert_se(r >= 0);
+
+ assert_se(sd_bus_message_verify_type(m, 'a', "{yv}") > 0);
+
+ r = sd_bus_message_skip(m, "a{yv}y(ty)y(yt)y");
+ assert_se(r >= 0);
+
+ assert_se(sd_bus_message_verify_type(m, 'b', NULL) > 0);
+
+ r = sd_bus_message_read(m, "b", &boolean);
+ assert_se(r > 0);
+ assert_se(boolean);
+
+ r = sd_bus_message_enter_container(m, 0, NULL);
+ assert_se(r > 0);
+
+ r = sd_bus_message_read(m, "(ss)", &x, &y);
+ assert_se(r > 0);
+
+ r = sd_bus_message_read(m, "(ss)", &a, &b);
+ assert_se(r > 0);
+
+ r = sd_bus_message_read(m, "(ss)", &c, &d);
+ assert_se(r > 0);
+
+ r = sd_bus_message_read(m, "(ss)", &x, &y);
+ assert_se(r == 0);
+
+ r = sd_bus_message_exit_container(m);
+ assert_se(r >= 0);
+
+ assert_se(streq(x, "aaa"));
+ assert_se(streq(y, "1"));
+ assert_se(streq(a, "bbb"));
+ assert_se(streq(b, "2"));
+ assert_se(streq(c, "ccc"));
+ assert_se(streq(d, "3"));
+
+ test_bus_label_escape();
+ test_bus_path_encode();
+ test_bus_path_encode_unique();
+ test_bus_path_encode_many();
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-match.c b/src/libsystemd/sd-bus/test-bus-match.c
new file mode 100644
index 0000000..7a20a6c
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-match.c
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bus-match.h"
+#include "bus-message.h"
+#include "bus-slot.h"
+#include "log.h"
+#include "macro.h"
+#include "memory-util.h"
+#include "tests.h"
+
+static bool mask[32];
+
+static int filter(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ log_info("Ran %u", PTR_TO_UINT(userdata));
+ assert_se(PTR_TO_UINT(userdata) < ELEMENTSOF(mask));
+ mask[PTR_TO_UINT(userdata)] = true;
+ return 0;
+}
+
+static bool mask_contains(unsigned a[], unsigned n) {
+ unsigned i, j;
+
+ for (i = 0; i < ELEMENTSOF(mask); i++) {
+ bool found = false;
+
+ for (j = 0; j < n; j++)
+ if (a[j] == i) {
+ found = true;
+ break;
+ }
+
+ if (found != mask[i])
+ return false;
+ }
+
+ return true;
+}
+
+static int match_add(sd_bus_slot *slots, struct bus_match_node *root, const char *match, int value) {
+ struct bus_match_component *components;
+ unsigned n_components;
+ sd_bus_slot *s;
+ int r;
+
+ s = slots + value;
+
+ r = bus_match_parse(match, &components, &n_components);
+ if (r < 0)
+ return r;
+
+ s->userdata = INT_TO_PTR(value);
+ s->match_callback.callback = filter;
+
+ r = bus_match_add(root, components, n_components, &s->match_callback);
+ bus_match_parse_free(components, n_components);
+
+ return r;
+}
+
+static void test_match_scope(const char *match, enum bus_match_scope scope) {
+ struct bus_match_component *components = NULL;
+ unsigned n_components = 0;
+
+ assert_se(bus_match_parse(match, &components, &n_components) >= 0);
+ assert_se(bus_match_get_scope(components, n_components) == scope);
+ bus_match_parse_free(components, n_components);
+}
+
+int main(int argc, char *argv[]) {
+ struct bus_match_node root = {
+ .type = BUS_MATCH_ROOT,
+ };
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ sd_bus_slot slots[19] = {};
+ int r;
+
+ test_setup_logging(LOG_INFO);
+
+ r = sd_bus_open_user(&bus);
+ if (r < 0)
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ return log_tests_skipped("Failed to connect to bus");
+
+ assert_se(match_add(slots, &root, "arg2='wal\\'do',sender='foo',type='signal',interface='bar.x',", 1) >= 0);
+ assert_se(match_add(slots, &root, "arg2='wal\\'do2',sender='foo',type='signal',interface='bar.x',", 2) >= 0);
+ assert_se(match_add(slots, &root, "arg3='test',sender='foo',type='signal',interface='bar.x',", 3) >= 0);
+ assert_se(match_add(slots, &root, "arg3='test',sender='foo',type='method_call',interface='bar.x',", 4) >= 0);
+ assert_se(match_add(slots, &root, "", 5) >= 0);
+ assert_se(match_add(slots, &root, "interface='quux.x'", 6) >= 0);
+ assert_se(match_add(slots, &root, "interface='bar.x'", 7) >= 0);
+ assert_se(match_add(slots, &root, "member='waldo',path='/foo/bar'", 8) >= 0);
+ assert_se(match_add(slots, &root, "path='/foo/bar'", 9) >= 0);
+ assert_se(match_add(slots, &root, "path_namespace='/foo'", 10) >= 0);
+ assert_se(match_add(slots, &root, "path_namespace='/foo/quux'", 11) >= 0);
+ assert_se(match_add(slots, &root, "arg1='two'", 12) >= 0);
+ assert_se(match_add(slots, &root, "member='waldo',arg2path='/prefix/'", 13) >= 0);
+ assert_se(match_add(slots, &root, "member=waldo,path='/foo/bar',arg3namespace='prefix'", 14) >= 0);
+ assert_se(match_add(slots, &root, "arg4has='pi'", 15) >= 0);
+ assert_se(match_add(slots, &root, "arg4has='pa'", 16) >= 0);
+ assert_se(match_add(slots, &root, "arg4has='po'", 17) >= 0);
+ assert_se(match_add(slots, &root, "arg4='pi'", 18) >= 0);
+
+ bus_match_dump(stdout, &root, 0);
+
+ assert_se(sd_bus_message_new_signal(bus, &m, "/foo/bar", "bar.x", "waldo") >= 0);
+ assert_se(sd_bus_message_append(m, "ssssas", "one", "two", "/prefix/three", "prefix.four", 3, "pi", "pa", "po") >= 0);
+ assert_se(sd_bus_message_seal(m, 1, 0) >= 0);
+
+ zero(mask);
+ assert_se(bus_match_run(NULL, &root, m) == 0);
+ assert_se(mask_contains((unsigned[]) { 9, 8, 7, 5, 10, 12, 13, 14, 15, 16, 17 }, 11));
+
+ assert_se(bus_match_remove(&root, &slots[8].match_callback) >= 0);
+ assert_se(bus_match_remove(&root, &slots[13].match_callback) >= 0);
+
+ bus_match_dump(stdout, &root, 0);
+
+ zero(mask);
+ assert_se(bus_match_run(NULL, &root, m) == 0);
+ assert_se(mask_contains((unsigned[]) { 9, 5, 10, 12, 14, 7, 15, 16, 17 }, 9));
+
+ for (enum bus_match_node_type i = 0; i < _BUS_MATCH_NODE_TYPE_MAX; i++) {
+ char buf[32];
+ const char *x;
+
+ assert_se(x = bus_match_node_type_to_string(i, buf, sizeof(buf)));
+
+ if (i >= BUS_MATCH_MESSAGE_TYPE)
+ assert_se(bus_match_node_type_from_string(x, strlen(x)) == i);
+ }
+
+ bus_match_free(&root);
+
+ test_match_scope("interface='foobar'", BUS_MATCH_GENERIC);
+ test_match_scope("", BUS_MATCH_GENERIC);
+ test_match_scope("interface='org.freedesktop.DBus.Local'", BUS_MATCH_LOCAL);
+ test_match_scope("sender='org.freedesktop.DBus.Local'", BUS_MATCH_LOCAL);
+ test_match_scope("member='gurke',path='/org/freedesktop/DBus/Local'", BUS_MATCH_LOCAL);
+ test_match_scope("arg2='piep',sender='org.freedesktop.DBus',member='waldo'", BUS_MATCH_DRIVER);
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-objects.c b/src/libsystemd/sd-bus/test-bus-objects.c
new file mode 100644
index 0000000..949ef07
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-objects.c
@@ -0,0 +1,675 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <pthread.h>
+#include <stdlib.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-dump.h"
+#include "bus-internal.h"
+#include "bus-message.h"
+#include "log.h"
+#include "macro.h"
+#include "strv.h"
+#include "util.h"
+
+struct context {
+ int fds[2];
+ bool quit;
+ char *something;
+ char *automatic_string_property;
+ uint32_t automatic_integer_property;
+};
+
+static int something_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ struct context *c = userdata;
+ const char *s;
+ char *n = NULL;
+ int r;
+
+ r = sd_bus_message_read(m, "s", &s);
+ assert_se(r > 0);
+
+ n = strjoin("<<<", s, ">>>");
+ assert_se(n);
+
+ free(c->something);
+ c->something = n;
+
+ log_info("AlterSomething() called, got %s, returning %s", s, n);
+
+ /* This should fail, since the return type doesn't match */
+ assert_se(sd_bus_reply_method_return(m, "u", 4711) == -ENOMSG);
+
+ r = sd_bus_reply_method_return(m, "s", n);
+ assert_se(r >= 0);
+
+ return 1;
+}
+
+static int exit_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ struct context *c = userdata;
+ int r;
+
+ c->quit = true;
+
+ log_info("Exit called");
+
+ r = sd_bus_reply_method_return(m, "");
+ assert_se(r >= 0);
+
+ return 1;
+}
+
+static int get_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) {
+ struct context *c = userdata;
+ int r;
+
+ log_info("property get for %s called, returning \"%s\".", property, c->something);
+
+ r = sd_bus_message_append(reply, "s", c->something);
+ assert_se(r >= 0);
+
+ return 1;
+}
+
+static int set_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, void *userdata, sd_bus_error *error) {
+ struct context *c = userdata;
+ const char *s;
+ char *n;
+ int r;
+
+ log_info("property set for %s called", property);
+
+ r = sd_bus_message_read(value, "s", &s);
+ assert_se(r >= 0);
+
+ n = strdup(s);
+ assert_se(n);
+
+ free(c->something);
+ c->something = n;
+
+ return 1;
+}
+
+static int value_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *s = NULL;
+ const char *x;
+ int r;
+
+ assert_se(asprintf(&s, "object %p, path %s", userdata, path) >= 0);
+ r = sd_bus_message_append(reply, "s", s);
+ assert_se(r >= 0);
+
+ assert_se(x = startswith(path, "/value/"));
+
+ assert_se(PTR_TO_UINT(userdata) == 30);
+
+ return 1;
+}
+
+static int notify_test(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ int r;
+
+ assert_se(sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.ValueTest", "Value", NULL) >= 0);
+
+ r = sd_bus_reply_method_return(m, NULL);
+ assert_se(r >= 0);
+
+ return 1;
+}
+
+static int notify_test2(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ int r;
+
+ assert_se(sd_bus_emit_properties_changed_strv(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.ValueTest", NULL) >= 0);
+
+ r = sd_bus_reply_method_return(m, NULL);
+ assert_se(r >= 0);
+
+ return 1;
+}
+
+static int emit_interfaces_added(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ int r;
+
+ assert_se(sd_bus_emit_interfaces_added(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0);
+
+ r = sd_bus_reply_method_return(m, NULL);
+ assert_se(r >= 0);
+
+ return 1;
+}
+
+static int emit_interfaces_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ int r;
+
+ assert_se(sd_bus_emit_interfaces_removed(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0);
+
+ r = sd_bus_reply_method_return(m, NULL);
+ assert_se(r >= 0);
+
+ return 1;
+}
+
+static int emit_object_added(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ int r;
+
+ assert_se(sd_bus_emit_object_added(sd_bus_message_get_bus(m), "/value/a/x") >= 0);
+
+ r = sd_bus_reply_method_return(m, NULL);
+ assert_se(r >= 0);
+
+ return 1;
+}
+
+static int emit_object_with_manager_added(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ assert_se(sd_bus_emit_object_added(sd_bus_message_get_bus(m), "/value/a") >= 0);
+
+ return ASSERT_SE_NONNEG(sd_bus_reply_method_return(m, NULL));
+}
+
+static int emit_object_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ int r;
+
+ assert_se(sd_bus_emit_object_removed(sd_bus_message_get_bus(m), "/value/a/x") >= 0);
+
+ r = sd_bus_reply_method_return(m, NULL);
+ assert_se(r >= 0);
+
+ return 1;
+}
+
+static const sd_bus_vtable vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD("AlterSomething", "s", "s", something_handler, 0),
+ SD_BUS_METHOD("Exit", "", "", exit_handler, 0),
+ SD_BUS_WRITABLE_PROPERTY("Something", "s", get_handler, set_handler, 0, 0),
+ SD_BUS_WRITABLE_PROPERTY("AutomaticStringProperty", "s", NULL, NULL, offsetof(struct context, automatic_string_property), 0),
+ SD_BUS_WRITABLE_PROPERTY("AutomaticIntegerProperty", "u", NULL, NULL, offsetof(struct context, automatic_integer_property), 0),
+ SD_BUS_METHOD("NoOperation", NULL, NULL, NULL, 0),
+ SD_BUS_METHOD("EmitInterfacesAdded", NULL, NULL, emit_interfaces_added, 0),
+ SD_BUS_METHOD("EmitInterfacesRemoved", NULL, NULL, emit_interfaces_removed, 0),
+ SD_BUS_METHOD("EmitObjectAdded", NULL, NULL, emit_object_added, 0),
+ SD_BUS_METHOD("EmitObjectWithManagerAdded", NULL, NULL, emit_object_with_manager_added, 0),
+ SD_BUS_METHOD("EmitObjectRemoved", NULL, NULL, emit_object_removed, 0),
+ SD_BUS_VTABLE_END
+};
+
+static const sd_bus_vtable vtable2[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD("NotifyTest", "", "", notify_test, 0),
+ SD_BUS_METHOD("NotifyTest2", "", "", notify_test2, 0),
+ SD_BUS_PROPERTY("Value", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("Value2", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
+ SD_BUS_PROPERTY("Value3", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Value4", "s", value_handler, 10, 0),
+ SD_BUS_PROPERTY("AnExplicitProperty", "s", NULL, offsetof(struct context, something), SD_BUS_VTABLE_PROPERTY_EXPLICIT|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
+ SD_BUS_VTABLE_END
+};
+
+static int enumerator_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
+
+ if (object_path_startswith("/value", path))
+ assert_se(*nodes = strv_new("/value/a", "/value/b", "/value/c"));
+
+ return 1;
+}
+
+static int enumerator2_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
+
+ if (object_path_startswith("/value/a", path))
+ assert_se(*nodes = strv_new("/value/a/x", "/value/a/y", "/value/a/z"));
+
+ return 1;
+}
+
+static int enumerator3_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
+ _cleanup_strv_free_ char **v = NULL;
+
+ if (!object_path_startswith("/value/b", path))
+ return 1;
+
+ for (unsigned i = 0; i < 30; i++)
+ assert_se(strv_extendf(&v, "/value/b/%u", i) >= 0);
+
+ *nodes = TAKE_PTR(v);
+ return 1;
+}
+
+static void *server(void *p) {
+ struct context *c = p;
+ sd_bus *bus = NULL;
+ sd_id128_t id;
+ int r;
+
+ c->quit = false;
+
+ assert_se(sd_id128_randomize(&id) >= 0);
+
+ assert_se(sd_bus_new(&bus) >= 0);
+ assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0);
+ assert_se(sd_bus_set_server(bus, 1, id) >= 0);
+
+ assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.test", vtable, c) >= 0);
+ assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.test2", vtable, c) >= 0);
+ assert_se(sd_bus_add_fallback_vtable(bus, NULL, "/value", "org.freedesktop.systemd.ValueTest", vtable2, NULL, UINT_TO_PTR(20)) >= 0);
+ assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value", enumerator_callback, NULL) >= 0);
+ assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value/a", enumerator2_callback, NULL) >= 0);
+ assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value/b", enumerator3_callback, NULL) >= 0);
+ assert_se(sd_bus_add_object_manager(bus, NULL, "/value") >= 0);
+ assert_se(sd_bus_add_object_manager(bus, NULL, "/value/a") >= 0);
+
+ assert_se(sd_bus_start(bus) >= 0);
+
+ log_error("Entering event loop on server");
+
+ while (!c->quit) {
+ log_error("Loop!");
+
+ r = sd_bus_process(bus, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to process requests: %m");
+ goto fail;
+ }
+
+ if (r == 0) {
+ r = sd_bus_wait(bus, UINT64_MAX);
+ if (r < 0) {
+ log_error_errno(r, "Failed to wait: %m");
+ goto fail;
+ }
+
+ continue;
+ }
+ }
+
+ r = 0;
+
+fail:
+ if (bus) {
+ sd_bus_flush(bus);
+ sd_bus_unref(bus);
+ }
+
+ return INT_TO_PTR(r);
+}
+
+static int client(struct context *c) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_strv_free_ char **lines = NULL;
+ const char *s;
+ int r;
+
+ assert_se(sd_bus_new(&bus) >= 0);
+ assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0);
+ assert_se(sd_bus_start(bus) >= 0);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "NoOperation", &error, NULL, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "s", "hallo");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_read(reply, "s", &s);
+ assert_se(r >= 0);
+ assert_se(streq(s, "<<<hallo>>>"));
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Doesntexist", &error, &reply, "");
+ assert_se(r < 0);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD));
+
+ sd_bus_error_free(&error);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Doesntexist", &error, &reply, NULL); /* NULL and "" are equivalent */
+ assert_se(r < 0);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD));
+
+ sd_bus_error_free(&error);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "as", 1, "hallo");
+ assert_se(r < 0);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS));
+
+ sd_bus_error_free(&error);
+
+ r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_read(reply, "s", &s);
+ assert_se(r >= 0);
+ assert_se(streq(s, "<<<hallo>>>"));
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, "s", "test");
+ assert_se(r >= 0);
+
+ r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_read(reply, "s", &s);
+ assert_se(r >= 0);
+ assert_se(streq(s, "test"));
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AutomaticIntegerProperty", &error, "u", 815);
+ assert_se(r >= 0);
+
+ assert_se(c->automatic_integer_property == 815);
+
+ r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AutomaticStringProperty", &error, "s", "Du Dödel, Du!");
+ assert_se(r >= 0);
+
+ assert_se(streq(c->automatic_string_property, "Du Dödel, Du!"));
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, "");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_read(reply, "s", &s);
+ assert_se(r >= 0);
+ fputs(s, stdout);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, NULL); /* NULL and "" are equivalent */
+ assert_se(r >= 0);
+
+ r = sd_bus_message_read(reply, "s", &s);
+ assert_se(r >= 0);
+ fputs(s, stdout);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/value/xuzz", "org.freedesktop.systemd.ValueTest", "Value", &error, &reply, "s");
+ assert_se(r >= 0);
+
+ r = sd_bus_message_read(reply, "s", &s);
+ assert_se(r >= 0);
+ log_info("read %s", s);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_read(reply, "s", &s);
+ assert_se(r >= 0);
+ fputs(s, stdout);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_read(reply, "s", &s);
+ assert_se(r >= 0);
+ fputs(s, stdout);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_read(reply, "s", &s);
+ assert_se(r >= 0);
+ fputs(s, stdout);
+
+ assert_se(lines = strv_split_newlines(s));
+ assert_se(strv_contains(lines, " <node name=\"x\"/>"));
+ assert_se(strv_contains(lines, " <node name=\"y\"/>"));
+ assert_se(strv_contains(lines, " <node name=\"z\"/>"));
+ lines = strv_free(lines);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/b", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_message_read(reply, "s", &s);
+ assert_se(r >= 0);
+ fputs(s, stdout);
+
+ assert_se(lines = strv_split_newlines(s));
+ for (unsigned i = 0; i < 30; i++) {
+ _cleanup_free_ char *n = NULL;
+
+ assert_se(asprintf(&n, " <node name=\"%u\"/>", i) >= 0);
+ assert_se(strv_contains(lines, n));
+ }
+ lines = strv_free(lines);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", NULL);
+ assert_se(r >= 0);
+
+ sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", "org.freedesktop.systemd.ValueTest2");
+ assert_se(r < 0);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_INTERFACE));
+ sd_bus_error_free(&error);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", &error, &reply, NULL);
+ assert_se(r < 0);
+ assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD));
+ sd_bus_error_free(&error);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", &error, &reply, NULL);
+ assert_se(r >= 0);
+
+ sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ /* Check that /value/b does not have ObjectManager interface but /value/a does */
+ assert_se(sd_bus_message_rewind(reply, 1) > 0);
+ assert_se(sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{oa{sa{sv}}}") > 0);
+ while (ASSERT_SE_NONNEG(sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "oa{sa{sv}}")) > 0) {
+ const char *path = NULL;
+ assert_se(sd_bus_message_read_basic(reply, 'o', &path) > 0);
+ if (STR_IN_SET(path, "/value/b", "/value/a")) {
+ /* Check that there is no object manager interface here */
+ bool found_object_manager_interface = false;
+ assert_se(sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sa{sv}}") > 0);
+ while (ASSERT_SE_NONNEG(sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sa{sv}")) > 0) {
+ const char *interface_name = NULL;
+ assert_se(sd_bus_message_read_basic(reply, 's', &interface_name) > 0);
+
+ if (streq(interface_name, "org.freedesktop.DBus.ObjectManager")) {
+ assert_se(!streq(path, "/value/b"));
+ found_object_manager_interface = true;
+ }
+
+ assert_se(sd_bus_message_skip(reply, "a{sv}") >= 0);
+ assert_se(sd_bus_message_exit_container(reply) >= 0);
+ }
+ assert_se(sd_bus_message_exit_container(reply) >= 0);
+
+ if (streq(path, "/value/a")) {
+ /* ObjectManager must be here */
+ assert_se(found_object_manager_interface);
+ }
+
+ } else
+ assert_se(sd_bus_message_skip(reply, "a{sa{sv}}") >= 0);
+
+ assert_se(sd_bus_message_exit_container(reply) >= 0);
+ }
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.systemd.ValueTest", "NotifyTest", &error, NULL, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_process(bus, &reply);
+ assert_se(r > 0);
+
+ assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged"));
+ sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.systemd.ValueTest", "NotifyTest2", &error, NULL, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_process(bus, &reply);
+ assert_se(r > 0);
+
+ assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged"));
+ sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitInterfacesAdded", &error, NULL, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_process(bus, &reply);
+ assert_se(r > 0);
+
+ assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"));
+ sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitInterfacesRemoved", &error, NULL, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_process(bus, &reply);
+ assert_se(r > 0);
+
+ assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"));
+ sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectAdded", &error, NULL, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_process(bus, &reply);
+ assert_se(r > 0);
+
+ assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"));
+ sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ /* Check if /value/a/x does not have org.freedesktop.DBus.ObjectManager */
+ assert_se(sd_bus_message_rewind(reply, 1) >= 0);
+ const char* should_be_value_a_x = NULL;
+ assert_se(sd_bus_message_read_basic(reply, 'o', &should_be_value_a_x) > 0);
+ assert_se(streq(should_be_value_a_x, "/value/a/x"));
+ assert_se(sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sa{sv}}") > 0);
+ while (ASSERT_SE_NONNEG(sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sa{sv}")) > 0) {
+ const char* interface_name = NULL;
+ assert_se(sd_bus_message_read_basic(reply, 's', &interface_name) > 0);
+
+ assert(!streq(interface_name, "org.freedesktop.DBus.ObjectManager"));
+
+ assert_se(sd_bus_message_skip(reply, "a{sv}") >= 0);
+
+ assert_se(sd_bus_message_exit_container(reply) >= 0);
+ }
+
+ reply = sd_bus_message_unref(reply);
+
+ assert_se(sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectWithManagerAdded", &error, NULL, NULL) >= 0);
+
+ assert_se(sd_bus_process(bus, &reply) > 0);
+
+ assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"));
+ sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ /* Check if /value/a has org.freedesktop.DBus.ObjectManager */
+ assert_se(sd_bus_message_rewind(reply, 1) >= 0);
+ const char* should_be_value_a = NULL;
+ bool found_object_manager = false;
+ assert_se(sd_bus_message_read_basic(reply, 'o', &should_be_value_a) > 0);
+ assert_se(streq(should_be_value_a, "/value/a"));
+ assert_se(sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sa{sv}}") > 0);
+ while (ASSERT_SE_NONNEG(sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sa{sv}")) > 0) {
+ const char* interface_name = NULL;
+ assert_se(sd_bus_message_read_basic(reply, 's', &interface_name));
+
+ if (streq(interface_name, "org.freedesktop.DBus.ObjectManager")) {
+ found_object_manager = true;
+ break;
+ }
+
+ assert_se(sd_bus_message_skip(reply, "a{sv}") >= 0);
+
+ assert_se(sd_bus_message_exit_container(reply) >= 0);
+ }
+ assert_se(found_object_manager);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectRemoved", &error, NULL, NULL);
+ assert_se(r >= 0);
+
+ r = sd_bus_process(bus, &reply);
+ assert_se(r > 0);
+
+ assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"));
+ sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
+
+ /* Check if /value/a/x does not have org.freedesktop.DBus.ObjectManager */
+ assert_se(sd_bus_message_rewind(reply, 1) >= 0);
+ should_be_value_a_x = NULL;
+ assert_se(sd_bus_message_read_basic(reply, 'o', &should_be_value_a_x) > 0);
+ assert_se(streq(should_be_value_a_x, "/value/a/x"));
+ assert_se(sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "s") > 0);
+ const char* deleted_interface_name = NULL;
+ while (ASSERT_SE_NONNEG(sd_bus_message_read_basic(reply, 's', &deleted_interface_name)) > 0) {
+ assert(!streq(deleted_interface_name, "org.freedesktop.DBus.ObjectManager"));
+ }
+ assert_se(sd_bus_message_exit_container(reply) >= 0);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Exit", &error, NULL, NULL);
+ assert_se(r >= 0);
+
+ sd_bus_flush(bus);
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ struct context c = {};
+ pthread_t s;
+ void *p;
+ int r, q;
+
+ c.automatic_integer_property = 4711;
+ assert_se(c.automatic_string_property = strdup("dudeldu"));
+
+ assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0);
+
+ r = pthread_create(&s, NULL, server, &c);
+ if (r != 0)
+ return -r;
+
+ r = client(&c);
+
+ q = pthread_join(s, &p);
+ if (q != 0)
+ return -q;
+
+ if (r < 0)
+ return r;
+
+ if (PTR_TO_INT(p) < 0)
+ return PTR_TO_INT(p);
+
+ free(c.something);
+ free(c.automatic_string_property);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-queue-ref-cycle.c b/src/libsystemd/sd-bus/test-bus-queue-ref-cycle.c
new file mode 100644
index 0000000..7c2fa72
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-queue-ref-cycle.c
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "sd-bus.h"
+
+#include "main-func.h"
+#include "tests.h"
+
+static int test_ref_unref(void) {
+ sd_bus_message *m = NULL;
+ sd_bus *bus = NULL;
+ int r;
+
+ /* This test will result in a memory leak in <= v240, but not on v241. Hence to be really useful it
+ * should be run through a leak tracker such as valgrind. */
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ return log_tests_skipped("Failed to connect to bus");
+
+ /* Create a message and enqueue it (this shouldn't send it though as the connection setup is not complete yet) */
+ assert_se(sd_bus_message_new_method_call(bus, &m, "foo.bar", "/foo", "quux.quux", "waldo") >= 0);
+ assert_se(sd_bus_send(bus, m, NULL) >= 0);
+
+ /* Let's now unref the message first and the bus second. */
+ m = sd_bus_message_unref(m);
+ bus = sd_bus_unref(bus);
+
+ /* We should have a memory leak now on <= v240. Let's do this again, but destroy in the opposite
+ * order. On v240 that too should be a leak. */
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ return log_tests_skipped("Failed to connect to bus");
+
+ assert_se(sd_bus_message_new_method_call(bus, &m, "foo.bar", "/foo", "quux.quux", "waldo") >= 0);
+ assert_se(sd_bus_send(bus, m, NULL) >= 0);
+
+ /* Let's now unref things in the opposite order */
+ bus = sd_bus_unref(bus);
+ m = sd_bus_message_unref(m);
+
+ return 0;
+}
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ test_setup_logging(LOG_INFO);
+
+ r = test_ref_unref();
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/libsystemd/sd-bus/test-bus-server.c b/src/libsystemd/sd-bus/test-bus-server.c
new file mode 100644
index 0000000..ab4045e
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-server.c
@@ -0,0 +1,198 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <pthread.h>
+#include <stdlib.h>
+
+#include "sd-bus.h"
+
+#include "bus-internal.h"
+#include "log.h"
+#include "macro.h"
+#include "memory-util.h"
+#include "string-util.h"
+
+struct context {
+ int fds[2];
+
+ bool client_negotiate_unix_fds;
+ bool server_negotiate_unix_fds;
+
+ bool client_anonymous_auth;
+ bool server_anonymous_auth;
+};
+
+static void *server(void *p) {
+ struct context *c = p;
+ sd_bus *bus = NULL;
+ sd_id128_t id;
+ bool quit = false;
+ int r;
+
+ assert_se(sd_id128_randomize(&id) >= 0);
+
+ assert_se(sd_bus_new(&bus) >= 0);
+ assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0);
+ assert_se(sd_bus_set_server(bus, 1, id) >= 0);
+ assert_se(sd_bus_set_anonymous(bus, c->server_anonymous_auth) >= 0);
+ assert_se(sd_bus_negotiate_fds(bus, c->server_negotiate_unix_fds) >= 0);
+ assert_se(sd_bus_start(bus) >= 0);
+
+ while (!quit) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+
+ r = sd_bus_process(bus, &m);
+ if (r < 0) {
+ log_error_errno(r, "Failed to process requests: %m");
+ goto fail;
+ }
+
+ if (r == 0) {
+ r = sd_bus_wait(bus, UINT64_MAX);
+ if (r < 0) {
+ log_error_errno(r, "Failed to wait: %m");
+ goto fail;
+ }
+
+ continue;
+ }
+
+ if (!m)
+ continue;
+
+ log_info("Got message! member=%s", strna(sd_bus_message_get_member(m)));
+
+ if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Exit")) {
+
+ assert_se((sd_bus_can_send(bus, 'h') >= 1) ==
+ (c->server_negotiate_unix_fds && c->client_negotiate_unix_fds));
+
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate return: %m");
+ goto fail;
+ }
+
+ quit = true;
+
+ } else if (sd_bus_message_is_method_call(m, NULL, NULL)) {
+ r = sd_bus_message_new_method_error(
+ m,
+ &reply,
+ &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNKNOWN_METHOD, "Unknown method."));
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate return: %m");
+ goto fail;
+ }
+ }
+
+ if (reply) {
+ r = sd_bus_send(bus, reply, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to send reply: %m");
+ goto fail;
+ }
+ }
+ }
+
+ r = 0;
+
+fail:
+ if (bus) {
+ sd_bus_flush(bus);
+ sd_bus_unref(bus);
+ }
+
+ return INT_TO_PTR(r);
+}
+
+static int client(struct context *c) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert_se(sd_bus_new(&bus) >= 0);
+ assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0);
+ assert_se(sd_bus_negotiate_fds(bus, c->client_negotiate_unix_fds) >= 0);
+ assert_se(sd_bus_set_anonymous(bus, c->client_anonymous_auth) >= 0);
+ assert_se(sd_bus_start(bus) >= 0);
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &m,
+ "org.freedesktop.systemd.test",
+ "/",
+ "org.freedesktop.systemd.test",
+ "Exit");
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate method call: %m");
+
+ r = sd_bus_call(bus, m, 0, &error, &reply);
+ if (r < 0)
+ return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
+
+ return 0;
+}
+
+static int test_one(bool client_negotiate_unix_fds, bool server_negotiate_unix_fds,
+ bool client_anonymous_auth, bool server_anonymous_auth) {
+
+ struct context c;
+ pthread_t s;
+ void *p;
+ int r, q;
+
+ zero(c);
+
+ assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0);
+
+ c.client_negotiate_unix_fds = client_negotiate_unix_fds;
+ c.server_negotiate_unix_fds = server_negotiate_unix_fds;
+ c.client_anonymous_auth = client_anonymous_auth;
+ c.server_anonymous_auth = server_anonymous_auth;
+
+ r = pthread_create(&s, NULL, server, &c);
+ if (r != 0)
+ return -r;
+
+ r = client(&c);
+
+ q = pthread_join(s, &p);
+ if (q != 0)
+ return -q;
+
+ if (r < 0)
+ return r;
+
+ if (PTR_TO_INT(p) < 0)
+ return PTR_TO_INT(p);
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ int r;
+
+ r = test_one(true, true, false, false);
+ assert_se(r >= 0);
+
+ r = test_one(true, false, false, false);
+ assert_se(r >= 0);
+
+ r = test_one(false, true, false, false);
+ assert_se(r >= 0);
+
+ r = test_one(false, false, false, false);
+ assert_se(r >= 0);
+
+ r = test_one(true, true, true, true);
+ assert_se(r >= 0);
+
+ r = test_one(true, true, false, true);
+ assert_se(r >= 0);
+
+ r = test_one(true, true, true, false);
+ assert_se(r == -EPERM);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-signature.c b/src/libsystemd/sd-bus/test-bus-signature.c
new file mode 100644
index 0000000..ba94804
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-signature.c
@@ -0,0 +1,147 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bus-internal.h"
+#include "bus-signature.h"
+#include "log.h"
+#include "string-util.h"
+
+int main(int argc, char *argv[]) {
+ char prefix[256];
+ int r;
+
+ assert_se(signature_is_single("y", false));
+ assert_se(signature_is_single("u", false));
+ 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("((((s))))", false));
+ assert_se(signature_is_single("{ss}", true));
+ assert_se(signature_is_single("a{ss}", false));
+ assert_se(!signature_is_single("uu", 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("{}", true));
+ assert_se(!signature_is_single("{sss}", true));
+ assert_se(!signature_is_single("{s}", true));
+ assert_se(!signature_is_single("{ss}", false));
+ assert_se(!signature_is_single("{ass}", true));
+ assert_se(!signature_is_single("a}", true));
+
+ assert_se(signature_is_pair("yy"));
+ assert_se(signature_is_pair("ss"));
+ assert_se(signature_is_pair("sas"));
+ assert_se(signature_is_pair("sv"));
+ assert_se(signature_is_pair("sa(vs)"));
+ assert_se(!signature_is_pair(""));
+ assert_se(!signature_is_pair("va"));
+ assert_se(!signature_is_pair("sss"));
+ assert_se(!signature_is_pair("{s}ss"));
+
+ assert_se(signature_is_valid("ssa{ss}sssub", true));
+ assert_se(signature_is_valid("ssa{ss}sssub", false));
+ assert_se(signature_is_valid("{ss}", true));
+ assert_se(!signature_is_valid("{ss}", false));
+ assert_se(signature_is_valid("", true));
+ assert_se(signature_is_valid("", false));
+
+ assert_se(signature_is_valid("sssusa(uuubbba(uu)uuuu)a{u(uuuvas)}", false));
+
+ assert_se(!signature_is_valid("a", false));
+ assert_se(signature_is_valid("as", false));
+ assert_se(signature_is_valid("aas", false));
+ assert_se(signature_is_valid("aaas", false));
+ assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad", false));
+ assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas", false));
+ assert_se(!signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau", false));
+
+ assert_se(signature_is_valid("((((((((((((((((((((((((((((((((s))))))))))))))))))))))))))))))))", false));
+ assert_se(!signature_is_valid("((((((((((((((((((((((((((((((((()))))))))))))))))))))))))))))))))", false));
+
+ assert_se(namespace_complex_pattern("", ""));
+ assert_se(namespace_complex_pattern("foobar", "foobar"));
+ assert_se(namespace_complex_pattern("foobar.waldo", "foobar.waldo"));
+ assert_se(namespace_complex_pattern("foobar.", "foobar.waldo"));
+ assert_se(namespace_complex_pattern("foobar.waldo", "foobar."));
+ assert_se(!namespace_complex_pattern("foobar.waldo", "foobar"));
+ assert_se(!namespace_complex_pattern("foobar", "foobar.waldo"));
+ assert_se(!namespace_complex_pattern("", "foo"));
+ assert_se(!namespace_complex_pattern("foo", ""));
+ assert_se(!namespace_complex_pattern("foo.", ""));
+
+ assert_se(path_complex_pattern("", ""));
+ assert_se(!path_complex_pattern("", "/"));
+ assert_se(!path_complex_pattern("/", ""));
+ assert_se(path_complex_pattern("/", "/"));
+ assert_se(path_complex_pattern("/foobar/", "/"));
+ assert_se(!path_complex_pattern("/foobar/", "/foobar"));
+ assert_se(path_complex_pattern("/foobar", "/foobar"));
+ assert_se(!path_complex_pattern("/foobar", "/foobar/"));
+ assert_se(!path_complex_pattern("/foobar", "/foobar/waldo"));
+ assert_se(path_complex_pattern("/foobar/", "/foobar/waldo"));
+ assert_se(path_complex_pattern("/foobar/waldo", "/foobar/"));
+
+ assert_se(path_simple_pattern("/foo/", "/foo/bar/waldo"));
+
+ assert_se(namespace_simple_pattern("", ""));
+ assert_se(namespace_simple_pattern("", ".foobar"));
+ assert_se(namespace_simple_pattern("foobar", "foobar"));
+ assert_se(namespace_simple_pattern("foobar.waldo", "foobar.waldo"));
+ assert_se(namespace_simple_pattern("foobar", "foobar.waldo"));
+ assert_se(!namespace_simple_pattern("foobar.waldo", "foobar"));
+ assert_se(!namespace_simple_pattern("", "foo"));
+ assert_se(!namespace_simple_pattern("foo", ""));
+ assert_se(namespace_simple_pattern("foo.", "foo.bar.waldo"));
+
+ assert_se(streq(object_path_startswith("/foo/bar", "/foo"), "bar"));
+ assert_se(streq(object_path_startswith("/foo", "/foo"), ""));
+ assert_se(streq(object_path_startswith("/foo", "/"), "foo"));
+ assert_se(streq(object_path_startswith("/", "/"), ""));
+ assert_se(!object_path_startswith("/foo", "/bar"));
+ assert_se(!object_path_startswith("/", "/bar"));
+ assert_se(!object_path_startswith("/foo", ""));
+
+ assert_se(object_path_is_valid("/foo/bar"));
+ assert_se(object_path_is_valid("/foo"));
+ assert_se(object_path_is_valid("/"));
+ assert_se(object_path_is_valid("/foo5"));
+ assert_se(object_path_is_valid("/foo_5"));
+ assert_se(!object_path_is_valid(""));
+ assert_se(!object_path_is_valid("/foo/"));
+ assert_se(!object_path_is_valid("//"));
+ assert_se(!object_path_is_valid("//foo"));
+ assert_se(!object_path_is_valid("/foo//bar"));
+ assert_se(!object_path_is_valid("/foo/aaaäöä"));
+
+ OBJECT_PATH_FOREACH_PREFIX(prefix, "/") {
+ log_info("<%s>", prefix);
+ assert_not_reached();
+ }
+
+ r = 0;
+ OBJECT_PATH_FOREACH_PREFIX(prefix, "/xxx") {
+ log_info("<%s>", prefix);
+ assert_se(streq(prefix, "/"));
+ assert_se(r == 0);
+ r++;
+ }
+ assert_se(r == 1);
+
+ r = 0;
+ OBJECT_PATH_FOREACH_PREFIX(prefix, "/xxx/yyy/zzz") {
+ log_info("<%s>", prefix);
+ assert_se(r != 0 || streq(prefix, "/xxx/yyy"));
+ assert_se(r != 1 || streq(prefix, "/xxx"));
+ assert_se(r != 2 || streq(prefix, "/"));
+ r++;
+ }
+ assert_se(r == 3);
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-track.c b/src/libsystemd/sd-bus/test-bus-track.c
new file mode 100644
index 0000000..5604e84
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-track.c
@@ -0,0 +1,151 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <sys/socket.h>
+
+#include "sd-bus.h"
+
+#include "macro.h"
+#include "tests.h"
+
+static bool track_cb_called_x = false;
+static bool track_cb_called_y = false;
+static bool track_destroy_called_z = false;
+
+static int track_cb_x(sd_bus_track *t, void *userdata) {
+
+ log_error("TRACK CB X");
+
+ assert_se(!track_cb_called_x);
+ track_cb_called_x = true;
+
+ /* This means b's name disappeared. Let's now disconnect, to make sure the track handling on disconnect works
+ * as it should. */
+
+ assert_se(shutdown(sd_bus_get_fd(sd_bus_track_get_bus(t)), SHUT_RDWR) >= 0);
+ return 1;
+}
+
+static int track_cb_y(sd_bus_track *t, void *userdata) {
+
+ log_error("TRACK CB Y");
+
+ assert_se(!track_cb_called_y);
+ track_cb_called_y = true;
+
+ /* We got disconnected, let's close everything */
+
+ assert_se(sd_event_exit(sd_bus_get_event(sd_bus_track_get_bus(t)), EXIT_SUCCESS) >= 0);
+
+ return 0;
+}
+
+static int track_cb_z(sd_bus_track *t, void *userdata) {
+ assert_not_reached();
+}
+
+static void track_destroy_z(void *userdata) {
+ track_destroy_called_z = true;
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ _cleanup_(sd_bus_track_unrefp) sd_bus_track *x = NULL, *y = NULL, *z = NULL;
+ _cleanup_(sd_bus_unrefp) sd_bus *a = NULL, *b = NULL;
+ bool use_system_bus = false;
+ const char *unique;
+ int r;
+
+ test_setup_logging(LOG_INFO);
+
+ assert_se(sd_event_default(&event) >= 0);
+
+ r = sd_bus_open_user(&a);
+ if (IN_SET(r, -ECONNREFUSED, -ENOENT, -ENOMEDIUM)) {
+ r = sd_bus_open_system(&a);
+ if (IN_SET(r, -ECONNREFUSED, -ENOENT))
+ return log_tests_skipped("Failed to connect to bus");
+ use_system_bus = true;
+ }
+ assert_se(r >= 0);
+
+ assert_se(sd_bus_attach_event(a, event, SD_EVENT_PRIORITY_NORMAL) >= 0);
+
+ if (use_system_bus)
+ assert_se(sd_bus_open_system(&b) >= 0);
+ else
+ assert_se(sd_bus_open_user(&b) >= 0);
+
+ assert_se(sd_bus_attach_event(b, event, SD_EVENT_PRIORITY_NORMAL) >= 0);
+
+ /* Watch b's name from a */
+ assert_se(sd_bus_track_new(a, &x, track_cb_x, NULL) >= 0);
+
+ assert_se(sd_bus_get_unique_name(b, &unique) >= 0);
+
+ assert_se(sd_bus_track_add_name(x, unique) >= 0);
+
+ /* Watch's a's own name from a */
+ assert_se(sd_bus_track_new(a, &y, track_cb_y, NULL) >= 0);
+
+ assert_se(sd_bus_get_unique_name(a, &unique) >= 0);
+
+ assert_se(sd_bus_track_add_name(y, unique) >= 0);
+
+ /* Basic tests. */
+ assert_se(sd_bus_track_new(a, &z, track_cb_z, NULL) >= 0);
+
+ /* non-recursive case */
+ assert_se(sd_bus_track_set_recursive(z, false) >= 0);
+ assert_se(sd_bus_track_get_recursive(z) == 0);
+ assert_se(!sd_bus_track_contains(z, unique));
+ assert_se(sd_bus_track_count_name(z, unique) == 0);
+ assert_se(sd_bus_track_remove_name(z, unique) == 0);
+ assert_se(sd_bus_track_add_name(z, unique) >= 0);
+ assert_se(sd_bus_track_add_name(z, unique) >= 0);
+ assert_se(sd_bus_track_add_name(z, unique) >= 0);
+ assert_se(sd_bus_track_set_recursive(z, true) == -EBUSY);
+ assert_se(sd_bus_track_contains(z, unique));
+ assert_se(sd_bus_track_count_name(z, unique) == 1);
+ assert_se(sd_bus_track_remove_name(z, unique) == 1);
+ assert_se(!sd_bus_track_contains(z, unique));
+ assert_se(sd_bus_track_count_name(z, unique) == 0);
+ assert_se(sd_bus_track_remove_name(z, unique) == 0);
+
+ /* recursive case */
+ assert_se(sd_bus_track_set_recursive(z, true) >= 0);
+ assert_se(sd_bus_track_get_recursive(z) == 1);
+ assert_se(!sd_bus_track_contains(z, unique));
+ assert_se(sd_bus_track_count_name(z, unique) == 0);
+ assert_se(sd_bus_track_remove_name(z, unique) == 0);
+ assert_se(sd_bus_track_add_name(z, unique) >= 0);
+ assert_se(sd_bus_track_add_name(z, unique) >= 0);
+ assert_se(sd_bus_track_add_name(z, unique) >= 0);
+ assert_se(sd_bus_track_set_recursive(z, false) == -EBUSY);
+ assert_se(sd_bus_track_contains(z, unique));
+ assert_se(sd_bus_track_count_name(z, unique) == 3);
+ assert_se(sd_bus_track_remove_name(z, unique) == 1);
+ assert_se(sd_bus_track_contains(z, unique));
+ assert_se(sd_bus_track_count_name(z, unique) == 2);
+ assert_se(sd_bus_track_remove_name(z, unique) == 1);
+ assert_se(sd_bus_track_contains(z, unique));
+ assert_se(sd_bus_track_count_name(z, unique) == 1);
+ assert_se(sd_bus_track_remove_name(z, unique) == 1);
+ assert_se(!sd_bus_track_contains(z, unique));
+ assert_se(sd_bus_track_count_name(z, unique) == 0);
+ assert_se(sd_bus_track_remove_name(z, unique) == 0);
+
+ assert_se(sd_bus_track_set_destroy_callback(z, track_destroy_z) >= 0);
+ z = sd_bus_track_unref(z);
+ assert_se(track_destroy_called_z);
+
+ /* Now make b's name disappear */
+ sd_bus_close(b);
+
+ assert_se(sd_event_loop(event) >= 0);
+
+ assert_se(track_cb_called_x);
+ assert_se(track_cb_called_y);
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-vtable-cc.cc b/src/libsystemd/sd-bus/test-bus-vtable-cc.cc
new file mode 120000
index 0000000..abee398
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-vtable-cc.cc
@@ -0,0 +1 @@
+test-bus-vtable.c \ No newline at end of file
diff --git a/src/libsystemd/sd-bus/test-bus-vtable.c b/src/libsystemd/sd-bus/test-bus-vtable.c
new file mode 100644
index 0000000..fe12238
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-vtable.c
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+/* We use system assert.h here, because we don't want to keep macro.h and log.h C++ compatible */
+#undef NDEBUG
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "sd-bus-vtable.h"
+
+#ifndef __cplusplus
+# include "bus-objects.h"
+#endif
+
+#include "test-vtable-data.h"
+
+#define DEFAULT_BUS_PATH "unix:path=/run/dbus/system_bus_socket"
+
+static struct context c = {};
+static int happy_finder_object = 0;
+
+static int happy_finder(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
+ assert(userdata);
+ assert(userdata == &c);
+
+#ifndef __cplusplus
+ log_info("%s called", __func__);
+#endif
+
+ happy_finder_object++;
+ *found = &happy_finder_object;
+ return 1; /* found */
+}
+
+static void test_vtable(void) {
+ sd_bus *bus = NULL;
+ int r;
+
+ assert(sd_bus_new(&bus) >= 0);
+
+ assert(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.testVtable", test_vtable_2, &c) >= 0);
+ assert(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.testVtable2", test_vtable_2, &c) >= 0);
+ /* the cast on the line below is needed to test with the old version of the table */
+ assert(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.testVtable221",
+ (const sd_bus_vtable *)vtable_format_221, &c) >= 0);
+
+ assert(sd_bus_add_fallback_vtable(bus, NULL, "/fallback", "org.freedesktop.systemd.testVtable2", test_vtable_2, happy_finder, &c) >= 0);
+
+ assert(sd_bus_set_address(bus, DEFAULT_BUS_PATH) >= 0);
+ r = sd_bus_start(bus);
+ assert(r == 0 || /* success */
+ r == -ENOENT /* dbus is inactive */ );
+
+#ifndef __cplusplus
+ _cleanup_free_ char *s, *s2;
+
+ assert_se(introspect_path(bus, "/foo", NULL, false, true, NULL, &s, NULL) == 1);
+ fputs(s, stdout);
+
+ assert_se(introspect_path(bus, "/fallback", NULL, false, true, NULL, &s2, NULL) == 1);
+ fputs(s2, stdout);
+
+ assert_se(happy_finder_object == 1);
+#endif
+
+ sd_bus_unref(bus);
+}
+
+int main(int argc, char **argv) {
+ test_vtable();
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/test-bus-watch-bind.c b/src/libsystemd/sd-bus/test-bus-watch-bind.c
new file mode 100644
index 0000000..c577330
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-bus-watch-bind.c
@@ -0,0 +1,223 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <pthread.h>
+
+#include "sd-bus.h"
+#include "sd-event.h"
+#include "sd-id128.h"
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "random-util.h"
+#include "rm-rf.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "tmpfile-util.h"
+#include "tests.h"
+
+static int method_foobar(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ log_info("Got Foobar() call.");
+
+ assert_se(sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), 0) >= 0);
+ return sd_bus_reply_method_return(m, NULL);
+}
+
+static int method_exit(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ log_info("Got Exit() call");
+ assert_se(sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), 1) >= 0);
+ return sd_bus_reply_method_return(m, NULL);
+}
+
+static const sd_bus_vtable vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD("Foobar", NULL, NULL, method_foobar, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("Exit", NULL, NULL, method_exit, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_VTABLE_END,
+};
+
+static void* thread_server(void *p) {
+ _cleanup_free_ char *suffixed = NULL, *suffixed2 = NULL, *d = NULL;
+ _cleanup_close_ int fd = -1;
+ union sockaddr_union u;
+ const char *path = p;
+ int r;
+
+ log_debug("Initializing server");
+
+ /* Let's play some games, by slowly creating the socket directory, and renaming it in the middle */
+ (void) usleep(100 * USEC_PER_MSEC);
+
+ assert_se(mkdir_parents(path, 0755) >= 0);
+ (void) usleep(100 * USEC_PER_MSEC);
+
+ assert_se(path_extract_directory(path, &d) >= 0);
+ assert_se(asprintf(&suffixed, "%s.%" PRIx64, d, random_u64()) >= 0);
+ assert_se(rename(d, suffixed) >= 0);
+ (void) usleep(100 * USEC_PER_MSEC);
+
+ assert_se(asprintf(&suffixed2, "%s.%" PRIx64, d, random_u64()) >= 0);
+ assert_se(symlink(suffixed2, d) >= 0);
+ (void) usleep(100 * USEC_PER_MSEC);
+
+ assert_se(symlink(basename(suffixed), suffixed2) >= 0);
+ (void) usleep(100 * USEC_PER_MSEC);
+
+ socklen_t sa_len;
+ r = sockaddr_un_set_path(&u.un, path);
+ assert_se(r >= 0);
+ sa_len = r;
+
+ fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+ assert_se(fd >= 0);
+
+ assert_se(bind(fd, &u.sa, sa_len) >= 0);
+ usleep(100 * USEC_PER_MSEC);
+
+ assert_se(listen(fd, SOMAXCONN_DELUXE) >= 0);
+ usleep(100 * USEC_PER_MSEC);
+
+ assert_se(touch(path) >= 0);
+ usleep(100 * USEC_PER_MSEC);
+
+ log_debug("Initialized server");
+
+ for (;;) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ sd_id128_t id;
+ int bus_fd, code;
+
+ assert_se(sd_id128_randomize(&id) >= 0);
+
+ assert_se(sd_event_new(&event) >= 0);
+
+ bus_fd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
+ assert_se(bus_fd >= 0);
+
+ log_debug("Accepted server connection");
+
+ assert_se(sd_bus_new(&bus) >= 0);
+ assert_se(sd_bus_set_description(bus, "server") >= 0);
+ assert_se(sd_bus_set_fd(bus, bus_fd, bus_fd) >= 0);
+ assert_se(sd_bus_set_server(bus, true, id) >= 0);
+ /* assert_se(sd_bus_set_anonymous(bus, true) >= 0); */
+
+ assert_se(sd_bus_attach_event(bus, event, 0) >= 0);
+
+ assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "foo.TestInterface", vtable, NULL) >= 0);
+
+ assert_se(sd_bus_start(bus) >= 0);
+
+ assert_se(sd_event_loop(event) >= 0);
+
+ assert_se(sd_event_get_exit_code(event, &code) >= 0);
+
+ if (code > 0)
+ break;
+ }
+
+ log_debug("Server done");
+
+ return NULL;
+}
+
+static void* thread_client1(void *p) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ const char *path = p, *t;
+ int r;
+
+ log_debug("Initializing client1");
+
+ assert_se(sd_bus_new(&bus) >= 0);
+ assert_se(sd_bus_set_description(bus, "client1") >= 0);
+
+ t = strjoina("unix:path=", path);
+ assert_se(sd_bus_set_address(bus, t) >= 0);
+ assert_se(sd_bus_set_watch_bind(bus, true) >= 0);
+ assert_se(sd_bus_start(bus) >= 0);
+
+ r = sd_bus_call_method(bus, "foo.bar", "/foo", "foo.TestInterface", "Foobar", &error, NULL, NULL);
+ assert_se(r >= 0);
+
+ log_debug("Client1 done");
+
+ return NULL;
+}
+
+static int client2_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ assert_se(sd_bus_message_is_method_error(m, NULL) == 0);
+ assert_se(sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), 0) >= 0);
+ return 0;
+}
+
+static void* thread_client2(void *p) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ const char *path = p, *t;
+
+ log_debug("Initializing client2");
+
+ assert_se(sd_event_new(&event) >= 0);
+ assert_se(sd_bus_new(&bus) >= 0);
+ assert_se(sd_bus_set_description(bus, "client2") >= 0);
+
+ t = strjoina("unix:path=", path);
+ assert_se(sd_bus_set_address(bus, t) >= 0);
+ assert_se(sd_bus_set_watch_bind(bus, true) >= 0);
+ assert_se(sd_bus_attach_event(bus, event, 0) >= 0);
+ assert_se(sd_bus_start(bus) >= 0);
+
+ assert_se(sd_bus_call_method_async(bus, NULL, "foo.bar", "/foo", "foo.TestInterface", "Foobar", client2_callback, NULL, NULL) >= 0);
+
+ assert_se(sd_event_loop(event) >= 0);
+
+ log_debug("Client2 done");
+
+ return NULL;
+}
+
+static void request_exit(const char *path) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ const char *t;
+
+ assert_se(sd_bus_new(&bus) >= 0);
+
+ t = strjoina("unix:path=", path);
+ assert_se(sd_bus_set_address(bus, t) >= 0);
+ assert_se(sd_bus_set_watch_bind(bus, true) >= 0);
+ assert_se(sd_bus_set_description(bus, "request-exit") >= 0);
+ assert_se(sd_bus_start(bus) >= 0);
+
+ assert_se(sd_bus_call_method(bus, "foo.bar", "/foo", "foo.TestInterface", "Exit", NULL, NULL, NULL) >= 0);
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_(rm_rf_physical_and_freep) char *d = NULL;
+ pthread_t server, client1, client2;
+ char *path;
+
+ test_setup_logging(LOG_DEBUG);
+
+ /* We use /dev/shm here rather than /tmp, since some weird distros might set up /tmp as some weird fs that
+ * doesn't support inotify properly. */
+ assert_se(mkdtemp_malloc("/dev/shm/systemd-watch-bind-XXXXXX", &d) >= 0);
+
+ path = strjoina(d, "/this/is/a/socket");
+
+ assert_se(pthread_create(&server, NULL, thread_server, path) == 0);
+ assert_se(pthread_create(&client1, NULL, thread_client1, path) == 0);
+ assert_se(pthread_create(&client2, NULL, thread_client2, path) == 0);
+
+ assert_se(pthread_join(client1, NULL) == 0);
+ assert_se(pthread_join(client2, NULL) == 0);
+
+ request_exit(path);
+
+ assert_se(pthread_join(server, NULL) == 0);
+
+ return 0;
+}
diff --git a/src/libsystemd/sd-bus/test-vtable-data.h b/src/libsystemd/sd-bus/test-vtable-data.h
new file mode 100644
index 0000000..7269a49
--- /dev/null
+++ b/src/libsystemd/sd-bus/test-vtable-data.h
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/* This is meant to be included in other files, hence no headers */
+
+struct context {
+ bool quit;
+ char *something;
+ char *automatic_string_property;
+ uint32_t automatic_integer_property;
+};
+
+static int handler(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ return 1;
+}
+
+static int value_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) {
+ return 1;
+}
+
+static int get_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) {
+ return 1;
+}
+
+static int set_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, void *userdata, sd_bus_error *error) {
+ return 1;
+}
+
+static const sd_bus_vtable test_vtable_1[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD("Hello", "ssas", "a(uu)", NULL, 0),
+ SD_BUS_METHOD("DeprecatedHello", "", "", NULL, SD_BUS_VTABLE_DEPRECATED),
+ SD_BUS_METHOD("DeprecatedHelloNoReply", "", "", NULL, SD_BUS_VTABLE_DEPRECATED|SD_BUS_VTABLE_METHOD_NO_REPLY),
+ SD_BUS_SIGNAL("Wowza", "sss", 0),
+ SD_BUS_SIGNAL("DeprecatedWowza", "ut", SD_BUS_VTABLE_DEPRECATED),
+ SD_BUS_WRITABLE_PROPERTY("AProperty", "s", get_handler, set_handler, 0, 0),
+ SD_BUS_PROPERTY("AReadOnlyDeprecatedProperty", "(ut)", get_handler, 0, SD_BUS_VTABLE_DEPRECATED),
+ SD_BUS_PROPERTY("ChangingProperty", "t", get_handler, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("Invalidating", "t", get_handler, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
+ SD_BUS_PROPERTY("Constant", "t", get_handler, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EXPLICIT),
+ SD_BUS_VTABLE_END
+};
+
+static const sd_bus_vtable test_vtable_2[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD("AlterSomething", "s", "s", handler, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("Exit", "", "", handler, 0),
+ SD_BUS_METHOD_WITH_OFFSET("AlterSomething2", "s", "s", handler, 200, 0),
+ SD_BUS_METHOD_WITH_OFFSET("Exit2", "", "", handler, 200, 0),
+ SD_BUS_METHOD_WITH_NAMES_OFFSET("AlterSomething3", "so", SD_BUS_PARAM(string) SD_BUS_PARAM(path),
+ "s", SD_BUS_PARAM(returnstring), handler, 200, 0),
+ SD_BUS_METHOD_WITH_NAMES("Exit3", "bx", SD_BUS_PARAM(with_confirmation) SD_BUS_PARAM(after_msec),
+ "bb", SD_BUS_PARAM(accepted) SD_BUS_PARAM(scheduled), handler, 0),
+ SD_BUS_PROPERTY("Value", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("Value2", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
+ SD_BUS_PROPERTY("Value3", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Value4", "s", value_handler, 10, 0),
+ SD_BUS_PROPERTY("AnExplicitProperty", "s", NULL, offsetof(struct context, something),
+ SD_BUS_VTABLE_PROPERTY_EXPLICIT|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
+ SD_BUS_WRITABLE_PROPERTY("Something", "s", get_handler, set_handler, 0, 0),
+ SD_BUS_WRITABLE_PROPERTY("AutomaticStringProperty", "s", NULL, NULL,
+ offsetof(struct context, automatic_string_property), 0),
+ SD_BUS_WRITABLE_PROPERTY("AutomaticIntegerProperty", "u", NULL, NULL,
+ offsetof(struct context, automatic_integer_property), 0),
+ SD_BUS_METHOD("NoOperation", NULL, NULL, NULL, 0),
+ SD_BUS_SIGNAL("DummySignal", "b", 0),
+ SD_BUS_SIGNAL("DummySignal2", "so", 0),
+ SD_BUS_SIGNAL_WITH_NAMES("DummySignal3", "so", SD_BUS_PARAM(string) SD_BUS_PARAM(path), 0),
+ SD_BUS_VTABLE_END
+};
+
+static const sd_bus_vtable test_vtable_deprecated[] = {
+ SD_BUS_VTABLE_START(SD_BUS_VTABLE_DEPRECATED),
+ SD_BUS_VTABLE_END
+};
+
+struct sd_bus_vtable_221 {
+ uint8_t type:8;
+ uint64_t flags:56;
+ union {
+ struct {
+ size_t element_size;
+ } start;
+ struct {
+ const char *member;
+ const char *signature;
+ const char *result;
+ sd_bus_message_handler_t handler;
+ size_t offset;
+ } method;
+ struct {
+ const char *member;
+ const char *signature;
+ } signal;
+ struct {
+ const char *member;
+ const char *signature;
+ sd_bus_property_get_t get;
+ sd_bus_property_set_t set;
+ size_t offset;
+ } property;
+ } x;
+};
+
+static const struct sd_bus_vtable_221 vtable_format_221[] = {
+ {
+ .type = _SD_BUS_VTABLE_START,
+ .flags = 0,
+ .x = {
+ .start = {
+ .element_size = sizeof(struct sd_bus_vtable_221)
+ },
+ },
+ },
+ {
+ .type = _SD_BUS_VTABLE_METHOD,
+ .flags = 0,
+ .x = {
+ .method = {
+ .member = "Exit",
+ .signature = "",
+ .result = "",
+ .handler = handler,
+ .offset = 0,
+ },
+ },
+ },
+ {
+ .type = _SD_BUS_VTABLE_END,
+ .flags = 0,
+ .x = { { 0 } },
+ }
+};