/* * * Embedded Linux library * * Copyright (C) 2011-2014 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include "util.h" #include "private.h" #include "useful.h" #include "dbus.h" #include "dbus-private.h" #include "gvariant-private.h" #define DBUS_MESSAGE_LITTLE_ENDIAN ('l') #define DBUS_MESSAGE_BIG_ENDIAN ('B') #define DBUS_MESSAGE_PROTOCOL_VERSION 1 #define DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED 0x01 #define DBUS_MESSAGE_FLAG_NO_AUTO_START 0x02 #define DBUS_MESSAGE_FIELD_PATH 1 #define DBUS_MESSAGE_FIELD_INTERFACE 2 #define DBUS_MESSAGE_FIELD_MEMBER 3 #define DBUS_MESSAGE_FIELD_ERROR_NAME 4 #define DBUS_MESSAGE_FIELD_REPLY_SERIAL 5 #define DBUS_MESSAGE_FIELD_DESTINATION 6 #define DBUS_MESSAGE_FIELD_SENDER 7 #define DBUS_MESSAGE_FIELD_SIGNATURE 8 #define DBUS_MESSAGE_FIELD_UNIX_FDS 9 #define DBUS_MAX_NESTING 32 struct l_dbus_message { int refcount; void *header; size_t header_size; size_t header_end; char *signature; void *body; size_t body_size; char *path; char *interface; char *member; char *error_name; uint32_t reply_serial; char *destination; char *sender; int fds[16]; uint32_t num_fds; bool sealed : 1; bool signature_free : 1; }; struct l_dbus_message_builder { struct l_dbus_message *message; struct dbus_builder *builder; struct builder_driver *driver; }; static inline bool _dbus_message_is_gvariant(struct l_dbus_message *msg) { struct dbus_header *hdr = msg->header; return hdr->version == 2; } void *_dbus_message_get_header(struct l_dbus_message *msg, size_t *out_size) { if (out_size) *out_size = msg->header_size; return msg->header; } void *_dbus_message_get_body(struct l_dbus_message *msg, size_t *out_size) { if (out_size) *out_size = msg->body_size; return msg->body; } /* Get a buffer containing the final message contents except the header */ void *_dbus_message_get_footer(struct l_dbus_message *msg, size_t *out_size) { size_t size; if (_dbus_message_is_gvariant(msg)) { size = _gvariant_message_finalize(msg->header_end, msg->body, msg->body_size, msg->signature); size -= msg->header_size; } else size = msg->body_size; if (out_size) *out_size = size; return msg->body; } int *_dbus_message_get_fds(struct l_dbus_message *msg, uint32_t *num_fds) { *num_fds = msg->num_fds; return msg->fds; } void _dbus_message_set_serial(struct l_dbus_message *msg, uint32_t serial) { struct dbus_header *hdr = msg->header; hdr->dbus1.serial = serial; } uint32_t _dbus_message_get_serial(struct l_dbus_message *msg) { struct dbus_header *hdr = msg->header; return hdr->dbus1.serial; } LIB_EXPORT bool l_dbus_message_set_no_reply(struct l_dbus_message *msg, bool on) { struct dbus_header *hdr; if (unlikely(!msg)) return false; hdr = msg->header; if (on) hdr->flags |= DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED; else hdr->flags &= ~DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED; return true; } LIB_EXPORT bool l_dbus_message_get_no_reply(struct l_dbus_message *msg) { struct dbus_header *hdr; if (unlikely(!msg)) return false; hdr = msg->header; if (hdr->flags & DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED) return true; return false; } LIB_EXPORT bool l_dbus_message_set_no_autostart(struct l_dbus_message *msg, bool on) { struct dbus_header *hdr; if (unlikely(!msg)) return false; hdr = msg->header; if (on) hdr->flags |= DBUS_MESSAGE_FLAG_NO_AUTO_START; else hdr->flags &= ~DBUS_MESSAGE_FLAG_NO_AUTO_START; return true; } LIB_EXPORT bool l_dbus_message_get_no_autostart(struct l_dbus_message *msg) { struct dbus_header *hdr; if (unlikely(!msg)) return false; hdr = msg->header; if (hdr->flags & DBUS_MESSAGE_FLAG_NO_AUTO_START) return true; return false; } static struct l_dbus_message *message_new_common(uint8_t type, uint8_t flags, uint8_t version) { struct l_dbus_message *message; struct dbus_header *hdr; message = l_new(struct l_dbus_message, 1); message->refcount = 1; /* * We allocate the header with the initial 12 bytes (up to the field * length) so that we can store the basic information here. For * GVariant we need 16 bytes. */ message->header_size = version == 1 ? 12 : 16; message->header_end = message->header_size; message->header = l_realloc(NULL, message->header_size); hdr = message->header; hdr->endian = DBUS_NATIVE_ENDIAN; hdr->message_type = type; hdr->flags = flags; hdr->version = version; return message; } struct l_dbus_message *_dbus_message_new_method_call(uint8_t version, const char *destination, const char *path, const char *interface, const char *method) { struct l_dbus_message *message; message = message_new_common(DBUS_MESSAGE_TYPE_METHOD_CALL, 0, version); message->destination = l_strdup(destination); message->path = l_strdup(path); message->interface = l_strdup(interface); message->member = l_strdup(method); return message; } LIB_EXPORT struct l_dbus_message *l_dbus_message_new_method_call( struct l_dbus *dbus, const char *destination, const char *path, const char *interface, const char *method) { uint8_t version; if (unlikely(!dbus)) return NULL; version = _dbus_get_version(dbus); return _dbus_message_new_method_call(version, destination, path, interface, method); } struct l_dbus_message *_dbus_message_new_signal(uint8_t version, const char *path, const char *interface, const char *name) { struct l_dbus_message *message; message = message_new_common(DBUS_MESSAGE_TYPE_SIGNAL, DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, version); message->path = l_strdup(path); message->interface = l_strdup(interface); message->member = l_strdup(name); return message; } LIB_EXPORT struct l_dbus_message *l_dbus_message_new_signal(struct l_dbus *dbus, const char *path, const char *interface, const char *name) { uint8_t version; if (unlikely(!dbus)) return NULL; version = _dbus_get_version(dbus); return _dbus_message_new_signal(version, path, interface, name); } LIB_EXPORT struct l_dbus_message *l_dbus_message_new_method_return( struct l_dbus_message *method_call) { struct l_dbus_message *message; struct dbus_header *hdr = method_call->header; const char *sender; message = message_new_common(DBUS_MESSAGE_TYPE_METHOD_RETURN, DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, hdr->version); if (!l_dbus_message_get_no_reply(method_call)) message->reply_serial = _dbus_message_get_serial(method_call); sender = l_dbus_message_get_sender(method_call); if (sender) message->destination = l_strdup(sender); return message; } struct l_dbus_message *_dbus_message_new_error(uint8_t version, uint32_t reply_serial, const char *destination, const char *name, const char *error) { struct l_dbus_message *reply; if (!_dbus_valid_interface(name)) return NULL; reply = message_new_common(DBUS_MESSAGE_TYPE_ERROR, DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, version); reply->error_name = l_strdup(name); reply->destination = l_strdup(destination); reply->reply_serial = reply_serial; if (!l_dbus_message_set_arguments(reply, "s", error)) { l_dbus_message_unref(reply); return NULL; } return reply; } LIB_EXPORT struct l_dbus_message *l_dbus_message_new_error_valist( struct l_dbus_message *method_call, const char *name, const char *format, va_list args) { char str[1024]; struct dbus_header *hdr = method_call->header; uint32_t reply_serial = 0; vsnprintf(str, sizeof(str), format, args); if (!l_dbus_message_get_no_reply(method_call)) reply_serial = _dbus_message_get_serial(method_call); return _dbus_message_new_error(hdr->version, reply_serial, l_dbus_message_get_sender(method_call), name, str); } LIB_EXPORT struct l_dbus_message *l_dbus_message_new_error( struct l_dbus_message *method_call, const char *name, const char *format, ...) { va_list args; struct l_dbus_message *reply; va_start(args, format); reply = l_dbus_message_new_error_valist(method_call, name, format, args); va_end(args); return reply; } LIB_EXPORT struct l_dbus_message *l_dbus_message_ref(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; __atomic_fetch_add(&message->refcount, 1, __ATOMIC_SEQ_CST); return message; } LIB_EXPORT void l_dbus_message_unref(struct l_dbus_message *message) { unsigned int i; if (unlikely(!message)) return; if (__atomic_sub_fetch(&message->refcount, 1, __ATOMIC_SEQ_CST)) return; for (i = 0; i < message->num_fds; i++) close(message->fds[i]); if (!message->sealed) { l_free(message->destination); l_free(message->path); l_free(message->interface); l_free(message->member); l_free(message->error_name); l_free(message->sender); } if (message->signature_free) l_free(message->signature); l_free(message->header); l_free(message->body); l_free(message); } const char *_dbus_message_get_nth_string_argument( struct l_dbus_message *message, int n) { struct l_dbus_message_iter iter; const char *signature, *value; void *body; size_t size; char type; bool (*skip_entry)(struct l_dbus_message_iter *); bool (*get_basic)(struct l_dbus_message_iter *, char, void *); signature = l_dbus_message_get_signature(message); body = _dbus_message_get_body(message, &size); if (!signature) return NULL; if (_dbus_message_is_gvariant(message)) { if (!_gvariant_iter_init(&iter, message, signature, NULL, body, size)) return NULL; skip_entry = _gvariant_iter_skip_entry; get_basic = _gvariant_iter_next_entry_basic; } else { _dbus1_iter_init(&iter, message, signature, NULL, body, size); skip_entry = _dbus1_iter_skip_entry; get_basic = _dbus1_iter_next_entry_basic; } while (n--) if (!skip_entry(&iter)) return NULL; if (!iter.sig_start) return NULL; type = iter.sig_start[iter.sig_pos]; if (!strchr("sog", type)) return NULL; if (!get_basic(&iter, type, &value)) return NULL; return value; } static bool message_iter_next_entry_valist(struct l_dbus_message_iter *orig, va_list args) { static const char *simple_types = "sogybnqiuxtd"; struct l_dbus_message_iter *iter = orig; const char *signature = orig->sig_start + orig->sig_pos; const char *end; struct l_dbus_message_iter *sub_iter; struct l_dbus_message_iter stack[DBUS_MAX_NESTING]; unsigned int indent = 0; uint32_t uint32_val; int fd; void *arg; bool (*get_basic)(struct l_dbus_message_iter *, char ,void *); bool (*enter_struct)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); bool (*enter_array)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); bool (*enter_variant)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); if (_dbus_message_is_gvariant(orig->message)) { get_basic = _gvariant_iter_next_entry_basic; enter_struct = _gvariant_iter_enter_struct; enter_array = _gvariant_iter_enter_array; enter_variant = _gvariant_iter_enter_variant; } else { get_basic = _dbus1_iter_next_entry_basic; enter_struct = _dbus1_iter_enter_struct; enter_array = _dbus1_iter_enter_array; enter_variant = _dbus1_iter_enter_variant; } while (signature < orig->sig_start + orig->sig_len) { if (strchr(simple_types, *signature)) { arg = va_arg(args, void *); if (!get_basic(iter, *signature, arg)) return false; signature += 1; continue; } switch (*signature) { case 'h': if (!get_basic(iter, 'h', &uint32_val)) return false; if (uint32_val < iter->message->num_fds) fd = fcntl(iter->message->fds[uint32_val], F_DUPFD_CLOEXEC, 3); else fd = -1; *va_arg(args, int *) = fd; signature += 1; break; case '(': case '{': signature += 1; indent += 1; if (indent > DBUS_MAX_NESTING) return false; if (!enter_struct(iter, &stack[indent - 1])) return false; iter = &stack[indent - 1]; break; case ')': case '}': /* * Sanity check in case of an unmatched paren/brace * that isn't caught elsewhere. */ if (unlikely(indent == 0)) return false; signature += 1; indent -= 1; if (indent == 0) iter = orig; else iter = &stack[indent - 1]; break; case 'a': sub_iter = va_arg(args, void *); if (!enter_array(iter, sub_iter)) return false; end = _dbus_signature_end(signature + 1); signature = end + 1; break; case 'v': sub_iter = va_arg(args, void *); if (!enter_variant(iter, sub_iter)) return false; signature += 1; break; default: return false; } } return true; } static inline bool message_iter_next_entry(struct l_dbus_message_iter *iter, ...) { va_list args; bool result; va_start(args, iter); result = message_iter_next_entry_valist(iter, args); va_end(args); return result; } static bool get_header_field_from_iter_valist(struct l_dbus_message *message, uint8_t type, char data_type, va_list args) { struct l_dbus_message_iter header; struct l_dbus_message_iter array, iter; uint8_t endian, message_type, flags, version; uint32_t body_length, serial; bool found; if (!message->sealed) return false; if (_dbus_message_is_gvariant(message)) { uint64_t field_type; if (!_gvariant_iter_init(&header, message, "a(tv)", NULL, message->header + 16, message->header_end - 16)) return false; if (!_gvariant_iter_enter_array(&header, &array)) return false; while ((found = message_iter_next_entry(&array, &field_type, &iter))) if (field_type == type) break; } else { uint8_t field_type; _dbus1_iter_init(&header, message, "yyyyuua(yv)", NULL, message->header, message->header_size); if (!message_iter_next_entry(&header, &endian, &message_type, &flags, &version, &body_length, &serial, &array)) return false; while ((found = message_iter_next_entry(&array, &field_type, &iter))) if (field_type == type) break; } if (!found) return false; if (iter.sig_start[iter.sig_pos] != data_type) return false; return message_iter_next_entry_valist(&iter, args); } static inline bool get_header_field(struct l_dbus_message *message, uint8_t type, int data_type, ...) { va_list args; bool result; va_start(args, data_type); result = get_header_field_from_iter_valist(message, type, data_type, args); va_end(args); return result; } static bool valid_header(const struct dbus_header *hdr) { if (hdr->endian != DBUS_MESSAGE_LITTLE_ENDIAN && hdr->endian != DBUS_MESSAGE_BIG_ENDIAN) return false; if (hdr->message_type < DBUS_MESSAGE_TYPE_METHOD_CALL || hdr->message_type > DBUS_MESSAGE_TYPE_SIGNAL) return false; if (hdr->version != 1 && hdr->version != 2) return false; if (hdr->version == 1) { if (hdr->dbus1.serial == 0) return false; } return true; } unsigned int _dbus_message_unix_fds_from_header(const void *data, size_t size) { struct l_dbus_message message; uint32_t unix_fds; message.header = (uint8_t *) data; message.header_size = size; message.body_size = 0; message.sealed = true; if (!get_header_field(&message, DBUS_MESSAGE_FIELD_UNIX_FDS, 'u', &unix_fds)) return 0; return unix_fds; } struct l_dbus_message *dbus_message_from_blob(const void *data, size_t size, int fds[], uint32_t num_fds) { const struct dbus_header *hdr = data; struct l_dbus_message *message; size_t body_pos; unsigned int i; if (unlikely(size < DBUS_HEADER_SIZE)) return NULL; message = l_new(struct l_dbus_message, 1); message->refcount = 1; if (hdr->version == 1) { message->header_size = align_len(DBUS_HEADER_SIZE + hdr->dbus1.field_length, 8); message->body_size = hdr->dbus1.body_length; if (message->header_size + message->body_size < size) goto free; body_pos = message->header_size; } else { struct l_dbus_message_iter iter; struct l_dbus_message_iter header, variant, body; /* * GVariant message structure as per * https://wiki.gnome.org/Projects/GLib/GDBus/Version2 * is "(yyyyuta{tv}v)". As noted this is equivalent to * some other types, this one lets us get iterators for * the header and the body in the fewest steps. */ if (!_gvariant_iter_init(&iter, message, "(yyyyuta{tv})v", NULL, data, size)) goto free; if (!_gvariant_iter_enter_struct(&iter, &header)) goto free; if (!_gvariant_iter_enter_variant(&iter, &variant)) goto free; if (!_gvariant_iter_enter_struct(&variant, &body)) goto free; message->header_size = align_len(header.len - header.pos, 8); message->body_size = body.len - body.pos; message->signature = l_strndup(body.sig_start + body.sig_pos, body.sig_len - body.sig_pos); message->signature_free = true; message->header_end = header.len; body_pos = body.data + body.pos - data; } message->header = l_malloc(message->header_size); message->body = l_malloc(message->body_size); memcpy(message->header, data, message->header_size); memcpy(message->body, data + body_pos, message->body_size); message->sealed = true; /* If the field is absent message->signature will remain NULL */ if (hdr->version == 1) get_header_field(message, DBUS_MESSAGE_FIELD_SIGNATURE, 'g', &message->signature); if (num_fds) { uint32_t unix_fds, orig_fds = num_fds; if (!get_header_field(message, DBUS_MESSAGE_FIELD_UNIX_FDS, 'u', &unix_fds)) goto free; if (num_fds > unix_fds) num_fds = unix_fds; if (num_fds > L_ARRAY_SIZE(message->fds)) num_fds = L_ARRAY_SIZE(message->fds); for (i = num_fds; i < orig_fds; i++) close(fds[i]); message->num_fds = num_fds; memcpy(message->fds, fds, num_fds * sizeof(int)); } return message; free: l_dbus_message_unref(message); return NULL; } struct l_dbus_message *dbus_message_build(void *header, size_t header_size, void *body, size_t body_size, int fds[], uint32_t num_fds) { const struct dbus_header *hdr = header; struct l_dbus_message *message; unsigned int i; if (unlikely(header_size < DBUS_HEADER_SIZE)) return NULL; if (unlikely(!valid_header(hdr))) return NULL; /* * With GVariant we need to know the signature, use * dbus_message_from_blob instead. */ if (unlikely(hdr->version != 1)) return NULL; message = l_new(struct l_dbus_message, 1); message->refcount = 1; message->header_size = header_size; message->header = header; message->body_size = body_size; message->body = body; message->sealed = true; if (num_fds) { uint32_t unix_fds, orig_fds = num_fds; if (!get_header_field(message, DBUS_MESSAGE_FIELD_UNIX_FDS, 'u', &unix_fds)) { l_free(message); return NULL; } if (num_fds > unix_fds) num_fds = unix_fds; if (num_fds > L_ARRAY_SIZE(message->fds)) num_fds = L_ARRAY_SIZE(message->fds); for (i = num_fds; i < orig_fds; i++) close(fds[i]); message->num_fds = num_fds; memcpy(message->fds, fds, num_fds * sizeof(int)); } /* If the field is absent message->signature will remain NULL */ get_header_field(message, DBUS_MESSAGE_FIELD_SIGNATURE, 'g', &message->signature); return message; } bool dbus_message_compare(struct l_dbus_message *message, const void *data, size_t size) { struct l_dbus_message *other; bool ret = false; other = dbus_message_from_blob(data, size, NULL, 0); if (message->signature) { if (!other->signature) goto done; if (strcmp(message->signature, other->signature)) goto done; } else { if (other->signature) goto done; } if (message->body_size != other->body_size) goto done; if (message->header_size != other->header_size) goto done; ret = !memcmp(message->body, other->body, message->body_size); done: l_dbus_message_unref(other); return ret; } struct builder_driver { bool (*append_basic)(struct dbus_builder *, char, const void *); bool (*enter_struct)(struct dbus_builder *, const char *); bool (*leave_struct)(struct dbus_builder *); bool (*enter_dict)(struct dbus_builder *, const char *); bool (*leave_dict)(struct dbus_builder *); bool (*enter_array)(struct dbus_builder *, const char *); bool (*leave_array)(struct dbus_builder *); bool (*enter_variant)(struct dbus_builder *, const char *); bool (*leave_variant)(struct dbus_builder *); char *(*finish)(struct dbus_builder *, void **, size_t *); bool (*mark)(struct dbus_builder *); bool (*rewind)(struct dbus_builder *); struct dbus_builder *(*new)(void *, size_t); void (*free)(struct dbus_builder *); }; static struct builder_driver dbus1_driver = { .append_basic = _dbus1_builder_append_basic, .enter_struct = _dbus1_builder_enter_struct, .leave_struct = _dbus1_builder_leave_struct, .enter_dict = _dbus1_builder_enter_dict, .leave_dict = _dbus1_builder_leave_dict, .enter_variant = _dbus1_builder_enter_variant, .leave_variant = _dbus1_builder_leave_variant, .enter_array = _dbus1_builder_enter_array, .leave_array = _dbus1_builder_leave_array, .finish = _dbus1_builder_finish, .mark = _dbus1_builder_mark, .rewind = _dbus1_builder_rewind, .new = _dbus1_builder_new, .free = _dbus1_builder_free, }; static struct builder_driver gvariant_driver = { .append_basic = _gvariant_builder_append_basic, .enter_struct = _gvariant_builder_enter_struct, .leave_struct = _gvariant_builder_leave_struct, .enter_dict = _gvariant_builder_enter_dict, .leave_dict = _gvariant_builder_leave_dict, .enter_variant = _gvariant_builder_enter_variant, .leave_variant = _gvariant_builder_leave_variant, .enter_array = _gvariant_builder_enter_array, .leave_array = _gvariant_builder_leave_array, .finish = _gvariant_builder_finish, .mark = _gvariant_builder_mark, .rewind = _gvariant_builder_rewind, .new = _gvariant_builder_new, .free = _gvariant_builder_free, }; static void add_field(struct dbus_builder *builder, struct builder_driver *driver, uint8_t field, const char *type, const void *value) { if (driver == &gvariant_driver) { uint64_t long_field = field; driver->enter_struct(builder, "tv"); driver->append_basic(builder, 't', &long_field); } else { driver->enter_struct(builder, "yv"); driver->append_basic(builder, 'y', &field); } driver->enter_variant(builder, type); driver->append_basic(builder, type[0], value); driver->leave_variant(builder); driver->leave_struct(builder); } static void build_header(struct l_dbus_message *message, const char *signature) { struct dbus_builder *builder; struct builder_driver *driver; char *generated_signature; size_t header_size; bool gvariant; gvariant = _dbus_message_is_gvariant(message); if (gvariant) driver = &gvariant_driver; else driver = &dbus1_driver; builder = driver->new(message->header, message->header_size); driver->enter_array(builder, gvariant ? "(tv)" : "(yv)"); if (message->path) { add_field(builder, driver, DBUS_MESSAGE_FIELD_PATH, "o", message->path); l_free(message->path); message->path = NULL; } if (message->member) { add_field(builder, driver, DBUS_MESSAGE_FIELD_MEMBER, "s", message->member); l_free(message->member); message->member = NULL; } if (message->interface) { add_field(builder, driver, DBUS_MESSAGE_FIELD_INTERFACE, "s", message->interface); l_free(message->interface); message->interface = NULL; } if (message->destination) { add_field(builder, driver, DBUS_MESSAGE_FIELD_DESTINATION, "s", message->destination); l_free(message->destination); message->destination = NULL; } if (message->error_name != 0) { add_field(builder, driver, DBUS_MESSAGE_FIELD_ERROR_NAME, "s", message->error_name); l_free(message->error_name); message->error_name = NULL; } if (message->reply_serial != 0) { if (gvariant) { uint64_t reply_serial = message->reply_serial; add_field(builder, driver, DBUS_MESSAGE_FIELD_REPLY_SERIAL, "t", &reply_serial); } else { add_field(builder, driver, DBUS_MESSAGE_FIELD_REPLY_SERIAL, "u", &message->reply_serial); } message->reply_serial = 0; } if (message->sender) { add_field(builder, driver, DBUS_MESSAGE_FIELD_SENDER, "s", message->sender); l_free(message->sender); message->sender = NULL; } if (signature[0] != '\0' && !gvariant) add_field(builder, driver, DBUS_MESSAGE_FIELD_SIGNATURE, "g", signature); if (message->num_fds) add_field(builder, driver, DBUS_MESSAGE_FIELD_UNIX_FDS, "u", &message->num_fds); driver->leave_array(builder); generated_signature = driver->finish(builder, &message->header, &header_size); l_free(generated_signature); driver->free(builder); if (!_dbus_message_is_gvariant(message)) { struct dbus_header *hdr = message->header; hdr->dbus1.body_length = message->body_size; } /* We must align the end of the header to an 8-byte boundary */ message->header_size = align_len(header_size, 8); message->header = l_realloc(message->header, message->header_size); memset(message->header + header_size, 0, message->header_size - header_size); message->header_end = header_size; } struct container { char type; const char *sig_start; const char *sig_end; unsigned int n_items; }; static bool append_arguments(struct l_dbus_message *message, const char *signature, va_list args) { struct l_dbus_message_builder *builder; bool ret; builder = l_dbus_message_builder_new(message); if (!builder) return false; if (!l_dbus_message_builder_append_from_valist(builder, signature, args)) { l_dbus_message_builder_destroy(builder); return false; } l_dbus_message_builder_finalize(builder); ret = strcmp(signature, builder->message->signature) == 0; l_dbus_message_builder_destroy(builder); return ret; } LIB_EXPORT bool l_dbus_message_get_error(struct l_dbus_message *message, const char **name, const char **text) { struct dbus_header *hdr; const char *str; if (unlikely(!message)) return false; hdr = message->header; if (hdr->message_type != DBUS_MESSAGE_TYPE_ERROR) return false; if (!message->signature) return false; if (message->signature[0] != 's') return false; str = _dbus_message_get_nth_string_argument(message, 0); if (!str) return false; if (!message->error_name) get_header_field(message, DBUS_MESSAGE_FIELD_ERROR_NAME, 's', &message->error_name); if (name) *name = message->error_name; if (text) *text = str; return true; } LIB_EXPORT bool l_dbus_message_is_error(struct l_dbus_message *message) { struct dbus_header *hdr; if (unlikely(!message)) return false; hdr = message->header; return hdr->message_type == DBUS_MESSAGE_TYPE_ERROR; } LIB_EXPORT bool l_dbus_message_get_arguments_valist( struct l_dbus_message *message, const char *signature, va_list args) { struct l_dbus_message_iter iter; if (unlikely(!message)) return false; if (!message->signature) { /* An empty signature is valid */ if (!signature || *signature == '\0') return true; return false; } if (!signature || strcmp(message->signature, signature)) return false; if (_dbus_message_is_gvariant(message)) { if (!_gvariant_iter_init(&iter, message, message->signature, NULL, message->body, message->body_size)) return false; } else _dbus1_iter_init(&iter, message, message->signature, NULL, message->body, message->body_size); return message_iter_next_entry_valist(&iter, args); } LIB_EXPORT bool l_dbus_message_get_arguments(struct l_dbus_message *message, const char *signature, ...) { va_list args; bool result; va_start(args, signature); result = l_dbus_message_get_arguments_valist(message, signature, args); va_end(args); return result; } LIB_EXPORT bool l_dbus_message_set_arguments(struct l_dbus_message *message, const char *signature, ...) { va_list args; bool result; if (unlikely(!message)) return false; if (unlikely(message->sealed)) return false; if (!signature) return true; va_start(args, signature); result = append_arguments(message, signature, args); va_end(args); return result; } LIB_EXPORT bool l_dbus_message_set_arguments_valist( struct l_dbus_message *message, const char *signature, va_list args) { bool result; if (unlikely(!message)) return false; if (!signature) return true; result = append_arguments(message, signature, args); return result; } LIB_EXPORT const char *l_dbus_message_get_path(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; if (!message->path && message->sealed) get_header_field(message, DBUS_MESSAGE_FIELD_PATH, 'o', &message->path); return message->path; } LIB_EXPORT const char *l_dbus_message_get_interface(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; if (!message->interface && message->sealed) get_header_field(message, DBUS_MESSAGE_FIELD_INTERFACE, 's', &message->interface); return message->interface; } LIB_EXPORT const char *l_dbus_message_get_member(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; if (!message->member && message->sealed) get_header_field(message, DBUS_MESSAGE_FIELD_MEMBER, 's', &message->member); return message->member; } LIB_EXPORT const char *l_dbus_message_get_destination(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; if (!message->destination && message->sealed) get_header_field(message, DBUS_MESSAGE_FIELD_DESTINATION, 's', &message->destination); return message->destination; } LIB_EXPORT const char *l_dbus_message_get_sender(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; if (!message->sender && message->sealed) get_header_field(message, DBUS_MESSAGE_FIELD_SENDER, 's', &message->sender); return message->sender; } LIB_EXPORT const char *l_dbus_message_get_signature( struct l_dbus_message *message) { if (unlikely(!message)) return NULL; return message->signature; } uint32_t _dbus_message_get_reply_serial(struct l_dbus_message *message) { if (unlikely(!message)) return 0; if (message->reply_serial == 0 && message->sealed) { if (_dbus_message_is_gvariant(message)) { uint64_t reply_serial = 0; get_header_field(message, DBUS_MESSAGE_FIELD_REPLY_SERIAL, 't', &reply_serial); message->reply_serial = reply_serial; } else get_header_field(message, DBUS_MESSAGE_FIELD_REPLY_SERIAL, 'u', &message->reply_serial); } return message->reply_serial; } enum dbus_message_type _dbus_message_get_type(struct l_dbus_message *message) { struct dbus_header *header; header = message->header; return header->message_type; } const char * _dbus_message_get_type_as_string(struct l_dbus_message *message) { struct dbus_header *header; header = message->header; switch (header->message_type) { case DBUS_MESSAGE_TYPE_METHOD_CALL: return "method_call"; case DBUS_MESSAGE_TYPE_METHOD_RETURN: return "method_return"; case DBUS_MESSAGE_TYPE_ERROR: return "error"; case DBUS_MESSAGE_TYPE_SIGNAL: return "signal"; } return NULL; } uint8_t _dbus_message_get_endian(struct l_dbus_message *message) { struct dbus_header *header = message->header; return header->endian; } uint8_t _dbus_message_get_version(struct l_dbus_message *message) { struct dbus_header *header = message->header; return header->version; } LIB_EXPORT bool l_dbus_message_iter_next_entry(struct l_dbus_message_iter *iter, ...) { va_list args; bool result; if (unlikely(!iter)) return false; va_start(args, iter); result = message_iter_next_entry_valist(iter, args); va_end(args); return result; } LIB_EXPORT bool l_dbus_message_iter_get_variant( struct l_dbus_message_iter *iter, const char *signature, ...) { va_list args; bool result; if (unlikely(!iter)) return false; if (!iter->sig_start || strlen(signature) != iter->sig_len || memcmp(iter->sig_start, signature, iter->sig_len)) return false; va_start(args, signature); result = message_iter_next_entry_valist(iter, args); va_end(args); return result; } LIB_EXPORT bool l_dbus_message_iter_get_fixed_array( struct l_dbus_message_iter *iter, void *out, uint32_t *n_elem) { if (unlikely(!iter)) return false; if (_dbus_message_is_gvariant(iter->message)) return false; return _dbus1_iter_get_fixed_array(iter, out, n_elem); } void _dbus_message_set_sender(struct l_dbus_message *message, const char *sender) { if (!_dbus_message_is_gvariant(message)) return; l_free(message->sender); message->sender = l_strdup(sender); } void _dbus_message_set_destination(struct l_dbus_message *message, const char *destination) { if (!_dbus_message_is_gvariant(message)) return; l_free(message->destination); message->destination = l_strdup(destination); } LIB_EXPORT struct l_dbus_message_builder *l_dbus_message_builder_new( struct l_dbus_message *message) { struct l_dbus_message_builder *ret; if (unlikely(!message)) return NULL; if (message->sealed) return NULL; ret = l_new(struct l_dbus_message_builder, 1); ret->message = l_dbus_message_ref(message); if (_dbus_message_is_gvariant(message)) ret->driver = &gvariant_driver; else ret->driver = &dbus1_driver; ret->builder = ret->driver->new(NULL, 0); return ret; } LIB_EXPORT void l_dbus_message_builder_destroy( struct l_dbus_message_builder *builder) { if (unlikely(!builder)) return; builder->driver->free(builder->builder); l_dbus_message_unref(builder->message); l_free(builder); } LIB_EXPORT bool l_dbus_message_builder_append_basic( struct l_dbus_message_builder *builder, char type, const void *value) { if (unlikely(!builder)) return false; return builder->driver->append_basic(builder->builder, type, value); } LIB_EXPORT bool l_dbus_message_builder_enter_container( struct l_dbus_message_builder *builder, char container_type, const char *signature) { if (unlikely(!builder)) return false; switch (container_type) { case DBUS_CONTAINER_TYPE_ARRAY: return builder->driver->enter_array(builder->builder, signature); case DBUS_CONTAINER_TYPE_DICT_ENTRY: return builder->driver->enter_dict(builder->builder, signature); case DBUS_CONTAINER_TYPE_STRUCT: return builder->driver->enter_struct(builder->builder, signature); case DBUS_CONTAINER_TYPE_VARIANT: return builder->driver->enter_variant(builder->builder, signature); default: break; } return false; } LIB_EXPORT bool l_dbus_message_builder_leave_container( struct l_dbus_message_builder *builder, char container_type) { if (unlikely(!builder)) return false; switch (container_type) { case DBUS_CONTAINER_TYPE_ARRAY: return builder->driver->leave_array(builder->builder); case DBUS_CONTAINER_TYPE_DICT_ENTRY: return builder->driver->leave_dict(builder->builder); case DBUS_CONTAINER_TYPE_STRUCT: return builder->driver->leave_struct(builder->builder); case DBUS_CONTAINER_TYPE_VARIANT: return builder->driver->leave_variant(builder->builder); default: break; } return false; } LIB_EXPORT bool l_dbus_message_builder_enter_struct( struct l_dbus_message_builder *builder, const char *signature) { return l_dbus_message_builder_enter_container(builder, 'r', signature); } LIB_EXPORT bool l_dbus_message_builder_leave_struct( struct l_dbus_message_builder *builder) { return l_dbus_message_builder_leave_container(builder, 'r'); } LIB_EXPORT bool l_dbus_message_builder_enter_array( struct l_dbus_message_builder *builder, const char *signature) { return l_dbus_message_builder_enter_container(builder, 'a', signature); } LIB_EXPORT bool l_dbus_message_builder_leave_array( struct l_dbus_message_builder *builder) { return l_dbus_message_builder_leave_container(builder, 'a'); } LIB_EXPORT bool l_dbus_message_builder_enter_dict( struct l_dbus_message_builder *builder, const char *signature) { return l_dbus_message_builder_enter_container(builder, 'e', signature); } LIB_EXPORT bool l_dbus_message_builder_leave_dict( struct l_dbus_message_builder *builder) { return l_dbus_message_builder_leave_container(builder, 'e'); } LIB_EXPORT bool l_dbus_message_builder_enter_variant( struct l_dbus_message_builder *builder, const char *signature) { return l_dbus_message_builder_enter_container(builder, 'v', signature); } LIB_EXPORT bool l_dbus_message_builder_leave_variant( struct l_dbus_message_builder *builder) { return l_dbus_message_builder_leave_container(builder, 'v'); } /** * l_dbus_message_builder_append_from_iter: * @builder: message builder to receive a new value * @from: message iterator to have its position moved by one value * * Copy one value from a message iterator onto a message builder. The * value's signature is also copied. * * Returns: whether the value was correctly copied. On failure both * the @from iterator and the @builder may have their positions * moved to somewhere within the new value if it's of a * container type. **/ LIB_EXPORT bool l_dbus_message_builder_append_from_iter( struct l_dbus_message_builder *builder, struct l_dbus_message_iter *from) { static const char *simple_types = "sogybnqiuxtd"; char type = from->sig_start[from->sig_pos]; char container_type; char signature[256]; struct l_dbus_message_iter iter; void *basic_ptr; uint64_t basic; uint32_t uint32_val; bool (*get_basic)(struct l_dbus_message_iter *, char, void *); bool (*enter_func)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); bool (*enter_struct)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); bool (*enter_array)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); bool (*enter_variant)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); if (_dbus_message_is_gvariant(from->message)) { get_basic = _gvariant_iter_next_entry_basic; enter_struct = _gvariant_iter_enter_struct; enter_array = _gvariant_iter_enter_array; enter_variant = _gvariant_iter_enter_variant; } else { get_basic = _dbus1_iter_next_entry_basic; enter_struct = _dbus1_iter_enter_struct; enter_array = _dbus1_iter_enter_array; enter_variant = _dbus1_iter_enter_variant; } if (strchr(simple_types, type)) { if (strchr("sog", type)) { if (!get_basic(from, type, &basic_ptr)) return false; } else { basic_ptr = &basic; if (!get_basic(from, type, basic_ptr)) return false; } if (!l_dbus_message_builder_append_basic(builder, type, basic_ptr)) return false; return true; } switch (type) { case 'h': if (!get_basic(from, type, &uint32_val)) return false; if (!l_dbus_message_builder_append_basic(builder, type, &builder->message->num_fds)) return false; if (builder->message->num_fds < L_ARRAY_SIZE(builder->message->fds)) { int fd; if (uint32_val < from->message->num_fds) fd = fcntl(from->message->fds[uint32_val], F_DUPFD_CLOEXEC, 3); else fd = -1; builder->message->fds[builder->message->num_fds++] = fd; } return true; case '(': enter_func = enter_struct; container_type = DBUS_CONTAINER_TYPE_STRUCT; break; case '{': enter_func = enter_struct; container_type = DBUS_CONTAINER_TYPE_DICT_ENTRY; break; case 'a': enter_func = enter_array; container_type = DBUS_CONTAINER_TYPE_ARRAY; break; case 'v': enter_func = enter_variant; container_type = DBUS_CONTAINER_TYPE_VARIANT; break; default: return false; } if (!enter_func(from, &iter)) return false; memcpy(signature, iter.sig_start, iter.sig_len); signature[iter.sig_len] = '\0'; if (!l_dbus_message_builder_enter_container(builder, container_type, signature)) return false; if (container_type == DBUS_CONTAINER_TYPE_ARRAY) while(l_dbus_message_builder_append_from_iter(builder, &iter)); else while (iter.sig_pos < iter.sig_len) if (!l_dbus_message_builder_append_from_iter(builder, &iter)) return false; if (!l_dbus_message_builder_leave_container(builder, container_type)) return false; return true; } LIB_EXPORT bool l_dbus_message_builder_append_from_valist( struct l_dbus_message_builder *builder, const char *signature, va_list args) { struct builder_driver *driver; char subsig[256]; const char *sigend; /* Nesting requires an extra stack entry for the base level */ struct container stack[DBUS_MAX_NESTING + 1]; unsigned int stack_index = 0; if (unlikely(!builder)) return false; driver = builder->driver; stack[stack_index].type = DBUS_CONTAINER_TYPE_STRUCT; stack[stack_index].sig_start = signature; stack[stack_index].sig_end = signature + strlen(signature); stack[stack_index].n_items = 0; while (stack_index != 0 || stack[0].sig_start != stack[0].sig_end) { const char *s; const char *str; if (stack[stack_index].type == DBUS_CONTAINER_TYPE_ARRAY && stack[stack_index].n_items == 0) stack[stack_index].sig_start = stack[stack_index].sig_end; if (stack[stack_index].sig_start == stack[stack_index].sig_end) { bool ret; /* * Sanity check in case of an invalid signature that * isn't caught elsewhere. */ if (unlikely(stack_index == 0)) return false; switch (stack[stack_index].type) { case DBUS_CONTAINER_TYPE_STRUCT: ret = driver->leave_struct(builder->builder); break; case DBUS_CONTAINER_TYPE_DICT_ENTRY: ret = driver->leave_dict(builder->builder); break; case DBUS_CONTAINER_TYPE_VARIANT: ret = driver->leave_variant(builder->builder); break; case DBUS_CONTAINER_TYPE_ARRAY: ret = driver->leave_array(builder->builder); break; default: ret = false; } if (!ret) return false; stack_index -= 1; continue; } s = stack[stack_index].sig_start; if (stack[stack_index].type != DBUS_CONTAINER_TYPE_ARRAY) stack[stack_index].sig_start += 1; else stack[stack_index].n_items -= 1; switch (*s) { case 'o': case 's': case 'g': str = va_arg(args, const char *); if (!driver->append_basic(builder->builder, *s, str)) return false; break; case 'b': case 'y': { uint8_t y = (uint8_t) va_arg(args, int); if (!driver->append_basic(builder->builder, *s, &y)) return false; break; } case 'n': case 'q': { uint16_t n = (uint16_t) va_arg(args, int); if (!driver->append_basic(builder->builder, *s, &n)) return false; break; } case 'i': case 'u': { uint32_t u = va_arg(args, uint32_t); if (!driver->append_basic(builder->builder, *s, &u)) return false; break; } case 'h': { int fd = va_arg(args, int); struct l_dbus_message *message = builder->message; if (!driver->append_basic(builder->builder, *s, &message->num_fds)) return false; if (message->num_fds < L_ARRAY_SIZE(message->fds)) message->fds[message->num_fds++] = fcntl(fd, F_DUPFD_CLOEXEC, 3); break; } case 'x': case 't': { uint64_t x = va_arg(args, uint64_t); if (!driver->append_basic(builder->builder, *s, &x)) return false; break; } case 'd': { double d = va_arg(args, double); if (!driver->append_basic(builder->builder, *s, &d)) return false; break; } case '(': case '{': if (stack_index == DBUS_MAX_NESTING) return false; sigend = _dbus_signature_end(s); memcpy(subsig, s + 1, sigend - s - 1); subsig[sigend - s - 1] = '\0'; if (*s == '(' && !driver->enter_struct(builder->builder, subsig)) return false; if (*s == '{' && !driver->enter_dict(builder->builder, subsig)) return false; if (stack[stack_index].type != DBUS_CONTAINER_TYPE_ARRAY) stack[stack_index].sig_start = sigend + 1; stack_index += 1; stack[stack_index].sig_start = s + 1; stack[stack_index].sig_end = sigend; stack[stack_index].n_items = 0; stack[stack_index].type = *s == '(' ? DBUS_CONTAINER_TYPE_STRUCT : DBUS_CONTAINER_TYPE_DICT_ENTRY; break; case 'v': if (stack_index == DBUS_MAX_NESTING) return false; str = va_arg(args, const char *); if (!str) return false; if (!driver->enter_variant(builder->builder, str)) return false; stack_index += 1; stack[stack_index].type = DBUS_CONTAINER_TYPE_VARIANT; stack[stack_index].sig_start = str; stack[stack_index].sig_end = str + strlen(str); stack[stack_index].n_items = 0; break; case 'a': if (stack_index == DBUS_MAX_NESTING) return false; sigend = _dbus_signature_end(s + 1) + 1; memcpy(subsig, s + 1, sigend - s - 1); subsig[sigend - s - 1] = '\0'; if (!driver->enter_array(builder->builder, subsig)) return false; if (stack[stack_index].type != DBUS_CONTAINER_TYPE_ARRAY) stack[stack_index].sig_start = sigend; stack_index += 1; stack[stack_index].sig_start = s + 1; stack[stack_index].sig_end = sigend; stack[stack_index].n_items = va_arg(args, unsigned int); stack[stack_index].type = DBUS_CONTAINER_TYPE_ARRAY; break; default: return false; } } return true; } LIB_EXPORT struct l_dbus_message *l_dbus_message_builder_finalize( struct l_dbus_message_builder *builder) { char *generated_signature; if (unlikely(!builder)) return NULL; generated_signature = builder->driver->finish(builder->builder, &builder->message->body, &builder->message->body_size); build_header(builder->message, generated_signature); builder->message->sealed = true; builder->message->signature = generated_signature; builder->message->signature_free = true; return builder->message; } bool _dbus_message_builder_mark(struct l_dbus_message_builder *builder) { if (unlikely(!builder)) return false; return builder->driver->mark(builder->builder); } bool _dbus_message_builder_rewind(struct l_dbus_message_builder *builder) { if (unlikely(!builder)) return false; return builder->driver->rewind(builder->builder); }