blob: d74a351e29c465c1612e15be56580f68efc8b551 [file] [log] [blame]
/* 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-gvariant.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 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);
free(c->offsets);
/* Move to previous container, but not if we are on root container */
if (m->n_containers > 0)
m->n_containers--;
}
static void message_reset_containers(sd_bus_message *m) {
assert(m);
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 align, 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 = ALIGN_TO(old_size, align);
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);
/* dbus1 only allows 8bit header field ids */
if (h > 0xFF)
return -EINVAL;
/* dbus1 doesn't allow strings over 32bit, let's enforce this
* globally, to not risk convertability */
l = strlen(s);
if (l > UINT32_MAX)
return -EINVAL;
/* Signature "(yv)" where the variant contains "s" */
if (BUS_MESSAGE_IS_GVARIANT(m)) {
/* (field id 64bit, ((string + NUL) + NUL + signature string 's') */
p = message_extend_fields(m, 8, 8 + l + 1 + 1 + 1, true);
if (!p)
return -ENOMEM;
*((uint64_t*) p) = h;
memcpy(p+8, s, l);
p[8+l] = 0;
p[8+l+1] = 0;
p[8+l+2] = type;
if (ret)
*ret = (char*) p + 8;
} else {
/* (field id byte + (signature length + signature 's' + NUL) + (string length + string + NUL)) */
p = message_extend_fields(m, 8, 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);
/* dbus1 only allows 8bit header field ids */
if (h > 0xFF)
return -EINVAL;
/* dbus1 doesn't allow signatures over 8bit, let's enforce
* this globally, to not risk convertability */
l = strlen(s);
if (l > SD_BUS_MAXIMUM_SIGNATURE_LENGTH)
return -EINVAL;
/* Signature "(yv)" where the variant contains "g" */
if (BUS_MESSAGE_IS_GVARIANT(m))
/* For gvariant the serialization is the same as for normal strings */
return message_append_field_string(m, h, 'g', s, ret);
else {
/* (field id byte + (signature length + signature 'g' + NUL) + (string length + string + NUL)) */
p = message_extend_fields(m, 8, 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);
/* dbus1 only allows 8bit header field ids */
if (h > 0xFF)
return -EINVAL;
if (BUS_MESSAGE_IS_GVARIANT(m)) {
/* (field id 64bit + ((value + NUL + signature string 'u') */
p = message_extend_fields(m, 8, 8 + 4 + 1 + 1, true);
if (!p)
return -ENOMEM;
*((uint64_t*) p) = h;
*((uint32_t*) (p + 8)) = x;
p[12] = 0;
p[13] = 'u';
} else {
/* (field id byte + (signature length + signature 'u' + NUL) + value) */
p = message_extend_fields(m, 8, 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_field_uint64(sd_bus_message *m, uint64_t h, uint64_t x) {
uint8_t *p;
assert(m);
/* dbus1 only allows 8bit header field ids */
if (h > 0xFF)
return -EINVAL;
if (BUS_MESSAGE_IS_GVARIANT(m)) {
/* (field id 64bit + ((value + NUL + signature string 't') */
p = message_extend_fields(m, 8, 8 + 8 + 1 + 1, true);
if (!p)
return -ENOMEM;
*((uint64_t*) p) = h;
*((uint64_t*) (p + 8)) = x;
p[16] = 0;
p[17] = 't';
} else {
/* (field id byte + (signature length + signature 't' + NUL) + 4 byte padding + value) */
p = message_extend_fields(m, 8, 4 + 4 + 8, false);
if (!p)
return -ENOMEM;
p[0] = (uint8_t) h;
p[1] = 1;
p[2] = 't';
p[3] = 0;
p[4] = 0;
p[5] = 0;
p[6] = 0;
p[7] = 0;
((uint64_t*) p)[1] = x;
}
return 0;
}
static int message_append_reply_cookie(sd_bus_message *m, uint64_t cookie) {
assert(m);
if (BUS_MESSAGE_IS_GVARIANT(m))
return message_append_field_uint64(m, BUS_MESSAGE_HEADER_REPLY_SERIAL, cookie);
else {
/* 64bit cookies are not supported on dbus1 */
if (cookie > 0xffffffffUL)
return -EOPNOTSUPP;
return message_append_field_uint32(m, BUS_MESSAGE_HEADER_REPLY_SERIAL, (uint32_t) cookie);
}
}
int bus_message_from_header(
sd_bus *bus,
void *header,
size_t header_accessible,
void *footer,
size_t footer_accessible,
size_t message_size,
int *fds,
size_t n_fds,
const char *label,
size_t extra,
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(header || header_accessible <= 0);
assert(footer || footer_accessible <= 0);
assert(fds || n_fds <= 0);
assert(ret);
if (header_accessible < sizeof(struct bus_header))
return -EBADMSG;
if (header_accessible > message_size)
return -EBADMSG;
if (footer_accessible > message_size)
return -EBADMSG;
h = header;
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)) + ALIGN(extra);
if (label) {
label_sz = strlen(label);
a += label_sz + 1;
}
m = malloc0(a);
if (!m)
return -ENOMEM;
m->sealed = true;
m->header = header;
m->header_accessible = header_accessible;
m->footer = footer;
m->footer_accessible = footer_accessible;
if (BUS_MESSAGE_IS_GVARIANT(m)) {
size_t ws;
if (h->dbus2.cookie == 0)
return -EBADMSG;
/* dbus2 derives the sizes from the message size and
the offset table at the end, since it is formatted as
gvariant "yyyyuta{tv}v". Since the message itself is a
structure with precisely to variable sized entries,
there's only one offset in the table, which marks the
end of the fields array. */
ws = bus_gvariant_determine_word_size(message_size, 0);
if (footer_accessible < ws)
return -EBADMSG;
m->fields_size = bus_gvariant_read_word_le((uint8_t*) footer + footer_accessible - ws, ws);
if (ALIGN8(m->fields_size) > message_size - ws)
return -EBADMSG;
if (m->fields_size < sizeof(struct bus_header))
return -EBADMSG;
m->fields_size -= sizeof(struct bus_header);
m->body_size = message_size - (sizeof(struct bus_header) + ALIGN8(m->fields_size));
} else {
if (h->dbus1.serial == 0)
return -EBADMSG;
/* dbus1 has the sizes in the header */
m->fields_size = BUS_MESSAGE_BSWAP32(m, h->dbus1.fields_size);
m->body_size = BUS_MESSAGE_BSWAP32(m, h->dbus1.body_size);
assert(message_size >= sizeof(struct bus_header));
if (m->fields_size > message_size - sizeof(struct bus_header) ||
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)) + ALIGN(extra);
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 = bus_message_from_header(
bus,
buffer, length, /* in this case the initial bytes and the final bytes are the same */
buffer, length,
length,
fds, n_fds,
label,
0, &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 = bus_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);
t->root_container.need_offsets = BUS_MESSAGE_IS_GVARIANT(t);
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);
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;
}
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 int message_add_offset(sd_bus_message *m, size_t offset) {
struct bus_container *c;
assert(m);
assert(BUS_MESSAGE_IS_GVARIANT(m));
/* Add offset to current container, unless this is the first
* item in it, which will have the 0 offset, which we can
* ignore. */
c = message_get_last_container(m);
if (!c->need_offsets)
return 0;
if (!GREEDY_REALLOC(c->offsets, c->n_offsets + 1))
return -ENOMEM;
c->offsets[c->n_offsets++] = offset;
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,
bool add_offset,
bool force_inline) {
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((size_t) 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) ||
(force_inline && m->body_end->size > MEMFD_MIN_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);
if (add_offset) {
r = message_add_offset(m, end_body);
if (r < 0) {
m->poisoned = true;
return NULL;
}
}
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, sizeof(int), m->n_fds + 1);
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;
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;
}
}
if (BUS_MESSAGE_IS_GVARIANT(m)) {
uint8_t u8;
uint32_t u32;
switch (type) {
case SD_BUS_TYPE_SIGNATURE:
case SD_BUS_TYPE_STRING:
p = strempty(p);
_fallthrough_;
case SD_BUS_TYPE_OBJECT_PATH:
if (!p)
return -EINVAL;
align = 1;
sz = strlen(p) + 1;
break;
case SD_BUS_TYPE_BOOLEAN:
u8 = p && *(int*) p;
p = &u8;
align = sz = 1;
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_gvariant_get_alignment(CHAR_TO_STR(type));
sz = bus_gvariant_get_size(CHAR_TO_STR(type));
break;
}
assert(align > 0);
assert(sz > 0);
a = message_extend_body(m, align, sz, true, false);
if (!a)
return -ENOMEM;
memcpy(a, p, sz);
if (stored)
*stored = (const uint8_t*) a;
} else {
uint32_t u32;
switch (type) {
case SD_BUS_TYPE_STRING:
/* To make things easy we'll serialize a NULL string
* into the empty string */
p = strempty(p);
_fallthrough_;
case SD_BUS_TYPE_OBJECT_PATH:
if (!p)
return -EINVAL;
align = 4;
sz = 4 + strlen(p) + 1;
break;
case SD_BUS_TYPE_SIGNATURE:
p = strempty(p);
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, false, false);
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;
}
}
if (BUS_MESSAGE_IS_GVARIANT(m)) {
a = message_extend_body(m, 1, size + 1, true, false);
if (!a)
return -ENOMEM;
*s = a;
} else {
a = message_extend_body(m, 4, 4 + size + 1, false, false);
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,
bool *need_offsets) {
unsigned nindex;
int alignment, r;
assert(m);
assert(c);
assert(contents);
assert(array_size);
assert(begin);
assert(need_offsets);
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;
}
if (BUS_MESSAGE_IS_GVARIANT(m)) {
alignment = bus_gvariant_get_alignment(contents);
if (alignment < 0)
return alignment;
/* Add alignment padding and add to offset list */
if (!message_extend_body(m, alignment, 0, false, false))
return -ENOMEM;
r = bus_gvariant_is_fixed_size(contents);
if (r < 0)
return r;
*begin = m->body_size;
*need_offsets = r == 0;
} else {
void *a, *op;
size_t os;
struct bus_body_part *o;
alignment = bus_type_get_alignment(contents[0]);
if (alignment < 0)
return alignment;
a = message_extend_body(m, 4, 4, false, false);
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, false, false))
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) {
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;
}
}
if (BUS_MESSAGE_IS_GVARIANT(m)) {
/* Variants are always aligned to 8 */
if (!message_extend_body(m, 8, 0, false, false))
return -ENOMEM;
} else {
size_t l;
void *a;
l = strlen(contents);
a = message_extend_body(m, 1, 1 + l + 1, false, false);
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,
bool *need_offsets) {
size_t nindex;
int r;
assert(m);
assert(c);
assert(contents);
assert(begin);
assert(need_offsets);
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;
}
if (BUS_MESSAGE_IS_GVARIANT(m)) {
int alignment;
alignment = bus_gvariant_get_alignment(contents);
if (alignment < 0)
return alignment;
if (!message_extend_body(m, alignment, 0, false, false))
return -ENOMEM;
r = bus_gvariant_is_fixed_size(contents);
if (r < 0)
return r;
*begin = m->body_size;
*need_offsets = r == 0;
} else {
/* Align contents to 8 byte boundary */
if (!message_extend_body(m, 8, 0, false, false))
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,
bool *need_offsets) {
int r;
assert(m);
assert(c);
assert(contents);
assert(begin);
assert(need_offsets);
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;
if (BUS_MESSAGE_IS_GVARIANT(m)) {
int alignment;
alignment = bus_gvariant_get_alignment(contents);
if (alignment < 0)
return alignment;
if (!message_extend_body(m, alignment, 0, false, false))
return -ENOMEM;
r = bus_gvariant_is_fixed_size(contents);
if (r < 0)
return r;
*begin = m->body_size;
*need_offsets = r == 0;
} else {
/* Align contents to 8 byte boundary */
if (!message_extend_body(m, 8, 0, false, false))
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;
bool need_offsets = false;
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, &need_offsets);
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, &need_offsets);
else if (type == SD_BUS_TYPE_DICT_ENTRY)
r = bus_message_open_dict_entry(m, c, contents, &begin, &need_offsets);
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,
.need_offsets = need_offsets,
};
return 0;
}
static int bus_message_close_array(sd_bus_message *m, struct bus_container *c) {
assert(m);
assert(c);
if (!BUS_MESSAGE_IS_GVARIANT(m))
return 0;
if (c->need_offsets) {
size_t payload, sz, i;
uint8_t *a;
/* Variable-width arrays */
payload = c->n_offsets > 0 ? c->offsets[c->n_offsets-1] - c->begin : 0;
sz = bus_gvariant_determine_word_size(payload, c->n_offsets);
a = message_extend_body(m, 1, sz * c->n_offsets, true, false);
if (!a)
return -ENOMEM;
for (i = 0; i < c->n_offsets; i++)
bus_gvariant_write_word_le(a + sz*i, sz, c->offsets[i] - c->begin);
} else {
void *a;
/* Fixed-width or empty arrays */
a = message_extend_body(m, 1, 0, true, false); /* let's add offset to parent */
if (!a)
return -ENOMEM;
}
return 0;
}
static int bus_message_close_variant(sd_bus_message *m, struct bus_container *c) {
uint8_t *a;
size_t l;
assert(m);
assert(c);
assert(c->signature);
if (!BUS_MESSAGE_IS_GVARIANT(m))
return 0;
l = strlen(c->signature);
a = message_extend_body(m, 1, 1 + l, true, false);
if (!a)
return -ENOMEM;
a[0] = 0;
memcpy(a+1, c->signature, l);
return 0;
}
static int bus_message_close_struct(sd_bus_message *m, struct bus_container *c, bool add_offset) {
bool fixed_size = true;
size_t n_variable = 0;
unsigned i = 0;
const char *p;
uint8_t *a;
int r;
assert(m);
assert(c);
if (!BUS_MESSAGE_IS_GVARIANT(m))
return 0;
p = strempty(c->signature);
while (*p != 0) {
size_t n;
r = signature_element_length(p, &n);
if (r < 0)
return r;
else {
char t[n+1];
memcpy(t, p, n);
t[n] = 0;
r = bus_gvariant_is_fixed_size(t);
if (r < 0)
return r;
}
assert(!c->need_offsets || i <= c->n_offsets);
/* We need to add an offset for each item that has a
* variable size and that is not the last one in the
* list */
if (r == 0)
fixed_size = false;
if (r == 0 && p[n] != 0)
n_variable++;
i++;
p += n;
}
assert(!c->need_offsets || i == c->n_offsets);
assert(c->need_offsets || n_variable == 0);
if (isempty(c->signature)) {
/* The unary type is encoded as fixed 1 byte padding */
a = message_extend_body(m, 1, 1, add_offset, false);
if (!a)
return -ENOMEM;
*a = 0;
} else if (n_variable <= 0) {
int alignment = 1;
/* Structures with fixed-size members only have to be
* fixed-size themselves. But gvariant requires all fixed-size
* elements to be sized a multiple of their alignment. Hence,
* we must *always* add final padding after the last member so
* the overall size of the structure is properly aligned. */
if (fixed_size)
alignment = bus_gvariant_get_alignment(strempty(c->signature));
assert(alignment > 0);
a = message_extend_body(m, alignment, 0, add_offset, false);
if (!a)
return -ENOMEM;
} else {
size_t sz;
unsigned j;
assert(c->offsets[c->n_offsets-1] == m->body_size);
sz = bus_gvariant_determine_word_size(m->body_size - c->begin, n_variable);
a = message_extend_body(m, 1, sz * n_variable, add_offset, false);
if (!a)
return -ENOMEM;
p = strempty(c->signature);
for (i = 0, j = 0; i < c->n_offsets; i++) {
unsigned k;
size_t n;
r = signature_element_length(p, &n);
if (r < 0)
return r;
else {
char t[n+1];
memcpy(t, p, n);
t[n] = 0;
p += n;
r = bus_gvariant_is_fixed_size(t);
if (r < 0)
return r;
if (r > 0 || p[0] == 0)
continue;
}
k = n_variable - 1 - j;
bus_gvariant_write_word_le(a + k * sz, sz, c->offsets[i] - c->begin);
j++;
}
}
return 0;
}
_public_ int sd_bus_message_close_container(sd_bus_message *m) {
struct bus_container *c;
int r;
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--;
if (c->enclosing == SD_BUS_TYPE_ARRAY)
r = bus_message_close_array(m, c);
else if (c->enclosing == SD_BUS_TYPE_VARIANT)
r = bus_message_close_variant(m, c);
else if (IN_SET(c->enclosing, SD_BUS_TYPE_STRUCT, SD_BUS_TYPE_DICT_ENTRY))
r = bus_message_close_struct(m, c, true);
else
assert_not_reached("Unknown container type");
free(c->signature);
free(c->offsets);
return r;
}
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;
{
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);
/* alignment and size of the trivial types (except bool) is
* identical for gvariant and dbus1 marshalling */
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, false, false);
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, false, false);
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;
}
}
if (!BUS_MESSAGE_IS_GVARIANT(m)) {
a = message_extend_body(m, 4, 4, false, false);
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 (BUS_MESSAGE_IS_GVARIANT(m)) {
r = message_add_offset(m, m->body_size);
if (r < 0) {
m->poisoned = true;
return -ENOMEM;
}
}
if (c->enclosing != SD_BUS_TYPE_ARRAY)
c->index++;
return 0;
}
_public_ int sd_bus_message_append_strv(sd_bus_message *m, char **l) {
char **i;
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 (at least on gvariant). Remember
this position, so that during parsing we know where to
put the outer container end. */
m->user_body_size = m->body_size;
if (BUS_MESSAGE_IS_GVARIANT(m)) {
const char *signature;
size_t sz, l;
void *d;
/* Add offset table to end of fields array */
if (m->n_header_offsets >= 1) {
uint8_t *a;
unsigned i;
assert(m->fields_size == m->header_offsets[m->n_header_offsets-1]);
sz = bus_gvariant_determine_word_size(m->fields_size, m->n_header_offsets);
a = message_extend_fields(m, 1, sz * m->n_header_offsets, false);
if (!a)
return -ENOMEM;
for (i = 0; i < m->n_header_offsets; i++)
bus_gvariant_write_word_le(a + sz*i, sz, m->header_offsets[i]);
}
/* Add gvariant NUL byte plus signature to the end of
* the body, followed by the final offset pointing to
* the end of the fields array */
signature = strempty(m->root_container.signature);
l = strlen(signature);
sz = bus_gvariant_determine_word_size(sizeof(struct bus_header) + ALIGN8(m->fields_size) + m->body_size + 1 + l + 2, 1);
d = message_extend_body(m, 1, 1 + l + 2 + sz, false, true);
if (!d)
return -ENOMEM;
*(uint8_t*) d = 0;
*((uint8_t*) d + 1) = SD_BUS_TYPE_STRUCT_BEGIN;
memcpy((uint8_t*) d + 2, signature, l);
*((uint8_t*) d + 1 + l + 1) = SD_BUS_TYPE_STRUCT_END;
bus_gvariant_write_word_le((uint8_t*) d + 1 + l + 2, sz, sizeof(struct bus_header) + m->fields_size);
m->footer = d;
m->footer_accessible = 1 + l + 2 + sz;
} else {
m->header->dbus1.fields_size = m->fields_size;
m->header->dbus1.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 > 0xffffffffULL &&
!BUS_MESSAGE_IS_GVARIANT(m))
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 gvariant marshalling is used we need to close the body structure */
r = bus_message_close_struct(m, &m->root_container, false);
if (r < 0)