diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:35:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:35:18 +0000 |
commit | b750101eb236130cf056c675997decbac904cc49 (patch) | |
tree | a5df1a06754bdd014cb975c051c83b01c9a97532 /src/libsystemd/sd-bus | |
parent | Initial commit. (diff) | |
download | systemd-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 '')
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, ¶m); + 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, ¶m); + 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, ©, "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 } }, + } +}; |