diff options
Diffstat (limited to 'src/3rdparty/libdbus/dbus')
122 files changed, 72822 insertions, 0 deletions
diff --git a/src/3rdparty/libdbus/dbus/dbus-address.c b/src/3rdparty/libdbus/dbus/dbus-address.c new file mode 100644 index 00000000..1093d7d1 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-address.c @@ -0,0 +1,654 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-address.c Server address parser. + * + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2004-2007 Red Hat, Inc. + * Copyright (C) 2007 Ralf Habacker + * Copyright (C) 2013 Chengwei Yang / Intel + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-address.h" +#include "dbus-internals.h" +#include "dbus-list.h" +#include "dbus-string.h" +#include "dbus-protocol.h" +#include <dbus/dbus-test-tap.h> + +/** + * @defgroup DBusAddressInternals Address parsing + * @ingroup DBusInternals + * @brief Implementation of parsing addresses of D-Bus servers. + * + * @{ + */ + +/** + * Internals of DBusAddressEntry + */ +struct DBusAddressEntry +{ + DBusString method; /**< The address type (unix, tcp, etc.) */ + + DBusList *keys; /**< List of keys */ + DBusList *values; /**< List of values */ +}; + + +/** + * + * Sets #DBUS_ERROR_BAD_ADDRESS. + * If address_problem_type and address_problem_field are not #NULL, + * sets an error message about how the field is no good. Otherwise, sets + * address_problem_other as the error message. + * + * @param error the error to set + * @param address_problem_type the address type of the bad address or #NULL + * @param address_problem_field the missing field of the bad address or #NULL + * @param address_problem_other any other error message or #NULL + */ +void +_dbus_set_bad_address (DBusError *error, + const char *address_problem_type, + const char *address_problem_field, + const char *address_problem_other) +{ + if (address_problem_type != NULL) + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "Server address of type %s was missing argument %s", + address_problem_type, address_problem_field); + else + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "Could not parse server address: %s", + address_problem_other); +} + +/** + * #TRUE if the byte need not be escaped when found in a dbus address. + * All other bytes are required to be escaped in a valid address. + */ +#define _DBUS_ADDRESS_OPTIONALLY_ESCAPED_BYTE(b) \ + (((b) >= 'a' && (b) <= 'z') || \ + ((b) >= 'A' && (b) <= 'Z') || \ + ((b) >= '0' && (b) <= '9') || \ + (b) == '-' || \ + (b) == '_' || \ + (b) == '/' || \ + (b) == '\\' || \ + (b) == '*' || \ + (b) == '.') + +/** + * Appends an escaped version of one string to another string, + * using the D-Bus address escaping mechanism + * + * @param escaped the string to append to + * @param unescaped the string to escape + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_address_append_escaped (DBusString *escaped, + const DBusString *unescaped) +{ + const unsigned char *p; + const unsigned char *end; + dbus_bool_t ret; + int orig_len; + + ret = FALSE; + + orig_len = _dbus_string_get_length (escaped); + p = _dbus_string_get_const_udata (unescaped); + end = p + _dbus_string_get_length (unescaped); + while (p != end) + { + if (_DBUS_ADDRESS_OPTIONALLY_ESCAPED_BYTE (*p)) + { + if (!_dbus_string_append_byte (escaped, *p)) + goto out; + } + else + { + if (!_dbus_string_append_byte (escaped, '%')) + goto out; + if (!_dbus_string_append_byte_as_hex (escaped, *p)) + goto out; + } + + ++p; + } + + ret = TRUE; + + out: + if (!ret) + _dbus_string_set_length (escaped, orig_len); + return ret; +} + +/** @} */ /* End of internals */ + +static void +dbus_address_entry_free (DBusAddressEntry *entry) +{ + DBusList *link; + + _dbus_string_free (&entry->method); + + link = _dbus_list_get_first_link (&entry->keys); + while (link != NULL) + { + _dbus_string_free (link->data); + dbus_free (link->data); + + link = _dbus_list_get_next_link (&entry->keys, link); + } + _dbus_list_clear (&entry->keys); + + link = _dbus_list_get_first_link (&entry->values); + while (link != NULL) + { + _dbus_string_free (link->data); + dbus_free (link->data); + + link = _dbus_list_get_next_link (&entry->values, link); + } + _dbus_list_clear (&entry->values); + + dbus_free (entry); +} + +/** + * @defgroup DBusAddress Address parsing + * @ingroup DBus + * @brief Parsing addresses of D-Bus servers. + * + * @{ + */ + +/** + * Frees a #NULL-terminated array of address entries. + * + * @param entries the array. + */ +void +dbus_address_entries_free (DBusAddressEntry **entries) +{ + int i; + + for (i = 0; entries[i] != NULL; i++) + dbus_address_entry_free (entries[i]); + dbus_free (entries); +} + +static DBusAddressEntry * +create_entry (void) +{ + DBusAddressEntry *entry; + + entry = dbus_new0 (DBusAddressEntry, 1); + + if (entry == NULL) + return NULL; + + if (!_dbus_string_init (&entry->method)) + { + dbus_free (entry); + return NULL; + } + + return entry; +} + +/** + * Returns the method string of an address entry. For example, given + * the address entry "tcp:host=example.com" it would return the string + * "tcp" + * + * @param entry the entry. + * @returns a string describing the method. This string + * must not be freed. + */ +const char * +dbus_address_entry_get_method (DBusAddressEntry *entry) +{ + return _dbus_string_get_const_data (&entry->method); +} + +/** + * Returns a value from a key of an entry. For example, + * given the address "tcp:host=example.com,port=8073" if you asked + * for the key "host" you would get the value "example.com" + * + * The returned value is already unescaped. + * + * @param entry the entry. + * @param key the key. + * @returns the key value. This string must not be freed. + */ +const char * +dbus_address_entry_get_value (DBusAddressEntry *entry, + const char *key) +{ + DBusList *values, *keys; + + keys = _dbus_list_get_first_link (&entry->keys); + values = _dbus_list_get_first_link (&entry->values); + + while (keys != NULL) + { + _dbus_assert (values != NULL); + + if (_dbus_string_equal_c_str (keys->data, key)) + return _dbus_string_get_const_data (values->data); + + keys = _dbus_list_get_next_link (&entry->keys, keys); + values = _dbus_list_get_next_link (&entry->values, values); + } + + return NULL; +} + +static dbus_bool_t +append_unescaped_value (DBusString *unescaped, + const DBusString *escaped, + int escaped_start, + int escaped_len, + DBusError *error) +{ + const char *p; + const char *end; + dbus_bool_t ret; + + ret = FALSE; + + p = _dbus_string_get_const_data (escaped) + escaped_start; + end = p + escaped_len; + while (p != end) + { + if (_DBUS_ADDRESS_OPTIONALLY_ESCAPED_BYTE (*p)) + { + if (!_dbus_string_append_byte (unescaped, *p)) + goto out; + } + else if (*p == '%') + { + /* Efficiency is king */ + char buf[3]; + DBusString hex; + int hex_end; + + ++p; + + if ((p + 2) > end) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "In D-Bus address, percent character was not followed by two hex digits"); + goto out; + } + + buf[0] = *p; + ++p; + buf[1] = *p; + buf[2] = '\0'; + + _dbus_string_init_const (&hex, buf); + + if (!_dbus_string_hex_decode (&hex, 0, &hex_end, + unescaped, + _dbus_string_get_length (unescaped))) + goto out; + + if (hex_end != 2) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "In D-Bus address, percent character was followed by characters other than hex digits"); + goto out; + } + } + else + { + /* Error, should have been escaped */ + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "In D-Bus address, character '%c' should have been escaped\n", + *p); + goto out; + } + + ++p; + } + + ret = TRUE; + + out: + if (!ret && error && !dbus_error_is_set (error)) + _DBUS_SET_OOM (error); + + _dbus_assert (ret || error == NULL || dbus_error_is_set (error)); + + return ret; +} + +/** + * Parses an address string of the form: + * + * method:key=value,key=value;method:key=value + * + * See the D-Bus specification for complete docs on the format. + * + * When connecting to an address, the first address entries + * in the semicolon-separated list should be tried first. + * + * @param address the address. + * @param entry_result return location to an array of entries. + * @param array_len return location for array length. + * @param error address where an error can be returned. + * @returns #TRUE on success, #FALSE otherwise. + */ +dbus_bool_t +dbus_parse_address (const char *address, + DBusAddressEntry ***entry_result, + int *array_len, + DBusError *error) +{ + DBusString str; + int pos, end_pos, len, i; + DBusList *entries, *link; + DBusAddressEntry **entry_array; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_string_init_const (&str, address); + + entries = NULL; + pos = 0; + len = _dbus_string_get_length (&str); + + if (len == 0) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "Empty address '%s'", address); + goto error; + } + + while (pos < len) + { + DBusAddressEntry *entry; + + int found_pos; + + entry = create_entry (); + if (!entry) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + + goto error; + } + + /* Append the entry */ + if (!_dbus_list_append (&entries, entry)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + dbus_address_entry_free (entry); + goto error; + } + + /* Look for a semi-colon */ + if (!_dbus_string_find (&str, pos, ";", &end_pos)) + end_pos = len; + + /* Look for the colon : */ + if (!_dbus_string_find_to (&str, pos, end_pos, ":", &found_pos)) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, "Address does not contain a colon"); + goto error; + } + + if (!_dbus_string_copy_len (&str, pos, found_pos - pos, &entry->method, 0)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto error; + } + + pos = found_pos + 1; + + while (pos < end_pos) + { + int comma_pos, equals_pos; + + if (!_dbus_string_find_to (&str, pos, end_pos, ",", &comma_pos)) + comma_pos = end_pos; + + if (!_dbus_string_find_to (&str, pos, comma_pos, "=", &equals_pos) || + equals_pos == pos || equals_pos + 1 == comma_pos) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "'=' character not found or has no value following it"); + goto error; + } + else + { + DBusString *key; + DBusString *value; + + key = dbus_new0 (DBusString, 1); + + if (!key) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto error; + } + + value = dbus_new0 (DBusString, 1); + if (!value) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + dbus_free (key); + goto error; + } + + if (!_dbus_string_init (key)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + dbus_free (key); + dbus_free (value); + + goto error; + } + + if (!_dbus_string_init (value)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (key); + + dbus_free (key); + dbus_free (value); + goto error; + } + + if (!_dbus_string_copy_len (&str, pos, equals_pos - pos, key, 0)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (key); + _dbus_string_free (value); + + dbus_free (key); + dbus_free (value); + goto error; + } + + if (!append_unescaped_value (value, &str, equals_pos + 1, + comma_pos - equals_pos - 1, error)) + { + _dbus_assert (error == NULL || dbus_error_is_set (error)); + _dbus_string_free (key); + _dbus_string_free (value); + + dbus_free (key); + dbus_free (value); + goto error; + } + + if (!_dbus_list_append (&entry->keys, key)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (key); + _dbus_string_free (value); + + dbus_free (key); + dbus_free (value); + goto error; + } + + if (!_dbus_list_append (&entry->values, value)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (value); + + dbus_free (value); + goto error; + } + } + + pos = comma_pos + 1; + } + + pos = end_pos + 1; + } + + *array_len = _dbus_list_get_length (&entries); + + entry_array = dbus_new (DBusAddressEntry *, *array_len + 1); + + if (!entry_array) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + + goto error; + } + + entry_array [*array_len] = NULL; + + link = _dbus_list_get_first_link (&entries); + i = 0; + while (link != NULL) + { + entry_array[i] = link->data; + i++; + link = _dbus_list_get_next_link (&entries, link); + } + + _dbus_list_clear (&entries); + *entry_result = entry_array; + + return TRUE; + + error: + + link = _dbus_list_get_first_link (&entries); + while (link != NULL) + { + dbus_address_entry_free (link->data); + link = _dbus_list_get_next_link (&entries, link); + } + + _dbus_list_clear (&entries); + + return FALSE; + +} + +/** + * Escapes the given string as a value in a key=value pair + * for a D-Bus address. + * + * @param value the unescaped value + * @returns newly-allocated escaped value or #NULL if no memory + */ +char* +dbus_address_escape_value (const char *value) +{ + DBusString escaped; + DBusString unescaped; + char *ret; + + ret = NULL; + + _dbus_string_init_const (&unescaped, value); + + if (!_dbus_string_init (&escaped)) + return NULL; + + if (!_dbus_address_append_escaped (&escaped, &unescaped)) + goto out; + + if (!_dbus_string_steal_data (&escaped, &ret)) + goto out; + + out: + _dbus_string_free (&escaped); + return ret; +} + +/** + * Unescapes the given string as a value in a key=value pair + * for a D-Bus address. Note that dbus_address_entry_get_value() + * returns an already-unescaped value. + * + * @param value the escaped value + * @param error error to set if the unescaping fails + * @returns newly-allocated unescaped value or #NULL if no memory + */ +char* +dbus_address_unescape_value (const char *value, + DBusError *error) +{ + DBusString unescaped; + DBusString escaped; + char *ret; + + ret = NULL; + + _dbus_string_init_const (&escaped, value); + + if (!_dbus_string_init (&unescaped)) + return NULL; + + if (!append_unescaped_value (&unescaped, &escaped, + 0, _dbus_string_get_length (&escaped), + error)) + goto out; + + if (!_dbus_string_steal_data (&unescaped, &ret)) + goto out; + + out: + if (ret == NULL && error && !dbus_error_is_set (error)) + _DBUS_SET_OOM (error); + + _dbus_assert (ret != NULL || error == NULL || dbus_error_is_set (error)); + + _dbus_string_free (&unescaped); + return ret; +} + +/** @} */ /* End of public API */ diff --git a/src/3rdparty/libdbus/dbus/dbus-address.h b/src/3rdparty/libdbus/dbus/dbus-address.h new file mode 100644 index 00000000..c0303430 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-address.h @@ -0,0 +1,87 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-address.h Server address parser. + * + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_ADDRESS_H +#define DBUS_ADDRESS_H + +#include <dbus/dbus-types.h> +#include <dbus/dbus-errors.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusAddress + * @{ + */ + +/** Opaque type representing one of the semicolon-separated items in an address */ +typedef struct DBusAddressEntry DBusAddressEntry; + +DBUS_EXPORT +dbus_bool_t dbus_parse_address (const char *address, + DBusAddressEntry ***entry_result, + int *array_len, + DBusError *error); +DBUS_EXPORT +const char *dbus_address_entry_get_value (DBusAddressEntry *entry, + const char *key); +DBUS_EXPORT +const char *dbus_address_entry_get_method (DBusAddressEntry *entry); +DBUS_EXPORT +void dbus_address_entries_free (DBusAddressEntry **entries); + +DBUS_EXPORT +char* dbus_address_escape_value (const char *value); +DBUS_EXPORT +char* dbus_address_unescape_value (const char *value, + DBusError *error); + +/** + * Clear a variable or struct member that contains an array of #DBusAddressEntry. + * If it does not contain #NULL, the entries that were previously + * there are freed with dbus_address_entries_free(). + * + * This is similar to dbus_clear_connection(): see that function + * for more details. + * + * @param pointer_to_entries A pointer to a variable or struct member. + * pointer_to_entries must not be #NULL, but *pointer_to_entries + * may be #NULL. + */ +static inline void +dbus_clear_address_entries (DBusAddressEntry ***pointer_to_entries) +{ + _dbus_clear_pointer_impl (DBusAddressEntry *, pointer_to_entries, + dbus_address_entries_free); +} + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_ADDRESS_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-arch-deps.h b/src/3rdparty/libdbus/dbus/dbus-arch-deps.h new file mode 100644 index 00000000..29dea44f --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-arch-deps.h @@ -0,0 +1,5 @@ +#if defined(PLATFORM_DBUS_ARCH_DEPS_H) +# include PLATFORM_DBUS_ARCH_DEPS_H +#else +# error "This libdbus build is not available for this platform" +#endif diff --git a/src/3rdparty/libdbus/dbus/dbus-arch-deps.h.in b/src/3rdparty/libdbus/dbus/dbus-arch-deps.h.in new file mode 100644 index 00000000..9dcb83cd --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-arch-deps.h.in @@ -0,0 +1,65 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-arch-deps.h Header with architecture/compiler specific information, installed to libdir + * + * Copyright (C) 2003 Red Hat, Inc. + * SPDX-License-Identifier: AFL-2.0 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.0 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_ARCH_DEPS_H +#define DBUS_ARCH_DEPS_H + +#include <dbus/dbus-macros.h> + +DBUS_BEGIN_DECLS + +/* D-Bus no longer supports platforms with no 64-bit integer type. */ +#define DBUS_HAVE_INT64 1 +_DBUS_GNUC_EXTENSION typedef @DBUS_INT64_TYPE@ dbus_int64_t; +_DBUS_GNUC_EXTENSION typedef unsigned @DBUS_INT64_TYPE@ dbus_uint64_t; +#define DBUS_INT64_MODIFIER "@DBUS_INT64_MODIFIER@" + +#define DBUS_INT64_CONSTANT(val) (_DBUS_GNUC_EXTENSION @DBUS_INT64_CONSTANT@) +#define DBUS_UINT64_CONSTANT(val) (_DBUS_GNUC_EXTENSION @DBUS_UINT64_CONSTANT@) + +typedef @DBUS_INT32_TYPE@ dbus_int32_t; +typedef unsigned @DBUS_INT32_TYPE@ dbus_uint32_t; + +typedef @DBUS_INT16_TYPE@ dbus_int16_t; +typedef unsigned @DBUS_INT16_TYPE@ dbus_uint16_t; + +#define DBUS_SIZEOF_VOID_P @DBUS_SIZEOF_VOID_P@ + +/* This is not really arch-dependent, but it's not worth + * creating an additional generated header just for this + */ +#define DBUS_MAJOR_VERSION @DBUS_MAJOR_VERSION@ +#define DBUS_MINOR_VERSION @DBUS_MINOR_VERSION@ +#define DBUS_MICRO_VERSION @DBUS_MICRO_VERSION@ + +#define DBUS_VERSION_STRING "@DBUS_VERSION@" + +#define DBUS_VERSION ((@DBUS_MAJOR_VERSION@ << 16) | (@DBUS_MINOR_VERSION@ << 8) | (@DBUS_MICRO_VERSION@)) + +DBUS_END_DECLS + +#endif /* DBUS_ARCH_DEPS_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-auth.c b/src/3rdparty/libdbus/dbus/dbus-auth.c new file mode 100644 index 00000000..09942f80 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-auth.c @@ -0,0 +1,2970 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-auth.c Authentication + * + * Copyright (C) 2002, 2003, 2004 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-auth.h" +#include "dbus-string.h" +#include "dbus-list.h" +#include "dbus-internals.h" +#include "dbus-keyring.h" +#include "dbus-sha.h" +#include "dbus-protocol.h" +#include "dbus-credentials.h" + +/** + * @defgroup DBusAuth Authentication + * @ingroup DBusInternals + * @brief DBusAuth object + * + * DBusAuth manages the authentication negotiation when a connection + * is first established, and also manages any encryption used over a + * connection. + * + * @todo some SASL profiles require sending the empty string as a + * challenge/response, but we don't currently allow that in our + * protocol. + * + * @todo right now sometimes both ends will block waiting for input + * from the other end, e.g. if there's an error during + * DBUS_COOKIE_SHA1. + * + * @todo the cookie keyring needs to be cached globally not just + * per-auth (which raises threadsafety issues too) + * + * @todo grep FIXME in dbus-auth.c + */ + +/** + * @defgroup DBusAuthInternals Authentication implementation details + * @ingroup DBusInternals + * @brief DBusAuth implementation details + * + * Private details of authentication code. + * + * @{ + */ + +/** + * This function appends an initial client response to the given string + */ +typedef dbus_bool_t (* DBusInitialResponseFunction) (DBusAuth *auth, + DBusString *response); + +/** + * This function processes a block of data received from the peer. + * i.e. handles a DATA command. + */ +typedef dbus_bool_t (* DBusAuthDataFunction) (DBusAuth *auth, + const DBusString *data); + +/** + * This function encodes a block of data from the peer. + */ +typedef dbus_bool_t (* DBusAuthEncodeFunction) (DBusAuth *auth, + const DBusString *data, + DBusString *encoded); + +/** + * This function decodes a block of data from the peer. + */ +typedef dbus_bool_t (* DBusAuthDecodeFunction) (DBusAuth *auth, + const DBusString *data, + DBusString *decoded); + +/** + * This function is called when the mechanism is abandoned. + */ +typedef void (* DBusAuthShutdownFunction) (DBusAuth *auth); + +/** + * Virtual table representing a particular auth mechanism. + */ +typedef struct +{ + const char *mechanism; /**< Name of the mechanism */ + DBusAuthDataFunction server_data_func; /**< Function on server side for DATA */ + DBusAuthEncodeFunction server_encode_func; /**< Function on server side to encode */ + DBusAuthDecodeFunction server_decode_func; /**< Function on server side to decode */ + DBusAuthShutdownFunction server_shutdown_func; /**< Function on server side to shut down */ + DBusInitialResponseFunction client_initial_response_func; /**< Function on client side to handle initial response */ + DBusAuthDataFunction client_data_func; /**< Function on client side for DATA */ + DBusAuthEncodeFunction client_encode_func; /**< Function on client side for encode */ + DBusAuthDecodeFunction client_decode_func; /**< Function on client side for decode */ + DBusAuthShutdownFunction client_shutdown_func; /**< Function on client side for shutdown */ +} DBusAuthMechanismHandler; + +/** + * Enumeration for the known authentication commands. + */ +typedef enum { + DBUS_AUTH_COMMAND_AUTH, + DBUS_AUTH_COMMAND_CANCEL, + DBUS_AUTH_COMMAND_DATA, + DBUS_AUTH_COMMAND_BEGIN, + DBUS_AUTH_COMMAND_REJECTED, + DBUS_AUTH_COMMAND_OK, + DBUS_AUTH_COMMAND_ERROR, + DBUS_AUTH_COMMAND_UNKNOWN, + DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD, + DBUS_AUTH_COMMAND_AGREE_UNIX_FD +} DBusAuthCommand; + +/** + * Auth state function, determines the reaction to incoming events for + * a particular state. Returns whether we had enough memory to + * complete the operation. + */ +typedef dbus_bool_t (* DBusAuthStateFunction) (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args); + +/** + * Information about a auth state. + */ +typedef struct +{ + const char *name; /**< Name of the state */ + DBusAuthStateFunction handler; /**< State function for this state */ +} DBusAuthStateData; + +/** + * Internal members of DBusAuth. + */ +struct DBusAuth +{ + int refcount; /**< reference count */ + const char *side; /**< Client or server */ + + DBusString incoming; /**< Incoming data buffer */ + DBusString outgoing; /**< Outgoing data buffer */ + + const DBusAuthStateData *state; /**< Current protocol state */ + + const DBusAuthMechanismHandler *mech; /**< Current auth mechanism */ + + DBusString identity; /**< Current identity we're authorizing + * as. + */ + + DBusCredentials *credentials; /**< Credentials read from socket + */ + + DBusCredentials *authorized_identity; /**< Credentials that are authorized */ + + DBusCredentials *desired_identity; /**< Identity client has requested */ + + DBusString context; /**< Cookie scope */ + DBusKeyring *keyring; /**< Keyring for cookie mechanism. */ + int cookie_id; /**< ID of cookie to use */ + DBusString challenge; /**< Challenge sent to client */ + + char **allowed_mechs; /**< Mechanisms we're allowed to use, + * or #NULL if we can use any + */ + + unsigned int needed_memory : 1; /**< We needed memory to continue since last + * successful getting something done + */ + unsigned int already_got_mechanisms : 1; /**< Client already got mech list */ + unsigned int already_asked_for_initial_response : 1; /**< Already sent a blank challenge to get an initial response */ + unsigned int buffer_outstanding : 1; /**< Buffer is "checked out" for reading data into */ + + unsigned int unix_fd_possible : 1; /**< This side could do unix fd passing */ + unsigned int unix_fd_negotiated : 1; /**< Unix fd was successfully negotiated */ +}; + +/** + * "Subclass" of DBusAuth for client side + */ +typedef struct +{ + DBusAuth base; /**< Parent class */ + + DBusList *mechs_to_try; /**< Mechanisms we got from the server that we're going to try using */ + + DBusString guid_from_server; /**< GUID received from server */ + +} DBusAuthClient; + +/** + * "Subclass" of DBusAuth for server side. + */ +typedef struct +{ + DBusAuth base; /**< Parent class */ + + int failures; /**< Number of times client has been rejected */ + int max_failures; /**< Number of times we reject before disconnect */ + + DBusString guid; /**< Our globally unique ID in hex encoding */ + +} DBusAuthServer; + +static void goto_state (DBusAuth *auth, + const DBusAuthStateData *new_state); +static dbus_bool_t send_auth (DBusAuth *auth, + const DBusAuthMechanismHandler *mech); +static dbus_bool_t send_data (DBusAuth *auth, + DBusString *data); +static dbus_bool_t send_rejected (DBusAuth *auth); +static dbus_bool_t send_error (DBusAuth *auth, + const char *message); +static dbus_bool_t send_ok (DBusAuth *auth); +static dbus_bool_t send_begin (DBusAuth *auth); +static dbus_bool_t send_cancel (DBusAuth *auth); +static dbus_bool_t send_negotiate_unix_fd (DBusAuth *auth); +static dbus_bool_t send_agree_unix_fd (DBusAuth *auth); + +/** + * Client states + */ + +static dbus_bool_t handle_server_state_waiting_for_auth (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args); +static dbus_bool_t handle_server_state_waiting_for_data (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args); +static dbus_bool_t handle_server_state_waiting_for_begin (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args); + +static const DBusAuthStateData server_state_waiting_for_auth = { + "WaitingForAuth", handle_server_state_waiting_for_auth +}; +static const DBusAuthStateData server_state_waiting_for_data = { + "WaitingForData", handle_server_state_waiting_for_data +}; +static const DBusAuthStateData server_state_waiting_for_begin = { + "WaitingForBegin", handle_server_state_waiting_for_begin +}; + +/** + * Client states + */ + +static dbus_bool_t handle_client_state_waiting_for_data (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args); +static dbus_bool_t handle_client_state_waiting_for_ok (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args); +static dbus_bool_t handle_client_state_waiting_for_reject (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args); +static dbus_bool_t handle_client_state_waiting_for_agree_unix_fd (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args); + +static const DBusAuthStateData client_state_need_send_auth = { + "NeedSendAuth", NULL +}; +static const DBusAuthStateData client_state_waiting_for_data = { + "WaitingForData", handle_client_state_waiting_for_data +}; +/* The WaitingForOK state doesn't appear to be used. + * See https://bugs.freedesktop.org/show_bug.cgi?id=97298 */ +_DBUS_GNUC_UNUSED +static const DBusAuthStateData client_state_waiting_for_ok = { + "WaitingForOK", handle_client_state_waiting_for_ok +}; +static const DBusAuthStateData client_state_waiting_for_reject = { + "WaitingForReject", handle_client_state_waiting_for_reject +}; +static const DBusAuthStateData client_state_waiting_for_agree_unix_fd = { + "WaitingForAgreeUnixFD", handle_client_state_waiting_for_agree_unix_fd +}; + +/** + * Common terminal states. Terminal states have handler == NULL. + */ + +static const DBusAuthStateData common_state_authenticated = { + "Authenticated", NULL +}; + +static const DBusAuthStateData common_state_need_disconnect = { + "NeedDisconnect", NULL +}; + +static const char auth_side_client[] = "client"; +static const char auth_side_server[] = "server"; +/** + * @param auth the auth conversation + * @returns #TRUE if the conversation is the server side + */ +#define DBUS_AUTH_IS_SERVER(auth) ((auth)->side == auth_side_server) +/** + * @param auth the auth conversation + * @returns #TRUE if the conversation is the client side + */ +#define DBUS_AUTH_IS_CLIENT(auth) ((auth)->side == auth_side_client) +/** + * @param auth the auth conversation + * @returns auth cast to DBusAuthClient + */ +#define DBUS_AUTH_CLIENT(auth) ((DBusAuthClient*)(auth)) +/** + * @param auth the auth conversation + * @returns auth cast to DBusAuthServer + */ +#define DBUS_AUTH_SERVER(auth) ((DBusAuthServer*)(auth)) + +/** + * The name of the auth ("client" or "server") + * @param auth the auth conversation + * @returns a string + */ +#define DBUS_AUTH_NAME(auth) ((auth)->side) + +static DBusAuth* +_dbus_auth_new (int size) +{ + DBusAuth *auth; + + auth = dbus_malloc0 (size); + if (auth == NULL) + return NULL; + + auth->refcount = 1; + + auth->keyring = NULL; + auth->cookie_id = -1; + + /* note that we don't use the max string length feature, + * because you can't use that feature if you're going to + * try to recover from out-of-memory (it creates + * what looks like unrecoverable inability to alloc + * more space in the string). But we do handle + * overlong buffers in _dbus_auth_do_work(). + */ + + if (!_dbus_string_init (&auth->incoming)) + goto enomem_0; + + if (!_dbus_string_init (&auth->outgoing)) + goto enomem_1; + + if (!_dbus_string_init (&auth->identity)) + goto enomem_2; + + if (!_dbus_string_init (&auth->context)) + goto enomem_3; + + if (!_dbus_string_init (&auth->challenge)) + goto enomem_4; + + /* default context if none is specified */ + if (!_dbus_string_append (&auth->context, "org_freedesktop_general")) + goto enomem_5; + + auth->credentials = _dbus_credentials_new (); + if (auth->credentials == NULL) + goto enomem_6; + + auth->authorized_identity = _dbus_credentials_new (); + if (auth->authorized_identity == NULL) + goto enomem_7; + + auth->desired_identity = _dbus_credentials_new (); + if (auth->desired_identity == NULL) + goto enomem_8; + + return auth; + +#if 0 + enomem_9: + _dbus_credentials_unref (auth->desired_identity); +#endif + enomem_8: + _dbus_credentials_unref (auth->authorized_identity); + enomem_7: + _dbus_credentials_unref (auth->credentials); + enomem_6: + /* last alloc was an append to context, which is freed already below */ ; + enomem_5: + _dbus_string_free (&auth->challenge); + enomem_4: + _dbus_string_free (&auth->context); + enomem_3: + _dbus_string_free (&auth->identity); + enomem_2: + _dbus_string_free (&auth->outgoing); + enomem_1: + _dbus_string_free (&auth->incoming); + enomem_0: + dbus_free (auth); + return NULL; +} + +static void +shutdown_mech (DBusAuth *auth) +{ + /* Cancel any auth */ + auth->already_asked_for_initial_response = FALSE; + _dbus_string_set_length (&auth->identity, 0); + + _dbus_credentials_clear (auth->authorized_identity); + _dbus_credentials_clear (auth->desired_identity); + + if (auth->mech != NULL) + { + _dbus_verbose ("%s: Shutting down mechanism %s\n", + DBUS_AUTH_NAME (auth), auth->mech->mechanism); + + if (DBUS_AUTH_IS_CLIENT (auth)) + (* auth->mech->client_shutdown_func) (auth); + else + (* auth->mech->server_shutdown_func) (auth); + + auth->mech = NULL; + } +} + +/* + * DBUS_COOKIE_SHA1 mechanism + */ + +/* Returns TRUE but with an empty string hash if the + * cookie_id isn't known. As with all this code + * TRUE just means we had enough memory. + */ +static dbus_bool_t +sha1_compute_hash (DBusAuth *auth, + int cookie_id, + const DBusString *server_challenge, + const DBusString *client_challenge, + DBusString *hash) +{ + DBusString cookie; + DBusString to_hash; + dbus_bool_t retval; + + _dbus_assert (auth->keyring != NULL); + + retval = FALSE; + + if (!_dbus_string_init (&cookie)) + return FALSE; + + if (!_dbus_keyring_get_hex_key (auth->keyring, cookie_id, + &cookie)) + goto out_0; + + if (_dbus_string_get_length (&cookie) == 0) + { + retval = TRUE; + goto out_0; + } + + if (!_dbus_string_init (&to_hash)) + goto out_0; + + if (!_dbus_string_copy (server_challenge, 0, + &to_hash, _dbus_string_get_length (&to_hash))) + goto out_1; + + if (!_dbus_string_append (&to_hash, ":")) + goto out_1; + + if (!_dbus_string_copy (client_challenge, 0, + &to_hash, _dbus_string_get_length (&to_hash))) + goto out_1; + + if (!_dbus_string_append (&to_hash, ":")) + goto out_1; + + if (!_dbus_string_copy (&cookie, 0, + &to_hash, _dbus_string_get_length (&to_hash))) + goto out_1; + + if (!_dbus_sha_compute (&to_hash, hash)) + goto out_1; + + retval = TRUE; + + out_1: + _dbus_string_zero (&to_hash); + _dbus_string_free (&to_hash); + out_0: + _dbus_string_zero (&cookie); + _dbus_string_free (&cookie); + return retval; +} + +/** http://www.ietf.org/rfc/rfc2831.txt suggests at least 64 bits of + * entropy, we use 128. This is the number of bytes in the random + * challenge. + */ +#define N_CHALLENGE_BYTES (128/8) + +static dbus_bool_t +sha1_handle_first_client_response (DBusAuth *auth, + const DBusString *data) +{ + /* We haven't sent a challenge yet, we're expecting a desired + * username from the client. + */ + DBusString tmp = _DBUS_STRING_INIT_INVALID; + DBusString tmp2 = _DBUS_STRING_INIT_INVALID; + dbus_bool_t retval = FALSE; + DBusError error = DBUS_ERROR_INIT; + DBusCredentials *myself = NULL; + + _dbus_string_set_length (&auth->challenge, 0); + + if (_dbus_string_get_length (data) > 0) + { + if (_dbus_string_get_length (&auth->identity) > 0) + { + /* Tried to send two auth identities, wtf */ + _dbus_verbose ("%s: client tried to send auth identity, but we already have one\n", + DBUS_AUTH_NAME (auth)); + return send_rejected (auth); + } + else + { + /* this is our auth identity */ + if (!_dbus_string_copy (data, 0, &auth->identity, 0)) + return FALSE; + } + } + + if (!_dbus_credentials_add_from_user (auth->desired_identity, data, + DBUS_CREDENTIALS_ADD_FLAGS_USER_DATABASE, + &error)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + return FALSE; + } + + _dbus_verbose ("%s: Did not get a valid username from client: %s\n", + DBUS_AUTH_NAME (auth), error.message); + dbus_error_free (&error); + return send_rejected (auth); + } + + if (!_dbus_string_init (&tmp)) + return FALSE; + + if (!_dbus_string_init (&tmp2)) + { + _dbus_string_free (&tmp); + return FALSE; + } + + myself = _dbus_credentials_new_from_current_process (); + + if (myself == NULL) + goto out; + + if (!_dbus_credentials_same_user (myself, auth->desired_identity)) + { + /* + * DBUS_COOKIE_SHA1 is not suitable for authenticating that the + * client is anyone other than the user owning the process + * containing the DBusServer: we probably aren't allowed to write + * to other users' home directories. Even if we can (for example + * uid 0 on traditional Unix or CAP_DAC_OVERRIDE on Linux), we + * must not, because the other user controls their home directory, + * and could carry out symlink attacks to make us read from or + * write to unintended locations. It's difficult to avoid symlink + * attacks in a portable way, so we just don't try. This isn't a + * regression, because DBUS_COOKIE_SHA1 never worked for other + * users anyway. + */ + _dbus_verbose ("%s: client tried to authenticate as \"%s\", " + "but that doesn't match this process", + DBUS_AUTH_NAME (auth), + _dbus_string_get_const_data (data)); + retval = send_rejected (auth); + goto out; + } + + /* we cache the keyring for speed, so here we drop it if it's the + * wrong one. FIXME caching the keyring here is useless since we use + * a different DBusAuth for every connection. + */ + if (auth->keyring && + !_dbus_keyring_is_for_credentials (auth->keyring, + auth->desired_identity)) + { + _dbus_keyring_unref (auth->keyring); + auth->keyring = NULL; + } + + if (auth->keyring == NULL) + { + auth->keyring = _dbus_keyring_new_for_credentials (auth->desired_identity, + &auth->context, + &error); + + if (auth->keyring == NULL) + { + if (dbus_error_has_name (&error, + DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + goto out; + } + else + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + _dbus_verbose ("%s: Error loading keyring: %s\n", + DBUS_AUTH_NAME (auth), error.message); + if (send_rejected (auth)) + retval = TRUE; /* retval is only about mem */ + dbus_error_free (&error); + goto out; + } + } + else + { + _dbus_assert (!dbus_error_is_set (&error)); + } + } + + _dbus_assert (auth->keyring != NULL); + + auth->cookie_id = _dbus_keyring_get_best_key (auth->keyring, &error); + if (auth->cookie_id < 0) + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + _dbus_verbose ("%s: Could not get a cookie ID to send to client: %s\n", + DBUS_AUTH_NAME (auth), error.message); + if (send_rejected (auth)) + retval = TRUE; + dbus_error_free (&error); + goto out; + } + else + { + _dbus_assert (!dbus_error_is_set (&error)); + } + + if (!_dbus_string_copy (&auth->context, 0, + &tmp2, _dbus_string_get_length (&tmp2))) + goto out; + + if (!_dbus_string_append (&tmp2, " ")) + goto out; + + if (!_dbus_string_append_int (&tmp2, auth->cookie_id)) + goto out; + + if (!_dbus_string_append (&tmp2, " ")) + goto out; + + if (!_dbus_generate_random_bytes (&tmp, N_CHALLENGE_BYTES, &error)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + goto out; + } + else + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + _dbus_verbose ("%s: Error generating challenge: %s\n", + DBUS_AUTH_NAME (auth), error.message); + if (send_rejected (auth)) + retval = TRUE; /* retval is only about mem */ + + dbus_error_free (&error); + goto out; + } + } + + _dbus_string_set_length (&auth->challenge, 0); + if (!_dbus_string_hex_encode (&tmp, 0, &auth->challenge, 0)) + goto out; + + if (!_dbus_string_hex_encode (&tmp, 0, &tmp2, + _dbus_string_get_length (&tmp2))) + goto out; + + if (!send_data (auth, &tmp2)) + goto out; + + goto_state (auth, &server_state_waiting_for_data); + retval = TRUE; + + out: + _dbus_string_zero (&tmp); + _dbus_string_free (&tmp); + _dbus_string_zero (&tmp2); + _dbus_string_free (&tmp2); + _dbus_clear_credentials (&myself); + + return retval; +} + +static dbus_bool_t +sha1_handle_second_client_response (DBusAuth *auth, + const DBusString *data) +{ + /* We are expecting a response which is the hex-encoded client + * challenge, space, then SHA-1 hash of the concatenation of our + * challenge, ":", client challenge, ":", secret key, all + * hex-encoded. + */ + int i; + DBusString client_challenge; + DBusString client_hash; + dbus_bool_t retval; + DBusString correct_hash; + + retval = FALSE; + + if (!_dbus_string_find_blank (data, 0, &i)) + { + _dbus_verbose ("%s: no space separator in client response\n", + DBUS_AUTH_NAME (auth)); + return send_rejected (auth); + } + + if (!_dbus_string_init (&client_challenge)) + goto out_0; + + if (!_dbus_string_init (&client_hash)) + goto out_1; + + if (!_dbus_string_copy_len (data, 0, i, &client_challenge, + 0)) + goto out_2; + + _dbus_string_skip_blank (data, i, &i); + + if (!_dbus_string_copy_len (data, i, + _dbus_string_get_length (data) - i, + &client_hash, + 0)) + goto out_2; + + if (_dbus_string_get_length (&client_challenge) == 0 || + _dbus_string_get_length (&client_hash) == 0) + { + _dbus_verbose ("%s: zero-length client challenge or hash\n", + DBUS_AUTH_NAME (auth)); + if (send_rejected (auth)) + retval = TRUE; + goto out_2; + } + + if (!_dbus_string_init (&correct_hash)) + goto out_2; + + if (!sha1_compute_hash (auth, auth->cookie_id, + &auth->challenge, + &client_challenge, + &correct_hash)) + goto out_3; + + /* if cookie_id was invalid, then we get an empty hash */ + if (_dbus_string_get_length (&correct_hash) == 0) + { + if (send_rejected (auth)) + retval = TRUE; + goto out_3; + } + + if (!_dbus_string_equal (&client_hash, &correct_hash)) + { + if (send_rejected (auth)) + retval = TRUE; + goto out_3; + } + + if (!_dbus_credentials_add_credentials (auth->authorized_identity, + auth->desired_identity)) + goto out_3; + + /* Copy process ID (and PID FD) from the socket credentials if it's there + */ + if (!_dbus_credentials_add_credential (auth->authorized_identity, + DBUS_CREDENTIAL_UNIX_PROCESS_FD, + auth->credentials)) + goto out_3; + if (!_dbus_credentials_add_credential (auth->authorized_identity, + DBUS_CREDENTIAL_UNIX_PROCESS_ID, + auth->credentials)) + goto out_3; + + if (!send_ok (auth)) + goto out_3; + + _dbus_verbose ("%s: authenticated client using DBUS_COOKIE_SHA1\n", + DBUS_AUTH_NAME (auth)); + + retval = TRUE; + + out_3: + _dbus_string_zero (&correct_hash); + _dbus_string_free (&correct_hash); + out_2: + _dbus_string_zero (&client_hash); + _dbus_string_free (&client_hash); + out_1: + _dbus_string_free (&client_challenge); + out_0: + return retval; +} + +static dbus_bool_t +handle_server_data_cookie_sha1_mech (DBusAuth *auth, + const DBusString *data) +{ + if (auth->cookie_id < 0) + return sha1_handle_first_client_response (auth, data); + else + return sha1_handle_second_client_response (auth, data); +} + +static void +handle_server_shutdown_cookie_sha1_mech (DBusAuth *auth) +{ + auth->cookie_id = -1; + _dbus_string_set_length (&auth->challenge, 0); +} + +static dbus_bool_t +handle_client_initial_response_cookie_sha1_mech (DBusAuth *auth, + DBusString *response) +{ + DBusString username; + dbus_bool_t retval; + + retval = FALSE; + + if (!_dbus_string_init (&username)) + return FALSE; + + if (!_dbus_append_user_from_current_process (&username)) + goto out_0; + + if (!_dbus_string_hex_encode (&username, 0, + response, + _dbus_string_get_length (response))) + goto out_0; + + retval = TRUE; + + out_0: + _dbus_string_free (&username); + + return retval; +} + +static dbus_bool_t +handle_client_data_cookie_sha1_mech (DBusAuth *auth, + const DBusString *data) +{ + /* The data we get from the server should be the cookie context + * name, the cookie ID, and the server challenge, separated by + * spaces. We send back our challenge string and the correct hash. + */ + dbus_bool_t retval = FALSE; + DBusString context; + DBusString cookie_id_str; + DBusString server_challenge; + DBusString client_challenge; + DBusString correct_hash; + DBusString tmp; + int i, j; + long val; + DBusError error = DBUS_ERROR_INIT; + + if (!_dbus_string_find_blank (data, 0, &i)) + { + if (send_error (auth, + "Server did not send context/ID/challenge properly")) + retval = TRUE; + goto out_0; + } + + if (!_dbus_string_init (&context)) + goto out_0; + + if (!_dbus_string_copy_len (data, 0, i, + &context, 0)) + goto out_1; + + _dbus_string_skip_blank (data, i, &i); + if (!_dbus_string_find_blank (data, i, &j)) + { + if (send_error (auth, + "Server did not send context/ID/challenge properly")) + retval = TRUE; + goto out_1; + } + + if (!_dbus_string_init (&cookie_id_str)) + goto out_1; + + if (!_dbus_string_copy_len (data, i, j - i, + &cookie_id_str, 0)) + goto out_2; + + if (!_dbus_string_init (&server_challenge)) + goto out_2; + + i = j; + _dbus_string_skip_blank (data, i, &i); + j = _dbus_string_get_length (data); + + if (!_dbus_string_copy_len (data, i, j - i, + &server_challenge, 0)) + goto out_3; + + if (!_dbus_keyring_validate_context (&context)) + { + if (send_error (auth, "Server sent invalid cookie context")) + retval = TRUE; + goto out_3; + } + + if (!_dbus_string_parse_int (&cookie_id_str, 0, &val, NULL)) + { + if (send_error (auth, "Could not parse cookie ID as an integer")) + retval = TRUE; + goto out_3; + } + + if (_dbus_string_get_length (&server_challenge) == 0) + { + if (send_error (auth, "Empty server challenge string")) + retval = TRUE; + goto out_3; + } + + if (auth->keyring == NULL) + { + auth->keyring = _dbus_keyring_new_for_credentials (NULL, + &context, + &error); + + if (auth->keyring == NULL) + { + if (dbus_error_has_name (&error, + DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + goto out_3; + } + else + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + + _dbus_verbose ("%s: Error loading keyring: %s\n", + DBUS_AUTH_NAME (auth), error.message); + + if (send_error (auth, "Could not load cookie file")) + retval = TRUE; /* retval is only about mem */ + + dbus_error_free (&error); + goto out_3; + } + } + else + { + _dbus_assert (!dbus_error_is_set (&error)); + } + } + + _dbus_assert (auth->keyring != NULL); + + if (!_dbus_string_init (&tmp)) + goto out_3; + + if (!_dbus_generate_random_bytes (&tmp, N_CHALLENGE_BYTES, &error)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + goto out_4; + } + else + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + + _dbus_verbose ("%s: Failed to generate challenge: %s\n", + DBUS_AUTH_NAME (auth), error.message); + + if (send_error (auth, "Failed to generate challenge")) + retval = TRUE; /* retval is only about mem */ + + dbus_error_free (&error); + goto out_4; + } + } + + if (!_dbus_string_init (&client_challenge)) + goto out_4; + + if (!_dbus_string_hex_encode (&tmp, 0, &client_challenge, 0)) + goto out_5; + + if (!_dbus_string_init (&correct_hash)) + goto out_5; + + if (!sha1_compute_hash (auth, val, + &server_challenge, + &client_challenge, + &correct_hash)) + goto out_6; + + if (_dbus_string_get_length (&correct_hash) == 0) + { + /* couldn't find the cookie ID or something */ + if (send_error (auth, "Don't have the requested cookie ID")) + retval = TRUE; + goto out_6; + } + + _dbus_string_set_length (&tmp, 0); + + if (!_dbus_string_copy (&client_challenge, 0, &tmp, + _dbus_string_get_length (&tmp))) + goto out_6; + + if (!_dbus_string_append (&tmp, " ")) + goto out_6; + + if (!_dbus_string_copy (&correct_hash, 0, &tmp, + _dbus_string_get_length (&tmp))) + goto out_6; + + if (!send_data (auth, &tmp)) + goto out_6; + + retval = TRUE; + + out_6: + _dbus_string_zero (&correct_hash); + _dbus_string_free (&correct_hash); + out_5: + _dbus_string_free (&client_challenge); + out_4: + _dbus_string_zero (&tmp); + _dbus_string_free (&tmp); + out_3: + _dbus_string_free (&server_challenge); + out_2: + _dbus_string_free (&cookie_id_str); + out_1: + _dbus_string_free (&context); + out_0: + return retval; +} + +static void +handle_client_shutdown_cookie_sha1_mech (DBusAuth *auth) +{ + auth->cookie_id = -1; + _dbus_string_set_length (&auth->challenge, 0); +} + +/* + * EXTERNAL mechanism + */ + +static dbus_bool_t +handle_server_data_external_mech (DBusAuth *auth, + const DBusString *data) +{ + if (_dbus_credentials_are_anonymous (auth->credentials)) + { + _dbus_verbose ("%s: no credentials, mechanism EXTERNAL can't authenticate\n", + DBUS_AUTH_NAME (auth)); + return send_rejected (auth); + } + + if (_dbus_string_get_length (data) > 0) + { + if (_dbus_string_get_length (&auth->identity) > 0) + { + /* Tried to send two auth identities, wtf */ + _dbus_verbose ("%s: client tried to send auth identity, but we already have one\n", + DBUS_AUTH_NAME (auth)); + return send_rejected (auth); + } + else + { + /* this is our auth identity */ + if (!_dbus_string_copy (data, 0, &auth->identity, 0)) + return FALSE; + } + } + + /* Poke client for an auth identity, if none given */ + if (_dbus_string_get_length (&auth->identity) == 0 && + !auth->already_asked_for_initial_response) + { + if (send_data (auth, NULL)) + { + _dbus_verbose ("%s: sending empty challenge asking client for auth identity\n", + DBUS_AUTH_NAME (auth)); + auth->already_asked_for_initial_response = TRUE; + goto_state (auth, &server_state_waiting_for_data); + return TRUE; + } + else + return FALSE; + } + + _dbus_credentials_clear (auth->desired_identity); + + /* If auth->identity is still empty here, then client + * responded with an empty string after we poked it for + * an initial response. This means to try to auth the + * identity provided in the credentials. + */ + if (_dbus_string_get_length (&auth->identity) == 0) + { + if (!_dbus_credentials_add_credentials (auth->desired_identity, + auth->credentials)) + { + return FALSE; /* OOM */ + } + } + else + { + DBusError error = DBUS_ERROR_INIT; + + if (!_dbus_credentials_add_from_user (auth->desired_identity, + &auth->identity, + DBUS_CREDENTIALS_ADD_FLAGS_NONE, + &error)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + return FALSE; + } + + _dbus_verbose ("%s: could not get credentials from uid string: %s\n", + DBUS_AUTH_NAME (auth), error.message); + dbus_error_free (&error); + return send_rejected (auth); + } + } + + if (_dbus_credentials_are_anonymous (auth->desired_identity)) + { + _dbus_verbose ("%s: desired user %s is no good\n", + DBUS_AUTH_NAME (auth), + _dbus_string_get_const_data (&auth->identity)); + return send_rejected (auth); + } + + if (_dbus_credentials_are_superset (auth->credentials, + auth->desired_identity)) + { + /* client has authenticated */ + if (!_dbus_credentials_add_credentials (auth->authorized_identity, + auth->desired_identity)) + return FALSE; + + /* also copy misc process info from the socket credentials + */ + if (!_dbus_credentials_add_credential (auth->authorized_identity, + DBUS_CREDENTIAL_UNIX_PROCESS_FD, + auth->credentials)) + return FALSE; + + if (!_dbus_credentials_add_credential (auth->authorized_identity, + DBUS_CREDENTIAL_UNIX_PROCESS_ID, + auth->credentials)) + return FALSE; + + if (!_dbus_credentials_add_credential (auth->authorized_identity, + DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID, + auth->credentials)) + return FALSE; + + if (!_dbus_credentials_add_credential (auth->authorized_identity, + DBUS_CREDENTIAL_UNIX_GROUP_IDS, + auth->credentials)) + return FALSE; + + if (!_dbus_credentials_add_credential (auth->authorized_identity, + DBUS_CREDENTIAL_LINUX_SECURITY_LABEL, + auth->credentials)) + return FALSE; + + if (!send_ok (auth)) + return FALSE; + + _dbus_verbose ("%s: authenticated client based on socket credentials\n", + DBUS_AUTH_NAME (auth)); + + return TRUE; + } + else + { + _dbus_verbose ("%s: desired identity not found in socket credentials\n", + DBUS_AUTH_NAME (auth)); + return send_rejected (auth); + } +} + +static void +handle_server_shutdown_external_mech (DBusAuth *auth) +{ + +} + +static dbus_bool_t +handle_client_initial_response_external_mech (DBusAuth *auth, + DBusString *response) +{ + /* We always append our UID as an initial response, so the server + * doesn't have to send back an empty challenge to check whether we + * want to specify an identity. i.e. this avoids a round trip that + * the spec for the EXTERNAL mechanism otherwise requires. + */ + DBusString plaintext; + + if (!_dbus_string_init (&plaintext)) + return FALSE; + + if (!_dbus_append_user_from_current_process (&plaintext)) + goto failed; + + if (!_dbus_string_hex_encode (&plaintext, 0, + response, + _dbus_string_get_length (response))) + goto failed; + + _dbus_string_free (&plaintext); + + return TRUE; + + failed: + _dbus_string_free (&plaintext); + return FALSE; +} + +static dbus_bool_t +handle_client_data_external_mech (DBusAuth *auth, + const DBusString *data) +{ + + return TRUE; +} + +static void +handle_client_shutdown_external_mech (DBusAuth *auth) +{ + +} + +/* + * ANONYMOUS mechanism + */ + +static dbus_bool_t +handle_server_data_anonymous_mech (DBusAuth *auth, + const DBusString *data) +{ + if (_dbus_string_get_length (data) > 0) + { + /* Client is allowed to send "trace" data, the only defined + * meaning is that if it contains '@' it is an email address, + * and otherwise it is anything else, and it's supposed to be + * UTF-8 + */ + if (!_dbus_string_validate_utf8 (data, 0, _dbus_string_get_length (data))) + { + _dbus_verbose ("%s: Received invalid UTF-8 trace data from ANONYMOUS client\n", + DBUS_AUTH_NAME (auth)); + return send_rejected (auth); + } + + _dbus_verbose ("%s: ANONYMOUS client sent trace string: '%s'\n", + DBUS_AUTH_NAME (auth), + _dbus_string_get_const_data (data)); + } + + /* We want to be anonymous (clear in case some other protocol got midway through I guess) */ + _dbus_credentials_clear (auth->desired_identity); + + /* Copy process ID (and PID FD) from the socket credentials + */ + if (!_dbus_credentials_add_credential (auth->authorized_identity, + DBUS_CREDENTIAL_UNIX_PROCESS_FD, + auth->credentials)) + return FALSE; + + if (!_dbus_credentials_add_credential (auth->authorized_identity, + DBUS_CREDENTIAL_UNIX_PROCESS_ID, + auth->credentials)) + return FALSE; + + /* Anonymous is always allowed */ + if (!send_ok (auth)) + return FALSE; + + _dbus_verbose ("%s: authenticated client as anonymous\n", + DBUS_AUTH_NAME (auth)); + + return TRUE; +} + +static void +handle_server_shutdown_anonymous_mech (DBusAuth *auth) +{ + +} + +static dbus_bool_t +handle_client_initial_response_anonymous_mech (DBusAuth *auth, + DBusString *response) +{ + /* Our initial response is a "trace" string which must be valid UTF-8 + * and must be an email address if it contains '@'. + * We just send the dbus implementation info, like a user-agent or + * something, because... why not. There's nothing guaranteed here + * though, we could change it later. + */ + DBusString plaintext; + + if (!_dbus_string_init (&plaintext)) + return FALSE; + + if (!_dbus_string_append (&plaintext, + "libdbus " DBUS_VERSION_STRING)) + goto failed; + + if (!_dbus_string_hex_encode (&plaintext, 0, + response, + _dbus_string_get_length (response))) + goto failed; + + _dbus_string_free (&plaintext); + + return TRUE; + + failed: + _dbus_string_free (&plaintext); + return FALSE; +} + +static dbus_bool_t +handle_client_data_anonymous_mech (DBusAuth *auth, + const DBusString *data) +{ + + return TRUE; +} + +static void +handle_client_shutdown_anonymous_mech (DBusAuth *auth) +{ + +} + +/* Put mechanisms here in order of preference. + * Right now we have: + * + * - EXTERNAL checks socket credentials (or in the future, other info from the OS) + * - DBUS_COOKIE_SHA1 uses a cookie in the home directory, like xauth or ICE + * - ANONYMOUS checks nothing but doesn't auth the person as a user + * + * We might ideally add a mechanism to chain to Cyrus SASL so we can + * use its mechanisms as well. + * + */ +static const DBusAuthMechanismHandler +all_mechanisms[] = { + { "EXTERNAL", + handle_server_data_external_mech, + NULL, NULL, + handle_server_shutdown_external_mech, + handle_client_initial_response_external_mech, + handle_client_data_external_mech, + NULL, NULL, + handle_client_shutdown_external_mech }, + { "DBUS_COOKIE_SHA1", + handle_server_data_cookie_sha1_mech, + NULL, NULL, + handle_server_shutdown_cookie_sha1_mech, + handle_client_initial_response_cookie_sha1_mech, + handle_client_data_cookie_sha1_mech, + NULL, NULL, + handle_client_shutdown_cookie_sha1_mech }, + { "ANONYMOUS", + handle_server_data_anonymous_mech, + NULL, NULL, + handle_server_shutdown_anonymous_mech, + handle_client_initial_response_anonymous_mech, + handle_client_data_anonymous_mech, + NULL, NULL, + handle_client_shutdown_anonymous_mech }, + { NULL, NULL } +}; + +static const DBusAuthMechanismHandler* +find_mech (const DBusString *name, + char **allowed_mechs) +{ + int i; + + if (allowed_mechs != NULL && + !_dbus_string_array_contains ((const char**) allowed_mechs, + _dbus_string_get_const_data (name))) + return NULL; + + i = 0; + while (all_mechanisms[i].mechanism != NULL) + { + if (_dbus_string_equal_c_str (name, + all_mechanisms[i].mechanism)) + + return &all_mechanisms[i]; + + ++i; + } + + return NULL; +} + +static dbus_bool_t +send_auth (DBusAuth *auth, const DBusAuthMechanismHandler *mech) +{ + DBusString auth_command; + + if (!_dbus_string_init (&auth_command)) + return FALSE; + + if (!_dbus_string_append (&auth_command, + "AUTH ")) + { + _dbus_string_free (&auth_command); + return FALSE; + } + + if (!_dbus_string_append (&auth_command, + mech->mechanism)) + { + _dbus_string_free (&auth_command); + return FALSE; + } + + if (mech->client_initial_response_func != NULL) + { + if (!_dbus_string_append (&auth_command, " ")) + { + _dbus_string_free (&auth_command); + return FALSE; + } + + if (!(* mech->client_initial_response_func) (auth, &auth_command)) + { + _dbus_string_free (&auth_command); + return FALSE; + } + } + + if (!_dbus_string_append (&auth_command, + "\r\n")) + { + _dbus_string_free (&auth_command); + return FALSE; + } + + if (!_dbus_string_copy (&auth_command, 0, + &auth->outgoing, + _dbus_string_get_length (&auth->outgoing))) + { + _dbus_string_free (&auth_command); + return FALSE; + } + + _dbus_string_free (&auth_command); + shutdown_mech (auth); + auth->mech = mech; + goto_state (auth, &client_state_waiting_for_data); + + return TRUE; +} + +static dbus_bool_t +send_data (DBusAuth *auth, DBusString *data) +{ + int old_len; + + if (data == NULL || _dbus_string_get_length (data) == 0) + return _dbus_string_append (&auth->outgoing, "DATA\r\n"); + else + { + old_len = _dbus_string_get_length (&auth->outgoing); + if (!_dbus_string_append (&auth->outgoing, "DATA ")) + goto out; + + if (!_dbus_string_hex_encode (data, 0, &auth->outgoing, + _dbus_string_get_length (&auth->outgoing))) + goto out; + + if (!_dbus_string_append (&auth->outgoing, "\r\n")) + goto out; + + return TRUE; + + out: + _dbus_string_set_length (&auth->outgoing, old_len); + + return FALSE; + } +} + +static dbus_bool_t +send_rejected (DBusAuth *auth) +{ + DBusString command; + DBusAuthServer *server_auth; + int i; + + if (!_dbus_string_init (&command)) + return FALSE; + + if (!_dbus_string_append (&command, + "REJECTED")) + goto nomem; + + for (i = 0; all_mechanisms[i].mechanism != NULL; i++) + { + /* skip mechanisms that aren't allowed */ + if (auth->allowed_mechs != NULL && + !_dbus_string_array_contains ((const char**)auth->allowed_mechs, + all_mechanisms[i].mechanism)) + continue; + + if (!_dbus_string_append (&command, + " ")) + goto nomem; + + if (!_dbus_string_append (&command, + all_mechanisms[i].mechanism)) + goto nomem; + } + + if (!_dbus_string_append (&command, "\r\n")) + goto nomem; + + if (!_dbus_string_copy (&command, 0, &auth->outgoing, + _dbus_string_get_length (&auth->outgoing))) + goto nomem; + + shutdown_mech (auth); + + _dbus_assert (DBUS_AUTH_IS_SERVER (auth)); + server_auth = DBUS_AUTH_SERVER (auth); + server_auth->failures += 1; + + if (server_auth->failures >= server_auth->max_failures) + goto_state (auth, &common_state_need_disconnect); + else + goto_state (auth, &server_state_waiting_for_auth); + + _dbus_string_free (&command); + + return TRUE; + + nomem: + _dbus_string_free (&command); + return FALSE; +} + +static dbus_bool_t +send_error (DBusAuth *auth, const char *message) +{ + return _dbus_string_append_printf (&auth->outgoing, + "ERROR \"%s\"\r\n", message); +} + +static dbus_bool_t +send_ok (DBusAuth *auth) +{ + int orig_len; + + orig_len = _dbus_string_get_length (&auth->outgoing); + + if (_dbus_string_append (&auth->outgoing, "OK ") && + _dbus_string_copy (& DBUS_AUTH_SERVER (auth)->guid, + 0, + &auth->outgoing, + _dbus_string_get_length (&auth->outgoing)) && + _dbus_string_append (&auth->outgoing, "\r\n")) + { + goto_state (auth, &server_state_waiting_for_begin); + return TRUE; + } + else + { + _dbus_string_set_length (&auth->outgoing, orig_len); + return FALSE; + } +} + +static dbus_bool_t +send_begin (DBusAuth *auth) +{ + + if (!_dbus_string_append (&auth->outgoing, + "BEGIN\r\n")) + return FALSE; + + goto_state (auth, &common_state_authenticated); + return TRUE; +} + +static dbus_bool_t +process_ok(DBusAuth *auth, + const DBusString *args_from_ok) { + + int end_of_hex; + + /* "args_from_ok" should be the GUID, whitespace already pulled off the front */ + _dbus_assert (_dbus_string_get_length (& DBUS_AUTH_CLIENT (auth)->guid_from_server) == 0); + + /* We decode the hex string to binary, using guid_from_server as scratch... */ + + end_of_hex = 0; + if (!_dbus_string_hex_decode (args_from_ok, 0, &end_of_hex, + & DBUS_AUTH_CLIENT (auth)->guid_from_server, 0)) + return FALSE; + + /* now clear out the scratch */ + _dbus_string_set_length (& DBUS_AUTH_CLIENT (auth)->guid_from_server, 0); + + if (end_of_hex != _dbus_string_get_length (args_from_ok) || + end_of_hex == 0) + { + _dbus_verbose ("Bad GUID from server, parsed %d bytes and had %d bytes from server\n", + end_of_hex, _dbus_string_get_length (args_from_ok)); + goto_state (auth, &common_state_need_disconnect); + return TRUE; + } + + if (!_dbus_string_copy (args_from_ok, 0, &DBUS_AUTH_CLIENT (auth)->guid_from_server, 0)) { + _dbus_string_set_length (& DBUS_AUTH_CLIENT (auth)->guid_from_server, 0); + return FALSE; + } + + _dbus_verbose ("Got GUID '%s' from the server\n", + _dbus_string_get_const_data (& DBUS_AUTH_CLIENT (auth)->guid_from_server)); + + if (auth->unix_fd_possible) + { + if (!send_negotiate_unix_fd (auth)) + { + _dbus_string_set_length (& DBUS_AUTH_CLIENT (auth)->guid_from_server, 0); + return FALSE; + } + + return TRUE; + } + + _dbus_verbose("Not negotiating unix fd passing, since not possible\n"); + + if (!send_begin (auth)) + { + _dbus_string_set_length (& DBUS_AUTH_CLIENT (auth)->guid_from_server, 0); + return FALSE; + } + + return TRUE; +} + +static dbus_bool_t +send_cancel (DBusAuth *auth) +{ + if (_dbus_string_append (&auth->outgoing, "CANCEL\r\n")) + { + goto_state (auth, &client_state_waiting_for_reject); + return TRUE; + } + else + return FALSE; +} + +static dbus_bool_t +process_data (DBusAuth *auth, + const DBusString *args, + DBusAuthDataFunction data_func) +{ + int end; + DBusString decoded; + + if (!_dbus_string_init (&decoded)) + return FALSE; + + if (!_dbus_string_hex_decode (args, 0, &end, &decoded, 0)) + { + _dbus_string_free (&decoded); + return FALSE; + } + + if (_dbus_string_get_length (args) != end) + { + _dbus_string_free (&decoded); + if (!send_error (auth, "Invalid hex encoding")) + return FALSE; + + return TRUE; + } + +#ifdef DBUS_ENABLE_VERBOSE_MODE + if (_dbus_string_validate_ascii (&decoded, 0, + _dbus_string_get_length (&decoded))) + _dbus_verbose ("%s: data: '%s'\n", + DBUS_AUTH_NAME (auth), + _dbus_string_get_const_data (&decoded)); +#endif + + if (!(* data_func) (auth, &decoded)) + { + _dbus_string_free (&decoded); + return FALSE; + } + + _dbus_string_free (&decoded); + return TRUE; +} + +static dbus_bool_t +send_negotiate_unix_fd (DBusAuth *auth) +{ + if (!_dbus_string_append (&auth->outgoing, + "NEGOTIATE_UNIX_FD\r\n")) + return FALSE; + + goto_state (auth, &client_state_waiting_for_agree_unix_fd); + return TRUE; +} + +static dbus_bool_t +send_agree_unix_fd (DBusAuth *auth) +{ + _dbus_assert(auth->unix_fd_possible); + + auth->unix_fd_negotiated = TRUE; + _dbus_verbose("Agreed to UNIX FD passing\n"); + + if (!_dbus_string_append (&auth->outgoing, + "AGREE_UNIX_FD\r\n")) + return FALSE; + + goto_state (auth, &server_state_waiting_for_begin); + return TRUE; +} + +static dbus_bool_t +handle_auth (DBusAuth *auth, const DBusString *args) +{ + if (_dbus_string_get_length (args) == 0) + { + /* No args to the auth, send mechanisms */ + if (!send_rejected (auth)) + return FALSE; + + return TRUE; + } + else + { + int i; + DBusString mech; + DBusString hex_response; + + _dbus_string_find_blank (args, 0, &i); + + if (!_dbus_string_init (&mech)) + return FALSE; + + if (!_dbus_string_init (&hex_response)) + { + _dbus_string_free (&mech); + return FALSE; + } + + if (!_dbus_string_copy_len (args, 0, i, &mech, 0)) + goto failed; + + _dbus_string_skip_blank (args, i, &i); + if (!_dbus_string_copy (args, i, &hex_response, 0)) + goto failed; + + auth->mech = find_mech (&mech, auth->allowed_mechs); + if (auth->mech != NULL) + { + _dbus_verbose ("%s: Trying mechanism %s\n", + DBUS_AUTH_NAME (auth), + auth->mech->mechanism); + + if (!process_data (auth, &hex_response, + auth->mech->server_data_func)) + goto failed; + } + else + { + /* Unsupported mechanism */ + _dbus_verbose ("%s: Unsupported mechanism %s\n", + DBUS_AUTH_NAME (auth), + _dbus_string_get_const_data (&mech)); + + if (!send_rejected (auth)) + goto failed; + } + + _dbus_string_free (&mech); + _dbus_string_free (&hex_response); + + return TRUE; + + failed: + auth->mech = NULL; + _dbus_string_free (&mech); + _dbus_string_free (&hex_response); + return FALSE; + } +} + +static dbus_bool_t +handle_server_state_waiting_for_auth (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args) +{ + switch (command) + { + case DBUS_AUTH_COMMAND_AUTH: + return handle_auth (auth, args); + + case DBUS_AUTH_COMMAND_CANCEL: + case DBUS_AUTH_COMMAND_DATA: + return send_error (auth, "Not currently in an auth conversation"); + + case DBUS_AUTH_COMMAND_BEGIN: + goto_state (auth, &common_state_need_disconnect); + return TRUE; + + case DBUS_AUTH_COMMAND_ERROR: + return send_rejected (auth); + + case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD: + return send_error (auth, "Need to authenticate first"); + + case DBUS_AUTH_COMMAND_REJECTED: + case DBUS_AUTH_COMMAND_OK: + case DBUS_AUTH_COMMAND_UNKNOWN: + case DBUS_AUTH_COMMAND_AGREE_UNIX_FD: + default: + return send_error (auth, "Unknown command"); + } +} + +static dbus_bool_t +handle_server_state_waiting_for_data (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args) +{ + switch (command) + { + case DBUS_AUTH_COMMAND_AUTH: + return send_error (auth, "Sent AUTH while another AUTH in progress"); + + case DBUS_AUTH_COMMAND_CANCEL: + case DBUS_AUTH_COMMAND_ERROR: + return send_rejected (auth); + + case DBUS_AUTH_COMMAND_DATA: + return process_data (auth, args, auth->mech->server_data_func); + + case DBUS_AUTH_COMMAND_BEGIN: + goto_state (auth, &common_state_need_disconnect); + return TRUE; + + case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD: + return send_error (auth, "Need to authenticate first"); + + case DBUS_AUTH_COMMAND_REJECTED: + case DBUS_AUTH_COMMAND_OK: + case DBUS_AUTH_COMMAND_UNKNOWN: + case DBUS_AUTH_COMMAND_AGREE_UNIX_FD: + default: + return send_error (auth, "Unknown command"); + } +} + +static dbus_bool_t +handle_server_state_waiting_for_begin (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args) +{ + switch (command) + { + case DBUS_AUTH_COMMAND_AUTH: + return send_error (auth, "Sent AUTH while expecting BEGIN"); + + case DBUS_AUTH_COMMAND_DATA: + return send_error (auth, "Sent DATA while expecting BEGIN"); + + case DBUS_AUTH_COMMAND_BEGIN: + goto_state (auth, &common_state_authenticated); + return TRUE; + + case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD: + if (auth->unix_fd_possible) + return send_agree_unix_fd(auth); + else + return send_error(auth, "Unix FD passing not supported, not authenticated or otherwise not possible"); + + case DBUS_AUTH_COMMAND_REJECTED: + case DBUS_AUTH_COMMAND_OK: + case DBUS_AUTH_COMMAND_UNKNOWN: + case DBUS_AUTH_COMMAND_AGREE_UNIX_FD: + default: + return send_error (auth, "Unknown command"); + + case DBUS_AUTH_COMMAND_CANCEL: + case DBUS_AUTH_COMMAND_ERROR: + return send_rejected (auth); + } +} + +/* return FALSE if no memory, TRUE if all OK */ +static dbus_bool_t +get_word (const DBusString *str, + int *start, + DBusString *word) +{ + int i; + + _dbus_string_skip_blank (str, *start, start); + _dbus_string_find_blank (str, *start, &i); + + if (i > *start) + { + if (!_dbus_string_copy_len (str, *start, i - *start, word, 0)) + return FALSE; + + *start = i; + } + + return TRUE; +} + +static dbus_bool_t +record_mechanisms (DBusAuth *auth, + const DBusString *args) +{ + int next; + int len; + + if (auth->already_got_mechanisms) + return TRUE; + + len = _dbus_string_get_length (args); + + next = 0; + while (next < len) + { + DBusString m; + const DBusAuthMechanismHandler *mech; + + if (!_dbus_string_init (&m)) + goto nomem; + + if (!get_word (args, &next, &m)) + { + _dbus_string_free (&m); + goto nomem; + } + + mech = find_mech (&m, auth->allowed_mechs); + + if (mech != NULL) + { + /* FIXME right now we try mechanisms in the order + * the server lists them; should we do them in + * some more deterministic order? + * + * Probably in all_mechanisms order, our order of + * preference. Of course when the server is us, + * it lists things in that order anyhow. + */ + + if (mech != &all_mechanisms[0]) + { + _dbus_verbose ("%s: Adding mechanism %s to list we will try\n", + DBUS_AUTH_NAME (auth), mech->mechanism); + + if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try, + (void*) mech)) + { + _dbus_string_free (&m); + goto nomem; + } + } + else + { + _dbus_verbose ("%s: Already tried mechanism %s; not adding to list we will try\n", + DBUS_AUTH_NAME (auth), mech->mechanism); + } + } + else + { + _dbus_verbose ("%s: Server offered mechanism \"%s\" that we don't know how to use\n", + DBUS_AUTH_NAME (auth), + _dbus_string_get_const_data (&m)); + } + + _dbus_string_free (&m); + } + + auth->already_got_mechanisms = TRUE; + + return TRUE; + + nomem: + _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try); + + return FALSE; +} + +static dbus_bool_t +process_rejected (DBusAuth *auth, const DBusString *args) +{ + const DBusAuthMechanismHandler *mech; + DBusAuthClient *client; + + client = DBUS_AUTH_CLIENT (auth); + + if (!auth->already_got_mechanisms) + { + if (!record_mechanisms (auth, args)) + return FALSE; + } + + if (DBUS_AUTH_CLIENT (auth)->mechs_to_try != NULL) + { + mech = client->mechs_to_try->data; + + if (!send_auth (auth, mech)) + return FALSE; + + _dbus_list_pop_first (&client->mechs_to_try); + + _dbus_verbose ("%s: Trying mechanism %s\n", + DBUS_AUTH_NAME (auth), + mech->mechanism); + } + else + { + /* Give up */ + _dbus_verbose ("%s: Disconnecting because we are out of mechanisms to try using\n", + DBUS_AUTH_NAME (auth)); + goto_state (auth, &common_state_need_disconnect); + } + + return TRUE; +} + + +static dbus_bool_t +handle_client_state_waiting_for_data (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args) +{ + _dbus_assert (auth->mech != NULL); + + switch (command) + { + case DBUS_AUTH_COMMAND_DATA: + return process_data (auth, args, auth->mech->client_data_func); + + case DBUS_AUTH_COMMAND_REJECTED: + return process_rejected (auth, args); + + case DBUS_AUTH_COMMAND_OK: + return process_ok(auth, args); + + case DBUS_AUTH_COMMAND_ERROR: + return send_cancel (auth); + + case DBUS_AUTH_COMMAND_AUTH: + case DBUS_AUTH_COMMAND_CANCEL: + case DBUS_AUTH_COMMAND_BEGIN: + case DBUS_AUTH_COMMAND_UNKNOWN: + case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD: + case DBUS_AUTH_COMMAND_AGREE_UNIX_FD: + default: + return send_error (auth, "Unknown command"); + } +} + +static dbus_bool_t +handle_client_state_waiting_for_ok (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args) +{ + switch (command) + { + case DBUS_AUTH_COMMAND_REJECTED: + return process_rejected (auth, args); + + case DBUS_AUTH_COMMAND_OK: + return process_ok(auth, args); + + case DBUS_AUTH_COMMAND_DATA: + case DBUS_AUTH_COMMAND_ERROR: + return send_cancel (auth); + + case DBUS_AUTH_COMMAND_AUTH: + case DBUS_AUTH_COMMAND_CANCEL: + case DBUS_AUTH_COMMAND_BEGIN: + case DBUS_AUTH_COMMAND_UNKNOWN: + case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD: + case DBUS_AUTH_COMMAND_AGREE_UNIX_FD: + default: + return send_error (auth, "Unknown command"); + } +} + +static dbus_bool_t +handle_client_state_waiting_for_reject (DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args) +{ + switch (command) + { + case DBUS_AUTH_COMMAND_REJECTED: + return process_rejected (auth, args); + + case DBUS_AUTH_COMMAND_AUTH: + case DBUS_AUTH_COMMAND_CANCEL: + case DBUS_AUTH_COMMAND_DATA: + case DBUS_AUTH_COMMAND_BEGIN: + case DBUS_AUTH_COMMAND_OK: + case DBUS_AUTH_COMMAND_ERROR: + case DBUS_AUTH_COMMAND_UNKNOWN: + case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD: + case DBUS_AUTH_COMMAND_AGREE_UNIX_FD: + default: + goto_state (auth, &common_state_need_disconnect); + return TRUE; + } +} + +static dbus_bool_t +handle_client_state_waiting_for_agree_unix_fd(DBusAuth *auth, + DBusAuthCommand command, + const DBusString *args) +{ + switch (command) + { + case DBUS_AUTH_COMMAND_AGREE_UNIX_FD: + _dbus_assert(auth->unix_fd_possible); + auth->unix_fd_negotiated = TRUE; + _dbus_verbose("Successfully negotiated UNIX FD passing\n"); + return send_begin (auth); + + case DBUS_AUTH_COMMAND_ERROR: + _dbus_assert(auth->unix_fd_possible); + auth->unix_fd_negotiated = FALSE; + _dbus_verbose("Failed to negotiate UNIX FD passing\n"); + return send_begin (auth); + + case DBUS_AUTH_COMMAND_OK: + case DBUS_AUTH_COMMAND_DATA: + case DBUS_AUTH_COMMAND_REJECTED: + case DBUS_AUTH_COMMAND_AUTH: + case DBUS_AUTH_COMMAND_CANCEL: + case DBUS_AUTH_COMMAND_BEGIN: + case DBUS_AUTH_COMMAND_UNKNOWN: + case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD: + default: + return send_error (auth, "Unknown command"); + } +} + +/** + * Mapping from command name to enum + */ +typedef struct { + const char *name; /**< Name of the command */ + DBusAuthCommand command; /**< Corresponding enum */ +} DBusAuthCommandName; + +static const DBusAuthCommandName auth_command_names[] = { + { "AUTH", DBUS_AUTH_COMMAND_AUTH }, + { "CANCEL", DBUS_AUTH_COMMAND_CANCEL }, + { "DATA", DBUS_AUTH_COMMAND_DATA }, + { "BEGIN", DBUS_AUTH_COMMAND_BEGIN }, + { "REJECTED", DBUS_AUTH_COMMAND_REJECTED }, + { "OK", DBUS_AUTH_COMMAND_OK }, + { "ERROR", DBUS_AUTH_COMMAND_ERROR }, + { "NEGOTIATE_UNIX_FD", DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD }, + { "AGREE_UNIX_FD", DBUS_AUTH_COMMAND_AGREE_UNIX_FD } +}; + +static DBusAuthCommand +lookup_command_from_name (DBusString *command) +{ + int i; + + for (i = 0; i < _DBUS_N_ELEMENTS (auth_command_names); i++) + { + if (_dbus_string_equal_c_str (command, + auth_command_names[i].name)) + return auth_command_names[i].command; + } + + return DBUS_AUTH_COMMAND_UNKNOWN; +} + +static void +goto_state (DBusAuth *auth, + const DBusAuthStateData *state) +{ + _dbus_verbose ("%s: going from state %s to state %s\n", + DBUS_AUTH_NAME (auth), + auth->state->name, + state->name); + + auth->state = state; +} + +/* returns whether to call it again right away */ +static dbus_bool_t +process_command (DBusAuth *auth) +{ + DBusAuthCommand command; + DBusString line; + DBusString args; + int eol; + int i, j; + dbus_bool_t retval; + + /* _dbus_verbose ("%s: trying process_command()\n"); */ + + retval = FALSE; + + eol = 0; + if (!_dbus_string_find (&auth->incoming, 0, "\r\n", &eol)) + return FALSE; + + if (!_dbus_string_init (&line)) + { + auth->needed_memory = TRUE; + return FALSE; + } + + if (!_dbus_string_init (&args)) + { + _dbus_string_free (&line); + auth->needed_memory = TRUE; + return FALSE; + } + + if (!_dbus_string_copy_len (&auth->incoming, 0, eol, &line, 0)) + goto out; + + if (!_dbus_string_validate_ascii (&line, 0, + _dbus_string_get_length (&line))) + { + _dbus_verbose ("%s: Command contained non-ASCII chars or embedded nul\n", + DBUS_AUTH_NAME (auth)); + if (!send_error (auth, "Command contained non-ASCII")) + goto out; + else + goto next_command; + } + + _dbus_verbose ("%s: got command \"%s\"\n", + DBUS_AUTH_NAME (auth), + _dbus_string_get_const_data (&line)); + + _dbus_string_find_blank (&line, 0, &i); + _dbus_string_skip_blank (&line, i, &j); + + if (j > i) + _dbus_string_delete (&line, i, j - i); + + if (!_dbus_string_move (&line, i, &args, 0)) + goto out; + + /* FIXME 1.0 we should probably validate that only the allowed + * chars are in the command name + */ + + command = lookup_command_from_name (&line); + if (!(* auth->state->handler) (auth, command, &args)) + goto out; + + next_command: + + /* We've succeeded in processing the whole command so drop it out + * of the incoming buffer and return TRUE to try another command. + */ + + _dbus_string_delete (&auth->incoming, 0, eol); + + /* kill the \r\n */ + _dbus_string_delete (&auth->incoming, 0, 2); + + retval = TRUE; + + out: + _dbus_string_free (&args); + _dbus_string_free (&line); + + if (!retval) + auth->needed_memory = TRUE; + else + auth->needed_memory = FALSE; + + return retval; +} + + +/** @} */ + +/** + * @addtogroup DBusAuth + * @{ + */ + +/** + * Creates a new auth conversation object for the server side. + * See http://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol + * for full details on what this object does. + * + * @returns the new object or #NULL if no memory + */ +DBusAuth* +_dbus_auth_server_new (const DBusString *guid) +{ + DBusAuth *auth; + DBusAuthServer *server_auth; + DBusString guid_copy; + + if (!_dbus_string_init (&guid_copy)) + return NULL; + + if (!_dbus_string_copy (guid, 0, &guid_copy, 0)) + { + _dbus_string_free (&guid_copy); + return NULL; + } + + auth = _dbus_auth_new (sizeof (DBusAuthServer)); + if (auth == NULL) + { + _dbus_string_free (&guid_copy); + return NULL; + } + + auth->side = auth_side_server; + auth->state = &server_state_waiting_for_auth; + + server_auth = DBUS_AUTH_SERVER (auth); + + server_auth->guid = guid_copy; + + /* perhaps this should be per-mechanism with a lower + * max + */ + server_auth->failures = 0; + server_auth->max_failures = 6; + + return auth; +} + +/** + * Creates a new auth conversation object for the client side. + * See http://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol + * for full details on what this object does. + * + * @returns the new object or #NULL if no memory + */ +DBusAuth* +_dbus_auth_client_new (void) +{ + DBusAuth *auth; + DBusString guid_str; + + if (!_dbus_string_init (&guid_str)) + return NULL; + + auth = _dbus_auth_new (sizeof (DBusAuthClient)); + if (auth == NULL) + { + _dbus_string_free (&guid_str); + return NULL; + } + + DBUS_AUTH_CLIENT (auth)->guid_from_server = guid_str; + + auth->side = auth_side_client; + auth->state = &client_state_need_send_auth; + + /* Start the auth conversation by sending AUTH for our default + * mechanism */ + if (!send_auth (auth, &all_mechanisms[0])) + { + _dbus_auth_unref (auth); + return NULL; + } + + return auth; +} + +/** + * Increments the refcount of an auth object. + * + * @param auth the auth conversation + * @returns the auth conversation + */ +DBusAuth * +_dbus_auth_ref (DBusAuth *auth) +{ + _dbus_assert (auth != NULL); + + auth->refcount += 1; + + return auth; +} + +/** + * Decrements the refcount of an auth object. + * + * @param auth the auth conversation + */ +void +_dbus_auth_unref (DBusAuth *auth) +{ + _dbus_assert (auth != NULL); + _dbus_assert (auth->refcount > 0); + + auth->refcount -= 1; + if (auth->refcount == 0) + { + shutdown_mech (auth); + + if (DBUS_AUTH_IS_CLIENT (auth)) + { + _dbus_string_free (& DBUS_AUTH_CLIENT (auth)->guid_from_server); + _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try); + } + else + { + _dbus_assert (DBUS_AUTH_IS_SERVER (auth)); + + _dbus_string_free (& DBUS_AUTH_SERVER (auth)->guid); + } + + if (auth->keyring) + _dbus_keyring_unref (auth->keyring); + + _dbus_string_free (&auth->context); + _dbus_string_free (&auth->challenge); + _dbus_string_free (&auth->identity); + _dbus_string_free (&auth->incoming); + _dbus_string_free (&auth->outgoing); + + dbus_free_string_array (auth->allowed_mechs); + + _dbus_credentials_unref (auth->credentials); + _dbus_credentials_unref (auth->authorized_identity); + _dbus_credentials_unref (auth->desired_identity); + + dbus_free (auth); + } +} + +/** + * Sets an array of authentication mechanism names + * that we are willing to use. + * + * @param auth the auth conversation + * @param mechanisms #NULL-terminated array of mechanism names + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_auth_set_mechanisms (DBusAuth *auth, + const char **mechanisms) +{ + char **copy; + + if (mechanisms != NULL) + { + copy = _dbus_dup_string_array (mechanisms); + if (copy == NULL) + return FALSE; + } + else + copy = NULL; + + dbus_free_string_array (auth->allowed_mechs); + + auth->allowed_mechs = copy; + + return TRUE; +} + +/** + * @param auth the auth conversation object + * @returns #TRUE if we're in a final state + */ +#define DBUS_AUTH_IN_END_STATE(auth) ((auth)->state->handler == NULL) + +/** + * Analyzes buffered input and moves the auth conversation forward, + * returning the new state of the auth conversation. + * + * @param auth the auth conversation + * @returns the new state + */ +DBusAuthState +_dbus_auth_do_work (DBusAuth *auth) +{ + auth->needed_memory = FALSE; + + /* Max amount we'll buffer up before deciding someone's on crack */ +#define MAX_BUFFER (16 * _DBUS_ONE_KILOBYTE) + + do + { + if (DBUS_AUTH_IN_END_STATE (auth)) + break; + + if (_dbus_string_get_length (&auth->incoming) > MAX_BUFFER || + _dbus_string_get_length (&auth->outgoing) > MAX_BUFFER) + { + goto_state (auth, &common_state_need_disconnect); + _dbus_verbose ("%s: Disconnecting due to excessive data buffered in auth phase\n", + DBUS_AUTH_NAME (auth)); + break; + } + } + while (process_command (auth)); + + if (auth->needed_memory) + return DBUS_AUTH_STATE_WAITING_FOR_MEMORY; + else if (_dbus_string_get_length (&auth->outgoing) > 0) + return DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND; + else if (auth->state == &common_state_need_disconnect) + return DBUS_AUTH_STATE_NEED_DISCONNECT; + else if (auth->state == &common_state_authenticated) + return DBUS_AUTH_STATE_AUTHENTICATED; + else return DBUS_AUTH_STATE_WAITING_FOR_INPUT; +} + +/** + * Gets bytes that need to be sent to the peer we're conversing with. + * After writing some bytes, _dbus_auth_bytes_sent() must be called + * to notify the auth object that they were written. + * + * @param auth the auth conversation + * @param str return location for a ref to the buffer to send + * @returns #FALSE if nothing to send + */ +dbus_bool_t +_dbus_auth_get_bytes_to_send (DBusAuth *auth, + const DBusString **str) +{ + _dbus_assert (auth != NULL); + _dbus_assert (str != NULL); + + *str = NULL; + + if (_dbus_string_get_length (&auth->outgoing) == 0) + return FALSE; + + *str = &auth->outgoing; + + return TRUE; +} + +/** + * Notifies the auth conversation object that + * the given number of bytes of the outgoing buffer + * have been written out. + * + * @param auth the auth conversation + * @param bytes_sent number of bytes written out + */ +void +_dbus_auth_bytes_sent (DBusAuth *auth, + int bytes_sent) +{ + _dbus_verbose ("%s: Sent %d bytes of: %s\n", + DBUS_AUTH_NAME (auth), + bytes_sent, + _dbus_string_get_const_data (&auth->outgoing)); + + _dbus_string_delete (&auth->outgoing, + 0, bytes_sent); +} + +/** + * Get a buffer to be used for reading bytes from the peer we're conversing + * with. Bytes should be appended to this buffer. + * + * @param auth the auth conversation + * @param buffer return location for buffer to append bytes to + */ +void +_dbus_auth_get_buffer (DBusAuth *auth, + DBusString **buffer) +{ + _dbus_assert (auth != NULL); + _dbus_assert (!auth->buffer_outstanding); + + *buffer = &auth->incoming; + + auth->buffer_outstanding = TRUE; +} + +/** + * Returns a buffer with new data read into it. + * + * @param auth the auth conversation + * @param buffer the buffer being returned + */ +void +_dbus_auth_return_buffer (DBusAuth *auth, + DBusString *buffer) +{ + _dbus_assert (buffer == &auth->incoming); + _dbus_assert (auth->buffer_outstanding); + + auth->buffer_outstanding = FALSE; +} + +/** + * Returns leftover bytes that were not used as part of the auth + * conversation. These bytes will be part of the message stream + * instead. This function may not be called until authentication has + * succeeded. + * + * @param auth the auth conversation + * @param str return location for pointer to string of unused bytes + */ +void +_dbus_auth_get_unused_bytes (DBusAuth *auth, + const DBusString **str) +{ + if (!DBUS_AUTH_IN_END_STATE (auth)) + return; + + *str = &auth->incoming; +} + + +/** + * Gets rid of unused bytes returned by _dbus_auth_get_unused_bytes() + * after we've gotten them and successfully moved them elsewhere. + * + * @param auth the auth conversation + */ +void +_dbus_auth_delete_unused_bytes (DBusAuth *auth) +{ + if (!DBUS_AUTH_IN_END_STATE (auth)) + return; + + _dbus_string_set_length (&auth->incoming, 0); +} + +/** + * Called post-authentication, indicates whether we need to encode + * the message stream with _dbus_auth_encode_data() prior to + * sending it to the peer. + * + * @param auth the auth conversation + * @returns #TRUE if we need to encode the stream + */ +dbus_bool_t +_dbus_auth_needs_encoding (DBusAuth *auth) +{ + if (auth->state != &common_state_authenticated) + return FALSE; + + if (auth->mech != NULL) + { + if (DBUS_AUTH_IS_CLIENT (auth)) + return auth->mech->client_encode_func != NULL; + else + return auth->mech->server_encode_func != NULL; + } + else + return FALSE; +} + +/** + * Called post-authentication, encodes a block of bytes for sending to + * the peer. If no encoding was negotiated, just copies the bytes + * (you can avoid this by checking _dbus_auth_needs_encoding()). + * + * @param auth the auth conversation + * @param plaintext the plain text data + * @param encoded initialized string to where encoded data is appended + * @returns #TRUE if we had enough memory and successfully encoded + */ +dbus_bool_t +_dbus_auth_encode_data (DBusAuth *auth, + const DBusString *plaintext, + DBusString *encoded) +{ + _dbus_assert (plaintext != encoded); + + if (auth->state != &common_state_authenticated) + return FALSE; + + if (_dbus_auth_needs_encoding (auth)) + { + if (DBUS_AUTH_IS_CLIENT (auth)) + return (* auth->mech->client_encode_func) (auth, plaintext, encoded); + else + return (* auth->mech->server_encode_func) (auth, plaintext, encoded); + } + else + { + return _dbus_string_copy (plaintext, 0, encoded, + _dbus_string_get_length (encoded)); + } +} + +/** + * Called post-authentication, indicates whether we need to decode + * the message stream with _dbus_auth_decode_data() after + * receiving it from the peer. + * + * @param auth the auth conversation + * @returns #TRUE if we need to encode the stream + */ +dbus_bool_t +_dbus_auth_needs_decoding (DBusAuth *auth) +{ + if (auth->state != &common_state_authenticated) + return FALSE; + + if (auth->mech != NULL) + { + if (DBUS_AUTH_IS_CLIENT (auth)) + return auth->mech->client_decode_func != NULL; + else + return auth->mech->server_decode_func != NULL; + } + else + return FALSE; +} + + +/** + * Called post-authentication, decodes a block of bytes received from + * the peer. If no encoding was negotiated, just copies the bytes (you + * can avoid this by checking _dbus_auth_needs_decoding()). + * + * @todo 1.0? We need to be able to distinguish "out of memory" error + * from "the data is hosed" error. + * + * @param auth the auth conversation + * @param encoded the encoded data + * @param plaintext initialized string where decoded data is appended + * @returns #TRUE if we had enough memory and successfully decoded + */ +dbus_bool_t +_dbus_auth_decode_data (DBusAuth *auth, + const DBusString *encoded, + DBusString *plaintext) +{ + _dbus_assert (plaintext != encoded); + + if (auth->state != &common_state_authenticated) + return FALSE; + + if (_dbus_auth_needs_decoding (auth)) + { + if (DBUS_AUTH_IS_CLIENT (auth)) + return (* auth->mech->client_decode_func) (auth, encoded, plaintext); + else + return (* auth->mech->server_decode_func) (auth, encoded, plaintext); + } + else + { + return _dbus_string_copy (encoded, 0, plaintext, + _dbus_string_get_length (plaintext)); + } +} + +/** + * Sets credentials received via reliable means from the operating + * system. + * + * @param auth the auth conversation + * @param credentials the credentials received + * @returns #FALSE on OOM + */ +dbus_bool_t +_dbus_auth_set_credentials (DBusAuth *auth, + DBusCredentials *credentials) +{ + _dbus_credentials_clear (auth->credentials); + return _dbus_credentials_add_credentials (auth->credentials, + credentials); +} + +/** + * Gets the identity we authorized the client as. Apps may have + * different policies as to what identities they allow. + * + * Returned credentials are not a copy and should not be modified + * + * @param auth the auth conversation + * @returns the credentials we've authorized BY REFERENCE do not modify + */ +DBusCredentials* +_dbus_auth_get_identity (DBusAuth *auth) +{ + if (auth->state == &common_state_authenticated) + { + return auth->authorized_identity; + } + else + { + /* FIXME instead of this, keep an empty credential around that + * doesn't require allocation or something + */ + /* return empty credentials */ + _dbus_assert (_dbus_credentials_are_empty (auth->authorized_identity)); + return auth->authorized_identity; + } +} + +/** + * Gets the GUID from the server if we've authenticated; gets + * #NULL otherwise. + * @param auth the auth object + * @returns the GUID in ASCII hex format + */ +const char* +_dbus_auth_get_guid_from_server (DBusAuth *auth) +{ + _dbus_assert (DBUS_AUTH_IS_CLIENT (auth)); + + if (auth->state == &common_state_authenticated) + return _dbus_string_get_const_data (& DBUS_AUTH_CLIENT (auth)->guid_from_server); + else + return NULL; +} + +/** + * Sets the "authentication context" which scopes cookies + * with the DBUS_COOKIE_SHA1 auth mechanism for example. + * + * @param auth the auth conversation + * @param context the context + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_auth_set_context (DBusAuth *auth, + const DBusString *context) +{ + return _dbus_string_replace_len (context, 0, _dbus_string_get_length (context), + &auth->context, 0, _dbus_string_get_length (context)); +} + +/** + * Sets whether unix fd passing is potentially on the transport and + * hence shall be negotiated. + * + * @param auth the auth conversation + * @param b TRUE when unix fd passing shall be negotiated, otherwise FALSE + */ +void +_dbus_auth_set_unix_fd_possible(DBusAuth *auth, dbus_bool_t b) +{ + auth->unix_fd_possible = b; +} + +/** + * Queries whether unix fd passing was successfully negotiated. + * + * @param auth the auth conversion + * @returns #TRUE when unix fd passing was negotiated. + */ +dbus_bool_t +_dbus_auth_get_unix_fd_negotiated(DBusAuth *auth) +{ + return auth->unix_fd_negotiated; +} + +/** + * Queries whether the given auth mechanism is supported. + * + * @param auth the auth mechanism to query for + * @returns #TRUE when auth mechanism is supported + */ +dbus_bool_t +_dbus_auth_is_supported_mechanism (DBusString *name) +{ + _dbus_assert (name != NULL); + + return find_mech (name, NULL) != NULL; +} + +/** + * Return a human-readable string containing all supported auth mechanisms. + * + * @param string to hold the supported auth mechanisms + * @returns #FALSE on oom + */ +dbus_bool_t +_dbus_auth_dump_supported_mechanisms (DBusString *buffer) +{ + unsigned int i; + _dbus_assert (buffer != NULL); + + for (i = 0; all_mechanisms[i].mechanism != NULL; i++) + { + if (i > 0) + { + if (!_dbus_string_append (buffer, ", ")) + return FALSE; + } + if (!_dbus_string_append (buffer, all_mechanisms[i].mechanism)) + return FALSE; + } + return TRUE; +} + +/** @} */ + +/* tests in dbus-auth-util.c */ diff --git a/src/3rdparty/libdbus/dbus/dbus-auth.h b/src/3rdparty/libdbus/dbus/dbus-auth.h new file mode 100644 index 00000000..4612d5a8 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-auth.h @@ -0,0 +1,104 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-auth.h Authentication + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_AUTH_H +#define DBUS_AUTH_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-errors.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-sysdeps.h> + +DBUS_BEGIN_DECLS + +typedef struct DBusAuth DBusAuth; + +typedef enum +{ + DBUS_AUTH_STATE_WAITING_FOR_INPUT, + DBUS_AUTH_STATE_WAITING_FOR_MEMORY, + DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND, + DBUS_AUTH_STATE_NEED_DISCONNECT, + DBUS_AUTH_STATE_AUTHENTICATED, + DBUS_AUTH_STATE_INVALID = -1 +} DBusAuthState; + +DBUS_PRIVATE_EXPORT +DBusAuth* _dbus_auth_server_new (const DBusString *guid); +DBUS_PRIVATE_EXPORT +DBusAuth* _dbus_auth_client_new (void); +DBUS_PRIVATE_EXPORT +DBusAuth* _dbus_auth_ref (DBusAuth *auth); +DBUS_PRIVATE_EXPORT +void _dbus_auth_unref (DBusAuth *auth); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_auth_set_mechanisms (DBusAuth *auth, + const char **mechanisms); +DBUS_PRIVATE_EXPORT +DBusAuthState _dbus_auth_do_work (DBusAuth *auth); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_auth_get_bytes_to_send (DBusAuth *auth, + const DBusString **str); +DBUS_PRIVATE_EXPORT +void _dbus_auth_bytes_sent (DBusAuth *auth, + int bytes_sent); +DBUS_PRIVATE_EXPORT +void _dbus_auth_get_buffer (DBusAuth *auth, + DBusString **buffer); +DBUS_PRIVATE_EXPORT +void _dbus_auth_return_buffer (DBusAuth *auth, + DBusString *buffer); +DBUS_PRIVATE_EXPORT +void _dbus_auth_get_unused_bytes (DBusAuth *auth, + const DBusString **str); +DBUS_PRIVATE_EXPORT +void _dbus_auth_delete_unused_bytes (DBusAuth *auth); +dbus_bool_t _dbus_auth_needs_encoding (DBusAuth *auth); +dbus_bool_t _dbus_auth_encode_data (DBusAuth *auth, + const DBusString *plaintext, + DBusString *encoded); +dbus_bool_t _dbus_auth_needs_decoding (DBusAuth *auth); +dbus_bool_t _dbus_auth_decode_data (DBusAuth *auth, + const DBusString *encoded, + DBusString *plaintext); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_auth_set_credentials (DBusAuth *auth, + DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +DBusCredentials* _dbus_auth_get_identity (DBusAuth *auth); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_auth_set_context (DBusAuth *auth, + const DBusString *context); +const char* _dbus_auth_get_guid_from_server(DBusAuth *auth); + +void _dbus_auth_set_unix_fd_possible(DBusAuth *auth, dbus_bool_t b); +dbus_bool_t _dbus_auth_get_unix_fd_negotiated(DBusAuth *auth); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_auth_is_supported_mechanism(DBusString *name); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_auth_dump_supported_mechanisms(DBusString *buffer); + +DBUS_END_DECLS + +#endif /* DBUS_AUTH_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-backtrace-win.c b/src/3rdparty/libdbus/dbus/dbus-backtrace-win.c new file mode 100644 index 00000000..d9bc2dc4 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-backtrace-win.c @@ -0,0 +1,213 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-backtrace-win.c Backtrace Generator + * + * Copyright 2004 Eric Poech + * Copyright 2004 Robert Shearman + * Copyright 2010 Patrick von Reth <patrick.vonreth@gmail.com> + * Copyright 2015 Ralf Habacker <ralf.habacker@freenet.de> + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <config.h> + +#include "dbus-internals.h" +#include "dbus-sysdeps.h" + +#if !defined (DBUS_DISABLE_ASSERT) || defined(DBUS_ENABLE_EMBEDDED_TESTS) + +#if defined(_MSC_VER) || defined(DBUS_WINCE) +# ifdef BACKTRACES +# undef BACKTRACES +# endif +#else +# define BACKTRACES +#endif + +#ifdef BACKTRACES +#include "dbus-sysdeps-win.h" + +#include <stdio.h> +#include <windows.h> +#include <imagehlp.h> + +#define DPRINTF(fmt, ...) fprintf (stderr, fmt, ##__VA_ARGS__) + +#ifdef _MSC_VER +#define BOOL int + +#define __i386__ +#endif + +static void dump_backtrace_for_thread (HANDLE hThread) +{ + ADDRESS old_address; + STACKFRAME sf; + CONTEXT context; + DWORD dwImageType; + int i = 0; + + SymSetOptions (SYMOPT_UNDNAME | SYMOPT_LOAD_LINES); + SymInitialize (GetCurrentProcess (), NULL, TRUE); + + + /* can't use this function for current thread as GetThreadContext + * doesn't support getting context from current thread */ + if (hThread == GetCurrentThread()) + return; + + DPRINTF ("Backtrace:\n"); + + _DBUS_ZERO (old_address); + _DBUS_ZERO (context); + context.ContextFlags = CONTEXT_FULL; + + SuspendThread (hThread); + + if (!GetThreadContext (hThread, &context)) + { + DPRINTF ("Couldn't get thread context (error %ld)\n", GetLastError ()); + ResumeThread (hThread); + return; + } + + _DBUS_ZERO (sf); + +#ifdef __i386__ + dwImageType = IMAGE_FILE_MACHINE_I386; + sf.AddrFrame.Offset = context.Ebp; + sf.AddrFrame.Mode = AddrModeFlat; + sf.AddrPC.Offset = context.Eip; + sf.AddrPC.Mode = AddrModeFlat; +#elif defined(_M_X64) + dwImageType = IMAGE_FILE_MACHINE_AMD64; + sf.AddrPC.Offset = context.Rip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrFrame.Offset = context.Rsp; + sf.AddrFrame.Mode = AddrModeFlat; + sf.AddrStack.Offset = context.Rsp; + sf.AddrStack.Mode = AddrModeFlat; +#elif defined(_M_IA64) + dwImageType = IMAGE_FILE_MACHINE_IA64; + sf.AddrPC.Offset = context.StIIP; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrFrame.Offset = context.IntSp; + sf.AddrFrame.Mode = AddrModeFlat; + sf.AddrBStore.Offset= context.RsBSP; + sf.AddrBStore.Mode = AddrModeFlat; + sf.AddrStack.Offset = context.IntSp; + sf.AddrStack.Mode = AddrModeFlat; +#else +# error You need to fill in the STACKFRAME structure for your architecture +#endif + + /* + backtrace format + <level> <address> <symbol>[+offset] [ '[' <file> ':' <line> ']' ] [ 'in' <module> ] + example: + 6 0xf75ade6b wine_switch_to_stack+0x2a [/usr/src/debug/wine-snapshot/libs/wine/port.c:59] in libwine.so.1 + */ + while (StackWalk (dwImageType, GetCurrentProcess (), + hThread, &sf, &context, NULL, SymFunctionTableAccess, + SymGetModuleBase, NULL)) + { + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(char)]; + PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; + DWORD64 displacement; + IMAGEHLP_LINE line; + DWORD dwDisplacement; + IMAGEHLP_MODULE moduleInfo; + + /* + on Wine64 version 1.7.54, we get an infinite number of stack entries + pointing to the same stack frame (_start+0x29 in <wine-loader>) + see bug https://bugs.winehq.org/show_bug.cgi?id=39606 + */ +#ifndef __i386__ + if (old_address.Offset == sf.AddrPC.Offset) + { + break; + } +#endif + + pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbol->MaxNameLen = MAX_SYM_NAME; + + if (SymFromAddr (GetCurrentProcess (), sf.AddrPC.Offset, &displacement, pSymbol)) + { + if (displacement) + DPRINTF ("%3d %s+0x%I64x", i++, pSymbol->Name, displacement); + else + DPRINTF ("%3d %s", i++, pSymbol->Name); + } + else + DPRINTF ("%3d 0x%Ix", i++, sf.AddrPC.Offset); + + line.SizeOfStruct = sizeof(IMAGEHLP_LINE); + if (SymGetLineFromAddr (GetCurrentProcess (), sf.AddrPC.Offset, &dwDisplacement, &line)) + { + DPRINTF (" [%s:%ld]", line.FileName, line.LineNumber); + } + + moduleInfo.SizeOfStruct = sizeof(moduleInfo); + if (SymGetModuleInfo (GetCurrentProcess (), sf.AddrPC.Offset, &moduleInfo)) + { + DPRINTF (" in %s", moduleInfo.ModuleName); + } + DPRINTF ("\n"); + old_address = sf.AddrPC; + } + ResumeThread (hThread); +} + +static DWORD WINAPI dump_thread_proc (LPVOID lpParameter) +{ + dump_backtrace_for_thread ((HANDLE) lpParameter); + return 0; +} + +/* cannot get valid context from current thread, so we have to execute + * backtrace from another thread */ +static void +dump_backtrace (void) +{ + HANDLE hCurrentThread; + HANDLE hThread; + DWORD dwThreadId; + DuplicateHandle (GetCurrentProcess (), GetCurrentThread (), + GetCurrentProcess (), &hCurrentThread, + 0, FALSE, DUPLICATE_SAME_ACCESS); + hThread = CreateThread (NULL, 0, dump_thread_proc, (LPVOID)hCurrentThread, + 0, &dwThreadId); + WaitForSingleObject (hThread, INFINITE); + CloseHandle (hThread); + CloseHandle (hCurrentThread); +} +#endif +#endif /* asserts or tests enabled */ + +#ifdef BACKTRACES +void _dbus_print_backtrace (void) +{ + dump_backtrace (); +} +#else +void _dbus_print_backtrace (void) +{ + _dbus_verbose (" D-Bus not compiled with backtrace support\n"); +} +#endif diff --git a/src/3rdparty/libdbus/dbus/dbus-bus.c b/src/3rdparty/libdbus/dbus/dbus-bus.c new file mode 100644 index 00000000..f0c790df --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-bus.c @@ -0,0 +1,1605 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-bus.c Convenience functions for communicating with the bus. + * + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2003 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-bus.h" +#include "dbus-protocol.h" +#include "dbus-internals.h" +#include "dbus-message.h" +#include "dbus-marshal-validate.h" +#include "dbus-misc.h" +#include "dbus-threads-internal.h" +#include "dbus-connection-internal.h" +#include "dbus-string.h" + +/** + * @defgroup DBusBus Message bus APIs + * @ingroup DBus + * @brief Functions for communicating with the message bus + * + * dbus_bus_get() allows all modules and libraries in a given + * process to share the same connection to the bus daemon by storing + * the connection globally. + * + * All other functions in this module are just convenience functions; + * most of them invoke methods on the bus daemon, by sending method + * call messages to #DBUS_SERVICE_DBUS. These convenience functions + * often make blocking method calls. If you don't want to block, + * you can send the method call messages manually in the same way + * you would any other method call message. + * + * This module is the only one in libdbus that's specific to + * communicating with the message bus daemon. The rest of the API can + * also be used for connecting to another application directly. + * + * @todo right now the default address of the system bus is hardcoded, + * so if you change it in the global config file suddenly you have to + * set DBUS_SYSTEM_BUS_ADDRESS env variable. Might be nice if the + * client lib somehow read the config file, or if the bus on startup + * somehow wrote out its address to a well-known spot, but might also + * not be worth it. + */ + +/** + * @defgroup DBusBusInternals Message bus APIs internals + * @ingroup DBusInternals + * @brief Internals of functions for communicating with the message bus + * + * @{ + */ + +/** + * Block of message-bus-related data we attach to each + * #DBusConnection used with these convenience functions. + * + */ +typedef struct +{ + DBusConnection *connection; /**< Connection we're associated with */ + char *unique_name; /**< Unique name of this connection */ + + unsigned int is_well_known : 1; /**< Is one of the well-known connections in our global array */ +} BusData; + +/** The slot we have reserved to store BusData. + * Protected by _DBUS_LOCK_connection_slots. + */ +static dbus_int32_t bus_data_slot = -1; + +/** Number of bus types */ +#define N_BUS_TYPES 3 + +/* Protected by _DBUS_LOCK_bus, except during shutdown, which can't safely + * be done in a threaded application anyway. */ +static DBusConnection *bus_connections[N_BUS_TYPES]; +static char *bus_connection_addresses[N_BUS_TYPES] = { NULL, NULL, NULL }; +static DBusBusType activation_bus_type = DBUS_BUS_STARTER; +static dbus_bool_t initialized = FALSE; + +static void +addresses_shutdown_func (void *data) +{ + int i; + + i = 0; + while (i < N_BUS_TYPES) + { + if (bus_connections[i] != NULL) + _dbus_warn_check_failed ("dbus_shutdown() called but connections were still live. This probably means the application did not drop all its references to bus connections."); + + dbus_free (bus_connection_addresses[i]); + bus_connection_addresses[i] = NULL; + ++i; + } + + activation_bus_type = DBUS_BUS_STARTER; + + initialized = FALSE; +} + +static dbus_bool_t +get_from_env (char **connection_p, + const char *env_var) +{ + const char *s; + + _dbus_assert (*connection_p == NULL); + + s = _dbus_getenv (env_var); + if (s == NULL || *s == '\0') + return TRUE; /* successfully didn't use the env var */ + else + { + *connection_p = _dbus_strdup (s); + return *connection_p != NULL; + } +} + +static dbus_bool_t +init_session_address (void) +{ + dbus_bool_t retval; + + retval = FALSE; + + /* First, look in the environment. This is the normal case on + * freedesktop.org/Unix systems. */ + get_from_env (&bus_connection_addresses[DBUS_BUS_SESSION], + "DBUS_SESSION_BUS_ADDRESS"); + if (bus_connection_addresses[DBUS_BUS_SESSION] == NULL) + { + dbus_bool_t supported; + DBusString addr; + DBusError error = DBUS_ERROR_INIT; + + if (!_dbus_string_init (&addr)) + return FALSE; + + supported = FALSE; + /* So it's not in the environment - let's try a platform-specific method. + * On MacOS, this involves asking launchd. On Windows (not specified yet) + * we might do a COM lookup. + * Ignore errors - if we failed, fall back to autolaunch. */ + retval = _dbus_lookup_session_address (&supported, &addr, &error); + if (supported && retval) + { + retval =_dbus_string_steal_data (&addr, &bus_connection_addresses[DBUS_BUS_SESSION]); + } + else if (supported && !retval) + { + if (dbus_error_is_set(&error)) + _dbus_warn ("Dynamic session lookup supported but failed: %s", error.message); + else + _dbus_warn ("Dynamic session lookup supported but failed silently"); + } + _dbus_string_free (&addr); + } + else + retval = TRUE; + + if (!retval) + return FALSE; + + /* We have a hard-coded (but compile-time-configurable) fallback address for + * the session bus. */ + if (bus_connection_addresses[DBUS_BUS_SESSION] == NULL) + bus_connection_addresses[DBUS_BUS_SESSION] = + _dbus_strdup (DBUS_SESSION_BUS_CONNECT_ADDRESS); + + if (bus_connection_addresses[DBUS_BUS_SESSION] == NULL) + return FALSE; + + return TRUE; +} + +static dbus_bool_t +init_connections_unlocked (void) +{ + if (!initialized) + { + const char *s; + int i; + + i = 0; + while (i < N_BUS_TYPES) + { + bus_connections[i] = NULL; + ++i; + } + + /* Don't init these twice, we may run this code twice if + * init_connections_unlocked() fails midway through. + * In practice, each block below should contain only one + * "return FALSE" or running through twice may not + * work right. + */ + + if (bus_connection_addresses[DBUS_BUS_SYSTEM] == NULL) + { + _dbus_verbose ("Filling in system bus address...\n"); + + if (!get_from_env (&bus_connection_addresses[DBUS_BUS_SYSTEM], + "DBUS_SYSTEM_BUS_ADDRESS")) + return FALSE; + } + + + if (bus_connection_addresses[DBUS_BUS_SYSTEM] == NULL) + { + /* Use default system bus address if none set in environment */ + bus_connection_addresses[DBUS_BUS_SYSTEM] = + _dbus_strdup (DBUS_SYSTEM_BUS_DEFAULT_ADDRESS); + + if (bus_connection_addresses[DBUS_BUS_SYSTEM] == NULL) + return FALSE; + + _dbus_verbose (" used default system bus \"%s\"\n", + bus_connection_addresses[DBUS_BUS_SYSTEM]); + } + else + _dbus_verbose (" used env var system bus \"%s\"\n", + bus_connection_addresses[DBUS_BUS_SYSTEM]); + + if (bus_connection_addresses[DBUS_BUS_SESSION] == NULL) + { + _dbus_verbose ("Filling in session bus address...\n"); + + if (!init_session_address ()) + return FALSE; + + _dbus_verbose (" \"%s\"\n", bus_connection_addresses[DBUS_BUS_SESSION] ? + bus_connection_addresses[DBUS_BUS_SESSION] : "none set"); + } + + if (bus_connection_addresses[DBUS_BUS_STARTER] == NULL) + { + _dbus_verbose ("Filling in activation bus address...\n"); + + if (!get_from_env (&bus_connection_addresses[DBUS_BUS_STARTER], + "DBUS_STARTER_ADDRESS")) + return FALSE; + + _dbus_verbose (" \"%s\"\n", bus_connection_addresses[DBUS_BUS_STARTER] ? + bus_connection_addresses[DBUS_BUS_STARTER] : "none set"); + } + + + if (bus_connection_addresses[DBUS_BUS_STARTER] != NULL) + { + s = _dbus_getenv ("DBUS_STARTER_BUS_TYPE"); + + if (s != NULL) + { + _dbus_verbose ("Bus activation type was set to \"%s\"\n", s); + + if (strcmp (s, "system") == 0) + activation_bus_type = DBUS_BUS_SYSTEM; + else if (strcmp (s, "session") == 0) + activation_bus_type = DBUS_BUS_SESSION; + } + } + else + { + /* Default to the session bus instead if available */ + if (bus_connection_addresses[DBUS_BUS_SESSION] != NULL) + { + bus_connection_addresses[DBUS_BUS_STARTER] = + _dbus_strdup (bus_connection_addresses[DBUS_BUS_SESSION]); + if (bus_connection_addresses[DBUS_BUS_STARTER] == NULL) + return FALSE; + } + } + + /* If we return FALSE we have to be sure that restarting + * the above code will work right + */ + + if (!_dbus_register_shutdown_func (addresses_shutdown_func, + NULL)) + return FALSE; + + initialized = TRUE; + } + + return initialized; +} + +static void +bus_data_free (void *data) +{ + BusData *bd = data; + + if (bd->is_well_known) + { + int i; + + if (!_DBUS_LOCK (bus)) + _dbus_assert_not_reached ("global locks should have been initialized " + "when we attached bus data"); + + /* We may be stored in more than one slot */ + /* This should now be impossible - these slots are supposed to + * be cleared on disconnect, so should not need to be cleared on + * finalize + */ + i = 0; + while (i < N_BUS_TYPES) + { + if (bus_connections[i] == bd->connection) + bus_connections[i] = NULL; + + ++i; + } + _DBUS_UNLOCK (bus); + } + + dbus_free (bd->unique_name); + dbus_free (bd); + + dbus_connection_free_data_slot (&bus_data_slot); +} + +static BusData* +ensure_bus_data (DBusConnection *connection) +{ + BusData *bd; + + if (!dbus_connection_allocate_data_slot (&bus_data_slot)) + return NULL; + + bd = dbus_connection_get_data (connection, bus_data_slot); + if (bd == NULL) + { + bd = dbus_new0 (BusData, 1); + if (bd == NULL) + { + dbus_connection_free_data_slot (&bus_data_slot); + return NULL; + } + + bd->connection = connection; + + if (!dbus_connection_set_data (connection, bus_data_slot, bd, + bus_data_free)) + { + dbus_free (bd); + dbus_connection_free_data_slot (&bus_data_slot); + return NULL; + } + + /* Data slot refcount now held by the BusData */ + } + else + { + dbus_connection_free_data_slot (&bus_data_slot); + } + + return bd; +} + +/** + * Internal function that checks to see if this + * is a shared connection owned by the bus and if it is unref it. + * + * @param connection a connection that has been disconnected. + */ +void +_dbus_bus_notify_shared_connection_disconnected_unlocked (DBusConnection *connection) +{ + int i; + + if (!_DBUS_LOCK (bus)) + { + /* If it was in bus_connections, we would have initialized global locks + * when we added it. So, it can't be. */ + return; + } + + /* We are expecting to have the connection saved in only one of these + * slots, but someone could in a pathological case set system and session + * bus to the same bus or something. Or set one of them to the starter + * bus without setting the starter bus type in the env variable. + * So we don't break the loop as soon as we find a match. + */ + for (i = 0; i < N_BUS_TYPES; ++i) + { + if (bus_connections[i] == connection) + { + bus_connections[i] = NULL; + } + } + + _DBUS_UNLOCK (bus); +} + +static DBusConnection * +internal_bus_get (DBusBusType type, + dbus_bool_t private, + DBusError *error) +{ + const char *address; + DBusConnection *connection; + BusData *bd; + DBusBusType address_type; + + _dbus_return_val_if_fail (type >= 0 && type < N_BUS_TYPES, NULL); + _dbus_return_val_if_error_is_set (error, NULL); + + connection = NULL; + + if (!_DBUS_LOCK (bus)) + { + _DBUS_SET_OOM (error); + /* do not "goto out", that would try to unlock */ + return NULL; + } + + if (!init_connections_unlocked ()) + { + _DBUS_SET_OOM (error); + goto out; + } + + /* We want to use the activation address even if the + * activating bus is the session or system bus, + * per the spec. + */ + address_type = type; + + /* Use the real type of the activation bus for getting its + * connection, but only if the real type's address is available. (If + * the activating bus isn't a well-known bus then + * activation_bus_type == DBUS_BUS_STARTER) + */ + if (type == DBUS_BUS_STARTER && + bus_connection_addresses[activation_bus_type] != NULL) + type = activation_bus_type; + + if (!private && bus_connections[type] != NULL) + { + connection = bus_connections[type]; + dbus_connection_ref (connection); + goto out; + } + + address = bus_connection_addresses[address_type]; + if (address == NULL) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Unable to determine the address of the message bus (try 'man dbus-launch' and 'man dbus-daemon' for help)"); + goto out; + } + + if (private) + connection = dbus_connection_open_private (address, error); + else + connection = dbus_connection_open (address, error); + + if (!connection) + { + goto out; + } + + if (!dbus_bus_register (connection, error)) + { + _dbus_connection_close_possibly_shared (connection); + dbus_connection_unref (connection); + connection = NULL; + goto out; + } + + if (!private) + { + /* store a weak ref to the connection (dbus-connection.c is + * supposed to have a strong ref that it drops on disconnect, + * since this is a shared connection) + */ + bus_connections[type] = connection; + } + + /* By default we're bound to the lifecycle of + * the message bus. + */ + dbus_connection_set_exit_on_disconnect (connection, + TRUE); + + if (!_DBUS_LOCK (bus_datas)) + _dbus_assert_not_reached ("global locks were initialized already"); + + bd = ensure_bus_data (connection); + _dbus_assert (bd != NULL); /* it should have been created on + register, so OOM not possible */ + bd->is_well_known = TRUE; + _DBUS_UNLOCK (bus_datas); + +out: + /* Return a reference to the caller, or NULL with error set. */ + if (connection == NULL) + _DBUS_ASSERT_ERROR_IS_SET (error); + + _DBUS_UNLOCK (bus); + return connection; +} + + +/** @} */ /* end of implementation details docs */ + +/** + * @addtogroup DBusBus + * @{ + */ + +/** + * Connects to a bus daemon and registers the client with it. If a + * connection to the bus already exists, then that connection is + * returned. The caller of this function owns a reference to the bus. + * + * The caller may NOT call dbus_connection_close() on this connection; + * see dbus_connection_open() and dbus_connection_close() for details + * on that. + * + * If this function obtains a new connection object never before + * returned from dbus_bus_get(), it will call + * dbus_connection_set_exit_on_disconnect(), so the application + * will exit if the connection closes. You can undo this + * by calling dbus_connection_set_exit_on_disconnect() yourself + * after you get the connection. + * + * dbus_bus_get() calls dbus_bus_register() for you. + * + * If returning a newly-created connection, this function will block + * until authentication and bus registration are complete. + * + * @param type bus type + * @param error address where an error can be returned. + * @returns a #DBusConnection with new ref or #NULL on error + */ +DBusConnection * +dbus_bus_get (DBusBusType type, + DBusError *error) +{ + return internal_bus_get (type, FALSE, error); +} + +/** + * Connects to a bus daemon and registers the client with it as with + * dbus_bus_register(). Unlike dbus_bus_get(), always creates a new + * connection. This connection will not be saved or recycled by + * libdbus. Caller owns a reference to the bus and must either close + * it or know it to be closed prior to releasing this reference. + * + * See dbus_connection_open_private() for more details on when to + * close and unref this connection. + * + * This function calls + * dbus_connection_set_exit_on_disconnect() on the new connection, so the application + * will exit if the connection closes. You can undo this + * by calling dbus_connection_set_exit_on_disconnect() yourself + * after you get the connection. + * + * dbus_bus_get_private() calls dbus_bus_register() for you. + * + * This function will block until authentication and bus registration + * are complete. + * + * @param type bus type + * @param error address where an error can be returned. + * @returns a DBusConnection with new ref + */ +DBusConnection * +dbus_bus_get_private (DBusBusType type, + DBusError *error) +{ + return internal_bus_get (type, TRUE, error); +} + +/** + * Registers a connection with the bus. This must be the first + * thing an application does when connecting to the message bus. + * If registration succeeds, the unique name will be set, + * and can be obtained using dbus_bus_get_unique_name(). + * + * This function will block until registration is complete. + * + * If the connection has already registered with the bus + * (determined by checking whether dbus_bus_get_unique_name() + * returns a non-#NULL value), then this function does nothing. + * + * If you use dbus_bus_get() or dbus_bus_get_private() this + * function will be called for you. + * + * @note Just use dbus_bus_get() or dbus_bus_get_private() instead of + * dbus_bus_register() and save yourself some pain. Using + * dbus_bus_register() manually is only useful if you have your + * own custom message bus not found in #DBusBusType. + * + * If you open a bus connection with dbus_connection_open() or + * dbus_connection_open_private() you will have to dbus_bus_register() + * yourself, or make the appropriate registration method calls + * yourself. If you send the method calls yourself, call + * dbus_bus_set_unique_name() with the unique bus name you get from + * the bus. + * + * For shared connections (created with dbus_connection_open()) in a + * multithreaded application, you can't really make the registration + * calls yourself, because you don't know whether some other thread is + * also registering, and the bus will kick you off if you send two + * registration messages. + * + * If you use dbus_bus_register() however, there is a lock that + * keeps both apps from registering at the same time. + * + * The rule in a multithreaded app, then, is that dbus_bus_register() + * must be used to register, or you need to have your own locks that + * all threads in the app will respect. + * + * In a single-threaded application you can register by hand instead + * of using dbus_bus_register(), as long as you check + * dbus_bus_get_unique_name() to see if a unique name has already been + * stored by another thread before you send the registration messages. + * + * @param connection the connection + * @param error place to store errors + * @returns #TRUE on success + */ +dbus_bool_t +dbus_bus_register (DBusConnection *connection, + DBusError *error) +{ + DBusMessage *message, *reply; + char *name; + BusData *bd; + dbus_bool_t retval; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_error_is_set (error, FALSE); + + retval = FALSE; + message = NULL; + reply = NULL; + + if (!_DBUS_LOCK (bus_datas)) + { + _DBUS_SET_OOM (error); + /* do not "goto out", that would try to unlock */ + return FALSE; + } + + bd = ensure_bus_data (connection); + if (bd == NULL) + { + _DBUS_SET_OOM (error); + goto out; + } + + if (bd->unique_name != NULL) + { + _dbus_verbose ("Ignoring attempt to register the same DBusConnection %s with the message bus a second time.\n", + bd->unique_name); + /* Success! */ + retval = TRUE; + goto out; + } + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "Hello"); + + if (!message) + { + _DBUS_SET_OOM (error); + goto out; + } + + reply = dbus_connection_send_with_reply_and_block (connection, message, -1, error); + + if (reply == NULL) + goto out; + else if (dbus_set_error_from_message (error, reply)) + goto out; + else if (!dbus_message_get_args (reply, error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + goto out; + + bd->unique_name = _dbus_strdup (name); + if (bd->unique_name == NULL) + { + _DBUS_SET_OOM (error); + goto out; + } + + retval = TRUE; + + out: + _DBUS_UNLOCK (bus_datas); + + if (message) + dbus_message_unref (message); + + if (reply) + dbus_message_unref (reply); + + if (!retval) + _DBUS_ASSERT_ERROR_IS_SET (error); + + return retval; +} + + +/** + * Sets the unique name of the connection, as assigned by the message + * bus. Can only be used if you registered with the bus manually + * (i.e. if you did not call dbus_bus_register()). Can only be called + * once per connection. After the unique name is set, you can get it + * with dbus_bus_get_unique_name(). + * + * The only reason to use this function is to re-implement the + * equivalent of dbus_bus_register() yourself. One (probably unusual) + * reason to do that might be to do the bus registration call + * asynchronously instead of synchronously. + * + * @note Just use dbus_bus_get() or dbus_bus_get_private(), or worst + * case dbus_bus_register(), instead of messing with this + * function. There's really no point creating pain for yourself by + * doing things manually. + * + * It's hard to use this function safely on shared connections + * (created by dbus_connection_open()) in a multithreaded application, + * because only one registration attempt can be sent to the bus. If + * two threads are both sending the registration message, there is no + * mechanism in libdbus itself to avoid sending it twice. + * + * Thus, you need a way to coordinate which thread sends the + * registration attempt; which also means you know which thread + * will call dbus_bus_set_unique_name(). If you don't know + * about all threads in the app (for example, if some libraries + * you're using might start libdbus-using threads), then you + * need to avoid using this function on shared connections. + * + * @param connection the connection + * @param unique_name the unique name + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_bus_set_unique_name (DBusConnection *connection, + const char *unique_name) +{ + BusData *bd; + dbus_bool_t success = FALSE; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (unique_name != NULL, FALSE); + + if (!_DBUS_LOCK (bus_datas)) + { + /* do not "goto out", that would try to unlock */ + return FALSE; + } + + bd = ensure_bus_data (connection); + if (bd == NULL) + goto out; + + _dbus_assert (bd->unique_name == NULL); + + bd->unique_name = _dbus_strdup (unique_name); + success = bd->unique_name != NULL; + +out: + _DBUS_UNLOCK (bus_datas); + + return success; +} + +/** + * Gets the unique name of the connection as assigned by the message + * bus. Only possible after the connection has been registered with + * the message bus. All connections returned by dbus_bus_get() or + * dbus_bus_get_private() have been successfully registered. + * + * The name remains valid until the connection is freed, and + * should not be freed by the caller. + * + * Other than dbus_bus_get(), there are two ways to set the unique + * name; one is dbus_bus_register(), the other is + * dbus_bus_set_unique_name(). You are responsible for calling + * dbus_bus_set_unique_name() if you register by hand instead of using + * dbus_bus_register(). + * + * @param connection the connection + * @returns the unique name or #NULL on error + */ +const char* +dbus_bus_get_unique_name (DBusConnection *connection) +{ + BusData *bd; + const char *unique_name = NULL; + + _dbus_return_val_if_fail (connection != NULL, NULL); + + if (!_DBUS_LOCK (bus_datas)) + { + /* We'd have initialized locks when we gave it its unique name, if it + * had one. Don't "goto out", that would try to unlock. */ + return NULL; + } + + bd = ensure_bus_data (connection); + if (bd == NULL) + goto out; + + unique_name = bd->unique_name; + +out: + _DBUS_UNLOCK (bus_datas); + + return unique_name; +} + +/** + * Asks the bus to return the UID the named connection authenticated + * as, if any. Only works on UNIX; only works for connections on the + * same machine as the bus. If you are not on the same machine as the + * bus, then calling this is probably a bad idea, since the UID will + * mean little to your application. + * + * For the system message bus you're guaranteed to be on the same + * machine since it only listens on a UNIX domain socket (at least, + * as shipped by default). + * + * This function only works for connections that authenticated as + * a UNIX user, right now that includes all bus connections, but + * it's very possible to have connections with no associated UID. + * So check for errors and do something sensible if they happen. + * + * This function will always return an error on Windows. + * + * @param connection the connection + * @param name a name owned by the connection + * @param error location to store the error + * @returns the unix user id, or ((unsigned)-1) if error is set + */ +unsigned long +dbus_bus_get_unix_user (DBusConnection *connection, + const char *name, + DBusError *error) +{ + DBusMessage *message, *reply; + dbus_uint32_t uid; + + _dbus_return_val_if_fail (connection != NULL, DBUS_UID_UNSET); + _dbus_return_val_if_fail (name != NULL, DBUS_UID_UNSET); + _dbus_return_val_if_fail (_dbus_check_is_valid_bus_name (name), DBUS_UID_UNSET); + _dbus_return_val_if_error_is_set (error, DBUS_UID_UNSET); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixUser"); + + if (message == NULL) + { + _DBUS_SET_OOM (error); + return DBUS_UID_UNSET; + } + + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + _DBUS_SET_OOM (error); + return DBUS_UID_UNSET; + } + + reply = dbus_connection_send_with_reply_and_block (connection, message, -1, + error); + + dbus_message_unref (message); + + if (reply == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return DBUS_UID_UNSET; + } + + if (dbus_set_error_from_message (error, reply)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_message_unref (reply); + return DBUS_UID_UNSET; + } + + if (!dbus_message_get_args (reply, error, + DBUS_TYPE_UINT32, &uid, + DBUS_TYPE_INVALID)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_message_unref (reply); + return DBUS_UID_UNSET; + } + + dbus_message_unref (reply); + + return (unsigned long) uid; +} + +/** + * Asks the bus to return its globally unique ID, as described in the + * D-Bus specification. For the session bus, this is useful as a way + * to uniquely identify each user session. For the system bus, + * probably the bus ID is not useful; instead, use the machine ID + * since it's accessible without necessarily connecting to the bus and + * may be persistent beyond a single bus instance (across reboots for + * example). See dbus_try_get_local_machine_id(). + * + * In addition to an ID for each bus and an ID for each machine, there is + * an ID for each address that the bus is listening on; that can + * be retrieved with dbus_connection_get_server_id(), though it is + * probably not very useful. + * + * @param connection the connection + * @param error location to store the error + * @returns the bus ID or #NULL if error is set + */ +char* +dbus_bus_get_id (DBusConnection *connection, + DBusError *error) +{ + DBusMessage *message, *reply; + char *id; + const char *v_STRING; + + _dbus_return_val_if_fail (connection != NULL, NULL); + _dbus_return_val_if_error_is_set (error, NULL); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetId"); + + if (message == NULL) + { + _DBUS_SET_OOM (error); + return NULL; + } + + reply = dbus_connection_send_with_reply_and_block (connection, message, -1, + error); + + dbus_message_unref (message); + + if (reply == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return NULL; + } + + if (dbus_set_error_from_message (error, reply)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_message_unref (reply); + return NULL; + } + + v_STRING = NULL; + if (!dbus_message_get_args (reply, error, + DBUS_TYPE_STRING, &v_STRING, + DBUS_TYPE_INVALID)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_message_unref (reply); + return NULL; + } + + id = _dbus_strdup (v_STRING); /* may be NULL */ + + dbus_message_unref (reply); + + if (id == NULL) + _DBUS_SET_OOM (error); + + /* FIXME it might be nice to cache the ID locally */ + + return id; +} + +/** + * Asks the bus to assign the given name to this connection by invoking + * the RequestName method on the bus. This method is fully documented + * in the D-Bus specification. For quick reference, the flags and + * result codes are discussed here, but the specification is the + * canonical version of this information. + * + * First you should know that for each bus name, the bus stores + * a queue of connections that would like to own it. Only + * one owns it at a time - called the primary owner. If the primary + * owner releases the name or disconnects, then the next owner in the + * queue atomically takes over. + * + * So for example if you have an application org.freedesktop.TextEditor + * and multiple instances of it can be run, you can have all of them + * sitting in the queue. The first one to start up will receive messages + * sent to org.freedesktop.TextEditor, but if that one exits another + * will become the primary owner and receive messages. + * + * The queue means you don't need to manually watch for the current owner to + * disappear and then request the name again. + * + * When requesting a name, you can specify several flags. + * + * #DBUS_NAME_FLAG_ALLOW_REPLACEMENT and #DBUS_NAME_FLAG_DO_NOT_QUEUE + * are properties stored by the bus for this connection with respect to + * each requested bus name. These properties are stored even if the + * connection is queued and does not become the primary owner. + * You can update these flags by calling RequestName again (even if + * you already own the name). + * + * #DBUS_NAME_FLAG_ALLOW_REPLACEMENT means that another requestor of the + * name can take it away from you by specifying #DBUS_NAME_FLAG_REPLACE_EXISTING. + * + * #DBUS_NAME_FLAG_DO_NOT_QUEUE means that if you aren't the primary owner, + * you don't want to be queued up - you only care about being the + * primary owner. + * + * Unlike the other two flags, #DBUS_NAME_FLAG_REPLACE_EXISTING is a property + * of the individual RequestName call, i.e. the bus does not persistently + * associate it with the connection-name pair. If a RequestName call includes + * the #DBUS_NAME_FLAG_REPLACE_EXISTING flag, and the current primary + * owner has #DBUS_NAME_FLAG_ALLOW_REPLACEMENT set, then the current primary + * owner will be kicked off. + * + * If no flags are given, an application will receive the requested + * name only if the name is currently unowned; and it will NOT give + * up the name if another application asks to take it over using + * #DBUS_NAME_FLAG_REPLACE_EXISTING. + * + * This function returns a result code. The possible result codes + * are as follows. + * + * #DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER means that the name had no + * existing owner, and the caller is now the primary owner; or that + * the name had an owner, and the caller specified + * #DBUS_NAME_FLAG_REPLACE_EXISTING, and the current owner + * specified #DBUS_NAME_FLAG_ALLOW_REPLACEMENT. + * + * #DBUS_REQUEST_NAME_REPLY_IN_QUEUE happens only if the caller does NOT + * specify #DBUS_NAME_FLAG_DO_NOT_QUEUE and either the current owner + * did NOT specify #DBUS_NAME_FLAG_ALLOW_REPLACEMENT or the caller did NOT + * specify #DBUS_NAME_FLAG_REPLACE_EXISTING. In this case the caller ends up + * in a queue to own the name after the current owner gives it up. + * + * #DBUS_REQUEST_NAME_REPLY_EXISTS happens if the name has an owner + * already and the caller specifies #DBUS_NAME_FLAG_DO_NOT_QUEUE + * and either the current owner has NOT specified + * #DBUS_NAME_FLAG_ALLOW_REPLACEMENT or the caller did NOT specify + * #DBUS_NAME_FLAG_REPLACE_EXISTING. + * + * #DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER happens if an application + * requests a name it already owns. (Re-requesting a name is useful if + * you want to change the #DBUS_NAME_FLAG_ALLOW_REPLACEMENT or + * #DBUS_NAME_FLAG_DO_NOT_QUEUE settings.) + * + * When a service represents an application, say "text editor," then + * it should specify #DBUS_NAME_FLAG_ALLOW_REPLACEMENT if it wants + * the last editor started to be the user's editor vs. the first one + * started. Then any editor that can be the user's editor should + * specify #DBUS_NAME_FLAG_REPLACE_EXISTING to either take over + * (last-started-wins) or be queued up (first-started-wins) according + * to whether #DBUS_NAME_FLAG_ALLOW_REPLACEMENT was given. + * + * Conventionally, single-instance applications often offer a command + * line option called --replace which means to replace the current + * instance. To implement this, always set + * #DBUS_NAME_FLAG_ALLOW_REPLACEMENT when you request your + * application's bus name. When you lose ownership of your bus name, + * you need to exit. Look for the signal "NameLost" from + * #DBUS_SERVICE_DBUS and #DBUS_INTERFACE_DBUS (the signal's first + * argument is the bus name that was lost). If starting up without + * --replace, do not specify #DBUS_NAME_FLAG_REPLACE_EXISTING, and + * exit if you fail to become the bus name owner. If --replace is + * given, ask to replace the old owner. + * + * @param connection the connection + * @param name the name to request + * @param flags flags + * @param error location to store the error + * @returns a result code, -1 if error is set + */ +int +dbus_bus_request_name (DBusConnection *connection, + const char *name, + unsigned int flags, + DBusError *error) +{ + DBusMessage *message, *reply; + dbus_uint32_t result; + + _dbus_return_val_if_fail (connection != NULL, 0); + _dbus_return_val_if_fail (name != NULL, 0); + _dbus_return_val_if_fail (_dbus_check_is_valid_bus_name (name), 0); + _dbus_return_val_if_error_is_set (error, 0); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "RequestName"); + + if (message == NULL) + { + _DBUS_SET_OOM (error); + return -1; + } + + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + _DBUS_SET_OOM (error); + return -1; + } + + reply = dbus_connection_send_with_reply_and_block (connection, message, -1, + error); + + dbus_message_unref (message); + + if (reply == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return -1; + } + + if (dbus_set_error_from_message (error, reply)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_message_unref (reply); + return -1; + } + + if (!dbus_message_get_args (reply, error, + DBUS_TYPE_UINT32, &result, + DBUS_TYPE_INVALID)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_message_unref (reply); + return -1; + } + + dbus_message_unref (reply); + + return result; +} + + +/** + * Asks the bus to unassign the given name from this connection by + * invoking the ReleaseName method on the bus. The "ReleaseName" + * method is canonically documented in the D-Bus specification. + * + * Possible results are: #DBUS_RELEASE_NAME_REPLY_RELEASED + * which means you owned the name or were in the queue to own it, + * and and now you don't own it and aren't in the queue. + * #DBUS_RELEASE_NAME_REPLY_NOT_OWNER which means someone else + * owns the name so you can't release it. + * #DBUS_RELEASE_NAME_REPLY_NON_EXISTENT + * which means nobody owned the name. + * + * @param connection the connection + * @param name the name to remove + * @param error location to store the error + * @returns a result code, -1 if error is set + */ +int +dbus_bus_release_name (DBusConnection *connection, + const char *name, + DBusError *error) +{ + DBusMessage *message, *reply; + dbus_uint32_t result; + + _dbus_return_val_if_fail (connection != NULL, 0); + _dbus_return_val_if_fail (name != NULL, 0); + _dbus_return_val_if_fail (_dbus_check_is_valid_bus_name (name), 0); + _dbus_return_val_if_error_is_set (error, 0); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "ReleaseName"); + + if (message == NULL) + { + _DBUS_SET_OOM (error); + return -1; + } + + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + _DBUS_SET_OOM (error); + return -1; + } + + reply = dbus_connection_send_with_reply_and_block (connection, message, -1, + error); + + dbus_message_unref (message); + + if (reply == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return -1; + } + + if (dbus_set_error_from_message (error, reply)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_message_unref (reply); + return -1; + } + + if (!dbus_message_get_args (reply, error, + DBUS_TYPE_UINT32, &result, + DBUS_TYPE_INVALID)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_message_unref (reply); + return -1; + } + + dbus_message_unref (reply); + + return result; +} + +/** + * Asks the bus whether a certain name has an owner. + * + * Using this can easily result in a race condition, + * since an owner can appear or disappear after you + * call this. + * + * If you want to request a name, just request it; + * if you want to avoid replacing a current owner, + * don't specify #DBUS_NAME_FLAG_REPLACE_EXISTING and + * you will get an error if there's already an owner. + * + * @param connection the connection + * @param name the name + * @param error location to store any errors + * @returns #TRUE if the name exists, #FALSE if not or on error + */ +dbus_bool_t +dbus_bus_name_has_owner (DBusConnection *connection, + const char *name, + DBusError *error) +{ + DBusMessage *message, *reply; + dbus_bool_t exists; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (name != NULL, FALSE); + _dbus_return_val_if_fail (_dbus_check_is_valid_bus_name (name), FALSE); + _dbus_return_val_if_error_is_set (error, FALSE); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "NameHasOwner"); + if (message == NULL) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + _DBUS_SET_OOM (error); + return FALSE; + } + + reply = dbus_connection_send_with_reply_and_block (connection, message, -1, error); + dbus_message_unref (message); + + if (reply == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return FALSE; + } + + if (!dbus_message_get_args (reply, error, + DBUS_TYPE_BOOLEAN, &exists, + DBUS_TYPE_INVALID)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_message_unref (reply); + return FALSE; + } + + dbus_message_unref (reply); + return exists; +} + +/** + * Starts a service that will request ownership of the given name. + * The returned result will be one of be one of + * #DBUS_START_REPLY_SUCCESS or #DBUS_START_REPLY_ALREADY_RUNNING if + * successful. Pass #NULL if you don't care about the result. + * + * The flags parameter is for future expansion, currently you should + * specify 0. + * + * It's often easier to avoid explicitly starting services, and + * just send a method call to the service's bus name instead. + * Method calls start a service to handle them by default + * unless you call dbus_message_set_auto_start() to disable this + * behavior. + * + * @param connection the connection + * @param name the name we want the new service to request + * @param flags the flags (should always be 0 for now) + * @param result a place to store the result or #NULL + * @param error location to store any errors + * @returns #TRUE if the activation succeeded, #FALSE if not + */ +dbus_bool_t +dbus_bus_start_service_by_name (DBusConnection *connection, + const char *name, + dbus_uint32_t flags, + dbus_uint32_t *result, + DBusError *error) +{ + DBusMessage *msg; + DBusMessage *reply; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (_dbus_check_is_valid_bus_name (name), FALSE); + + msg = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "StartServiceByName"); + + if (!dbus_message_append_args (msg, DBUS_TYPE_STRING, &name, + DBUS_TYPE_UINT32, &flags, DBUS_TYPE_INVALID)) + { + dbus_message_unref (msg); + _DBUS_SET_OOM (error); + return FALSE; + } + + reply = dbus_connection_send_with_reply_and_block (connection, msg, + -1, error); + dbus_message_unref (msg); + + if (reply == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return FALSE; + } + + if (dbus_set_error_from_message (error, reply)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_message_unref (reply); + return FALSE; + } + + if (result != NULL && + !dbus_message_get_args (reply, error, DBUS_TYPE_UINT32, + result, DBUS_TYPE_INVALID)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_message_unref (reply); + return FALSE; + } + + dbus_message_unref (reply); + return TRUE; +} + +static void +send_no_return_values (DBusConnection *connection, + DBusMessage *msg, + DBusError *error) +{ + if (error) + { + /* Block to check success codepath */ + DBusMessage *reply; + + reply = dbus_connection_send_with_reply_and_block (connection, msg, + -1, error); + + if (reply == NULL) + _DBUS_ASSERT_ERROR_IS_SET (error); + else + dbus_message_unref (reply); + } + else + { + /* Silently-fail nonblocking codepath */ + dbus_message_set_no_reply (msg, TRUE); + dbus_connection_send (connection, msg, NULL); + } +} + +/** + * Adds a match rule to match messages going through the message bus. + * The "rule" argument is the string form of a match rule. + * + * If you pass #NULL for the error, this function will not + * block; the match thus won't be added until you flush the + * connection, and if there's an error adding the match + * you won't find out about it. This is generally acceptable, since the + * possible errors (including a lack of resources in the bus, the connection + * having exceeded its quota of active match rules, or the match rule being + * unparseable) are generally unrecoverable. + * + * If you pass non-#NULL for the error this function will + * block until it gets a reply. This may be useful when using match rule keys + * introduced in recent versions of D-Bus, like 'arg0namespace', to allow the + * application to fall back to less efficient match rules supported by older + * versions of the daemon if the running version is not new enough; or when + * using user-supplied rules rather than rules hard-coded at compile time. + * + * Normal API conventions would have the function return + * a boolean value indicating whether the error was set, + * but that would require blocking always to determine + * the return value. + * + * The AddMatch method is fully documented in the D-Bus + * specification. For quick reference, the format of the + * match rules is discussed here, but the specification + * is the canonical version of this information. + * + * Rules are specified as a string of comma separated + * key/value pairs. An example is + * "type='signal',sender='org.freedesktop.DBus', + * interface='org.freedesktop.DBus',member='Foo', + * path='/bar/foo',destination=':452345.34'" + * + * Possible keys you can match on are type, sender, + * interface, member, path, destination and numbered + * keys to match message args (keys are 'arg0', 'arg1', etc.). + * Omitting a key from the rule indicates + * a wildcard match. For instance omitting + * the member from a match rule but adding a sender would + * let all messages from that sender through regardless of + * the member. + * + * Matches are inclusive not exclusive so as long as one + * rule matches the message will get through. It is important + * to note this because every time a message is received the + * application will be paged into memory to process it. This + * can cause performance problems such as draining batteries + * on embedded platforms. + * + * If you match message args ('arg0', 'arg1', and so forth) + * only string arguments will match. That is, arg0='5' means + * match the string "5" not the integer 5. + * + * Currently there is no way to match against non-string arguments. + * + * A specialised form of wildcard matching on arguments is + * supported for path-like namespaces. If your argument match has + * a 'path' suffix (eg: "arg0path='/some/path/'") then it is + * considered a match if the argument exactly matches the given + * string or if one of them ends in a '/' and is a prefix of the + * other. + * + * Matching on interface is tricky because method call + * messages only optionally specify the interface. + * If a message omits the interface, then it will NOT match + * if the rule specifies an interface name. This means match + * rules on method calls should not usually give an interface. + * + * However, signal messages are required to include the interface + * so when matching signals usually you should specify the interface + * in the match rule. + * + * For security reasons, you can match arguments only up to + * #DBUS_MAXIMUM_MATCH_RULE_ARG_NUMBER. + * + * Match rules have a maximum length of #DBUS_MAXIMUM_MATCH_RULE_LENGTH + * bytes. + * + * Both of these maximums are much higher than you're likely to need, + * they only exist because the D-Bus bus daemon has fixed limits on + * all resource usage. + * + * @param connection connection to the message bus + * @param rule textual form of match rule + * @param error location to store any errors + */ +void +dbus_bus_add_match (DBusConnection *connection, + const char *rule, + DBusError *error) +{ + DBusMessage *msg; + + _dbus_return_if_fail (rule != NULL); + + msg = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "AddMatch"); + + if (msg == NULL) + { + _DBUS_SET_OOM (error); + return; + } + + if (!dbus_message_append_args (msg, DBUS_TYPE_STRING, &rule, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (msg); + _DBUS_SET_OOM (error); + return; + } + + send_no_return_values (connection, msg, error); + + dbus_message_unref (msg); +} + +/** + * Removes a previously-added match rule "by value" (the most + * recently-added identical rule gets removed). The "rule" argument + * is the string form of a match rule. + * + * The bus compares match rules semantically, not textually, so + * whitespace and ordering don't have to be identical to + * the rule you passed to dbus_bus_add_match(). + * + * If you pass #NULL for the error, this function will not + * block; otherwise it will. See detailed explanation in + * docs for dbus_bus_add_match(). + * + * @param connection connection to the message bus + * @param rule textual form of match rule + * @param error location to store any errors + */ +void +dbus_bus_remove_match (DBusConnection *connection, + const char *rule, + DBusError *error) +{ + DBusMessage *msg; + + _dbus_return_if_fail (rule != NULL); + + msg = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "RemoveMatch"); + + if (!dbus_message_append_args (msg, DBUS_TYPE_STRING, &rule, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (msg); + _DBUS_SET_OOM (error); + return; + } + + send_no_return_values (connection, msg, error); + + dbus_message_unref (msg); +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-bus.h b/src/3rdparty/libdbus/dbus/dbus-bus.h new file mode 100644 index 00000000..31ade9a6 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-bus.h @@ -0,0 +1,97 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-bus.h Convenience functions for communicating with the bus. + * + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_BUS_H +#define DBUS_BUS_H + +#include <dbus/dbus-connection.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusBus + * @{ + */ + +DBUS_EXPORT +DBusConnection *dbus_bus_get (DBusBusType type, + DBusError *error); +DBUS_EXPORT +DBusConnection *dbus_bus_get_private (DBusBusType type, + DBusError *error); + +DBUS_EXPORT +dbus_bool_t dbus_bus_register (DBusConnection *connection, + DBusError *error); +DBUS_EXPORT +dbus_bool_t dbus_bus_set_unique_name (DBusConnection *connection, + const char *unique_name); +DBUS_EXPORT +const char* dbus_bus_get_unique_name (DBusConnection *connection); +DBUS_EXPORT +unsigned long dbus_bus_get_unix_user (DBusConnection *connection, + const char *name, + DBusError *error); +DBUS_EXPORT +char* dbus_bus_get_id (DBusConnection *connection, + DBusError *error); +DBUS_EXPORT +int dbus_bus_request_name (DBusConnection *connection, + const char *name, + unsigned int flags, + DBusError *error); +DBUS_EXPORT +int dbus_bus_release_name (DBusConnection *connection, + const char *name, + DBusError *error); +DBUS_EXPORT +dbus_bool_t dbus_bus_name_has_owner (DBusConnection *connection, + const char *name, + DBusError *error); + +DBUS_EXPORT +dbus_bool_t dbus_bus_start_service_by_name (DBusConnection *connection, + const char *name, + dbus_uint32_t flags, + dbus_uint32_t *reply, + DBusError *error); + +DBUS_EXPORT +void dbus_bus_add_match (DBusConnection *connection, + const char *rule, + DBusError *error); +DBUS_EXPORT +void dbus_bus_remove_match (DBusConnection *connection, + const char *rule, + DBusError *error); + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_BUS_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-connection-internal.h b/src/3rdparty/libdbus/dbus/dbus-connection-internal.h new file mode 100644 index 00000000..747e6e54 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-connection-internal.h @@ -0,0 +1,161 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-connection-internal.h DBusConnection internal interfaces + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_CONNECTION_INTERNAL_H +#define DBUS_CONNECTION_INTERNAL_H + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-connection.h> +#include <dbus/dbus-credentials.h> +#include <dbus/dbus-message.h> +#include <dbus/dbus-transport.h> +#include <dbus/dbus-resources.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-timeout.h> +#include <dbus/dbus-dataslot.h> + +DBUS_BEGIN_DECLS + +typedef enum +{ + DBUS_ITERATION_DO_WRITING = 1 << 0, /**< Write messages out. */ + DBUS_ITERATION_DO_READING = 1 << 1, /**< Read messages in. */ + DBUS_ITERATION_BLOCK = 1 << 2 /**< Block if nothing to do. */ +} DBusIterationFlags; + +/** default timeout value when waiting for a message reply, 25 seconds */ +#define _DBUS_DEFAULT_TIMEOUT_VALUE (25 * 1000) + +typedef void (* DBusPendingFdsChangeFunction) (void *data); + +DBUS_PRIVATE_EXPORT +void _dbus_connection_lock (DBusConnection *connection); +DBUS_PRIVATE_EXPORT +void _dbus_connection_unlock (DBusConnection *connection); +DBUS_PRIVATE_EXPORT +DBusConnection * _dbus_connection_ref_unlocked (DBusConnection *connection); +DBUS_PRIVATE_EXPORT +void _dbus_connection_unref_unlocked (DBusConnection *connection); +DBUS_PRIVATE_EXPORT +dbus_uint32_t _dbus_connection_get_next_client_serial (DBusConnection *connection); +void _dbus_connection_queue_received_message_link (DBusConnection *connection, + DBusList *link); +dbus_bool_t _dbus_connection_has_messages_to_send_unlocked (DBusConnection *connection); +DBusMessage* _dbus_connection_get_message_to_send (DBusConnection *connection); +void _dbus_connection_message_sent_unlocked (DBusConnection *connection, + DBusMessage *message); +dbus_bool_t _dbus_connection_add_watch_unlocked (DBusConnection *connection, + DBusWatch *watch); +void _dbus_connection_remove_watch_unlocked (DBusConnection *connection, + DBusWatch *watch); +void _dbus_connection_toggle_watch_unlocked (DBusConnection *connection, + DBusWatch *watch, + dbus_bool_t enabled); +dbus_bool_t _dbus_connection_handle_watch (DBusWatch *watch, + unsigned int condition, + void *data); +dbus_bool_t _dbus_connection_add_timeout_unlocked (DBusConnection *connection, + DBusTimeout *timeout); +void _dbus_connection_remove_timeout_unlocked (DBusConnection *connection, + DBusTimeout *timeout); +void _dbus_connection_toggle_timeout_unlocked (DBusConnection *connection, + DBusTimeout *timeout, + dbus_bool_t enabled); +DBusConnection* _dbus_connection_new_for_transport (DBusTransport *transport); +void _dbus_connection_do_iteration_unlocked (DBusConnection *connection, + DBusPendingCall *pending, + unsigned int flags, + int timeout_milliseconds); +void _dbus_connection_close_possibly_shared (DBusConnection *connection); +void _dbus_connection_close_if_only_one_ref (DBusConnection *connection); + +DBusPendingCall* _dbus_pending_call_new (DBusConnection *connection, + int timeout_milliseconds, + DBusTimeoutHandler timeout_handler); +void _dbus_pending_call_notify (DBusPendingCall *pending); +void _dbus_connection_remove_pending_call (DBusConnection *connection, + DBusPendingCall *pending); +void _dbus_connection_block_pending_call (DBusPendingCall *pending); +void _dbus_pending_call_complete_and_unlock (DBusPendingCall *pending, + DBusMessage *message); +dbus_bool_t _dbus_connection_send_and_unlock (DBusConnection *connection, + DBusMessage *message, + dbus_uint32_t *client_serial); + +void _dbus_connection_queue_synthesized_message_link (DBusConnection *connection, + DBusList *link); +DBUS_PRIVATE_EXPORT +void _dbus_connection_test_get_locks (DBusConnection *conn, + DBusMutex **mutex_loc, + DBusMutex **dispatch_mutex_loc, + DBusMutex **io_path_mutex_loc, + DBusCondVar **dispatch_cond_loc, + DBusCondVar **io_path_cond_loc); +DBUS_PRIVATE_EXPORT +int _dbus_connection_get_pending_fds_count (DBusConnection *connection); +DBUS_PRIVATE_EXPORT +void _dbus_connection_set_pending_fds_function (DBusConnection *connection, + DBusPendingFdsChangeFunction callback, + void *data); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_connection_get_linux_security_label (DBusConnection *connection, + char **label_p); +DBUS_PRIVATE_EXPORT +DBusCredentials *_dbus_connection_get_credentials (DBusConnection *connection); + +/* if DBUS_ENABLE_STATS */ +DBUS_PRIVATE_EXPORT +void _dbus_connection_get_stats (DBusConnection *connection, + dbus_uint32_t *in_messages, + dbus_uint32_t *in_bytes, + dbus_uint32_t *in_fds, + dbus_uint32_t *in_peak_bytes, + dbus_uint32_t *in_peak_fds, + dbus_uint32_t *out_messages, + dbus_uint32_t *out_bytes, + dbus_uint32_t *out_fds, + dbus_uint32_t *out_peak_bytes, + dbus_uint32_t *out_peak_fds); + + +DBUS_EMBEDDED_TESTS_EXPORT +const char* _dbus_connection_get_address (DBusConnection *connection); + +/* This _dbus_bus_* stuff doesn't really belong here, but dbus-bus-internal.h seems + * silly for one function + */ +/** + * @addtogroup DBusBusInternals + * @{ + */ + +void _dbus_bus_notify_shared_connection_disconnected_unlocked (DBusConnection *connection); + +/** @} */ + + +DBUS_END_DECLS + +#endif /* DBUS_CONNECTION_INTERNAL_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-connection.c b/src/3rdparty/libdbus/dbus/dbus-connection.c new file mode 100644 index 00000000..13f5085f --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-connection.c @@ -0,0 +1,6451 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-connection.c DBusConnection object + * + * Copyright (C) 2002-2006 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-shared.h" +#include "dbus-connection.h" +#include "dbus-list.h" +#include "dbus-timeout.h" +#include "dbus-transport.h" +#include "dbus-watch.h" +#include "dbus-connection-internal.h" +#include "dbus-pending-call-internal.h" +#include "dbus-list.h" +#include "dbus-hash.h" +#include "dbus-message-internal.h" +#include "dbus-message-private.h" +#include "dbus-threads.h" +#include "dbus-protocol.h" +#include "dbus-dataslot.h" +#include "dbus-string.h" +#include "dbus-signature.h" +#include "dbus-pending-call.h" +#include "dbus-object-tree.h" +#include "dbus-threads-internal.h" +#include "dbus-bus.h" +#include "dbus-marshal-basic.h" + +#ifdef DBUS_DISABLE_CHECKS +#define TOOK_LOCK_CHECK(connection) +#define RELEASING_LOCK_CHECK(connection) +#define HAVE_LOCK_CHECK(connection) +#else +#define TOOK_LOCK_CHECK(connection) do { \ + _dbus_assert (!(connection)->have_connection_lock); \ + (connection)->have_connection_lock = TRUE; \ + } while (0) +#define RELEASING_LOCK_CHECK(connection) do { \ + _dbus_assert ((connection)->have_connection_lock); \ + (connection)->have_connection_lock = FALSE; \ + } while (0) +#define HAVE_LOCK_CHECK(connection) _dbus_assert ((connection)->have_connection_lock) +/* A "DO_NOT_HAVE_LOCK_CHECK" is impossible since we need the lock to check the flag */ +#endif + +#define TRACE_LOCKS 1 + +#define CONNECTION_LOCK(connection) do { \ + if (TRACE_LOCKS) { _dbus_verbose ("LOCK\n"); } \ + _dbus_rmutex_lock ((connection)->mutex); \ + TOOK_LOCK_CHECK (connection); \ + } while (0) + +#define CONNECTION_UNLOCK(connection) _dbus_connection_unlock (connection) + +#define SLOTS_LOCK(connection) do { \ + _dbus_rmutex_lock ((connection)->slot_mutex); \ + } while (0) + +#define SLOTS_UNLOCK(connection) do { \ + _dbus_rmutex_unlock ((connection)->slot_mutex); \ + } while (0) + +#define DISPATCH_STATUS_NAME(s) \ + ((s) == DBUS_DISPATCH_COMPLETE ? "complete" : \ + (s) == DBUS_DISPATCH_DATA_REMAINS ? "data remains" : \ + (s) == DBUS_DISPATCH_NEED_MEMORY ? "need memory" : \ + "???") + +/** + * @defgroup DBusConnection DBusConnection + * @ingroup DBus + * @brief Connection to another application + * + * A DBusConnection represents a connection to another + * application. Messages can be sent and received via this connection. + * The other application may be a message bus; for convenience, the + * function dbus_bus_get() is provided to automatically open a + * connection to the well-known message buses. + * + * In brief a DBusConnection is a message queue associated with some + * message transport mechanism such as a socket. The connection + * maintains a queue of incoming messages and a queue of outgoing + * messages. + * + * Several functions use the following terms: + * <ul> + * <li><b>read</b> means to fill the incoming message queue by reading from the socket</li> + * <li><b>write</b> means to drain the outgoing queue by writing to the socket</li> + * <li><b>dispatch</b> means to drain the incoming queue by invoking application-provided message handlers</li> + * </ul> + * + * The function dbus_connection_read_write_dispatch() for example does all + * three of these things, offering a simple alternative to a main loop. + * + * In an application with a main loop, the read/write/dispatch + * operations are usually separate. + * + * The connection provides #DBusWatch and #DBusTimeout objects to + * the main loop. These are used to know when reading, writing, or + * dispatching should be performed. + * + * Incoming messages are processed + * by calling dbus_connection_dispatch(). dbus_connection_dispatch() + * runs any handlers registered for the topmost message in the message + * queue, then discards the message, then returns. + * + * dbus_connection_get_dispatch_status() indicates whether + * messages are currently in the queue that need dispatching. + * dbus_connection_set_dispatch_status_function() allows + * you to set a function to be used to monitor the dispatch status. + * + * If you're using GLib or Qt add-on libraries for D-Bus, there are + * special convenience APIs in those libraries that hide + * all the details of dispatch and watch/timeout monitoring. + * For example, dbus_connection_setup_with_g_main(). + * + * If you aren't using these add-on libraries, but want to process + * messages asynchronously, you must manually call + * dbus_connection_set_dispatch_status_function(), + * dbus_connection_set_watch_functions(), + * dbus_connection_set_timeout_functions() providing appropriate + * functions to integrate the connection with your application's main + * loop. This can be tricky to get right; main loops are not simple. + * + * If you don't need to be asynchronous, you can ignore #DBusWatch, + * #DBusTimeout, and dbus_connection_dispatch(). Instead, + * dbus_connection_read_write_dispatch() can be used. + * + * Or, in <em>very</em> simple applications, + * dbus_connection_pop_message() may be all you need, allowing you to + * avoid setting up any handler functions (see + * dbus_connection_add_filter(), + * dbus_connection_register_object_path() for more on handlers). + * + * When you use dbus_connection_send() or one of its variants to send + * a message, the message is added to the outgoing queue. It's + * actually written to the network later; either in + * dbus_watch_handle() invoked by your main loop, or in + * dbus_connection_flush() which blocks until it can write out the + * entire outgoing queue. The GLib/Qt add-on libraries again + * handle the details here for you by setting up watch functions. + * + * When a connection is disconnected, you are guaranteed to get a + * signal "Disconnected" from the interface + * #DBUS_INTERFACE_LOCAL, path + * #DBUS_PATH_LOCAL. + * + * You may not drop the last reference to a #DBusConnection + * until that connection has been disconnected. + * + * You may dispatch the unprocessed incoming message queue even if the + * connection is disconnected. However, "Disconnected" will always be + * the last message in the queue (obviously no messages are received + * after disconnection). + * + * After calling dbus_threads_init(), #DBusConnection has thread + * locks and drops them when invoking user callbacks, so in general is + * transparently threadsafe. However, #DBusMessage does NOT have + * thread locks; you must not send the same message to multiple + * #DBusConnection if those connections will be used from different threads, + * for example. + * + * Also, if you dispatch or pop messages from multiple threads, it + * may work in the sense that it won't crash, but it's tough to imagine + * sane results; it will be completely unpredictable which messages + * go to which threads. + * + * It's recommended to dispatch from a single thread. + * + * The most useful function to call from multiple threads at once + * is dbus_connection_send_with_reply_and_block(). That is, + * multiple threads can make method calls at the same time. + * + * If you aren't using threads, you can use a main loop and + * dbus_pending_call_set_notify() to achieve a similar result. + */ + +/** + * @defgroup DBusConnectionInternals DBusConnection implementation details + * @ingroup DBusInternals + * @brief Implementation details of DBusConnection + * + * @{ + */ + +static void +_dbus_connection_trace_ref (DBusConnection *connection, + int old_refcount, + int new_refcount, + const char *why) +{ +#ifdef DBUS_ENABLE_VERBOSE_MODE + static int enabled = -1; + + _dbus_trace_ref ("DBusConnection", connection, old_refcount, new_refcount, + why, "DBUS_CONNECTION_TRACE", &enabled); +#endif +} + +/** + * Internal struct representing a message filter function + */ +typedef struct DBusMessageFilter DBusMessageFilter; + +/** + * Internal struct representing a message filter function + */ +struct DBusMessageFilter +{ + DBusAtomic refcount; /**< Reference count */ + DBusHandleMessageFunction function; /**< Function to call to filter */ + void *user_data; /**< User data for the function */ + DBusFreeFunction free_user_data_function; /**< Function to free the user data */ +}; + + +/** + * Internals of DBusPreallocatedSend + */ +struct DBusPreallocatedSend +{ + DBusConnection *connection; /**< Connection we'd send the message to */ + DBusList *queue_link; /**< Preallocated link in the queue */ + DBusList *counter_link; /**< Preallocated link in the resource counter */ +}; + +#if HAVE_DECL_MSG_NOSIGNAL +static DBusAtomic _dbus_modify_sigpipe = { FALSE }; +#else +static DBusAtomic _dbus_modify_sigpipe = { TRUE }; +#endif + +/** + * Implementation details of DBusConnection. All fields are private. + */ +struct DBusConnection +{ + DBusAtomic refcount; /**< Reference count. */ + + DBusRMutex *mutex; /**< Lock on the entire DBusConnection */ + + DBusCMutex *dispatch_mutex; /**< Protects dispatch_acquired */ + DBusCondVar *dispatch_cond; /**< Notify when dispatch_acquired is available */ + DBusCMutex *io_path_mutex; /**< Protects io_path_acquired */ + DBusCondVar *io_path_cond; /**< Notify when io_path_acquired is available */ + + DBusList *outgoing_messages; /**< Queue of messages we need to send, send the end of the list first. */ + DBusList *incoming_messages; /**< Queue of messages we have received, end of the list received most recently. */ + DBusList *expired_messages; /**< Messages that will be released when we next unlock. */ + + DBusMessage *message_borrowed; /**< Filled in if the first incoming message has been borrowed; + * dispatch_acquired will be set by the borrower + */ + + int n_outgoing; /**< Length of outgoing queue. */ + int n_incoming; /**< Length of incoming queue. */ + + DBusCounter *outgoing_counter; /**< Counts size of outgoing messages. */ + + DBusTransport *transport; /**< Object that sends/receives messages over network. */ + DBusWatchList *watches; /**< Stores active watches. */ + DBusTimeoutList *timeouts; /**< Stores active timeouts. */ + + DBusList *filter_list; /**< List of filters. */ + + DBusRMutex *slot_mutex; /**< Lock on slot_list so overall connection lock need not be taken */ + DBusDataSlotList slot_list; /**< Data stored by allocated integer ID */ + + DBusHashTable *pending_replies; /**< Hash of message serials to #DBusPendingCall. */ + + dbus_uint32_t client_serial; /**< Client serial. Increments each time a message is sent */ + DBusList *disconnect_message_link; /**< Preallocated list node for queueing the disconnection message */ + + DBusWakeupMainFunction wakeup_main_function; /**< Function to wake up the mainloop */ + void *wakeup_main_data; /**< Application data for wakeup_main_function */ + DBusFreeFunction free_wakeup_main_data; /**< free wakeup_main_data */ + + DBusDispatchStatusFunction dispatch_status_function; /**< Function on dispatch status changes */ + void *dispatch_status_data; /**< Application data for dispatch_status_function */ + DBusFreeFunction free_dispatch_status_data; /**< free dispatch_status_data */ + + DBusDispatchStatus last_dispatch_status; /**< The last dispatch status we reported to the application. */ + + DBusObjectTree *objects; /**< Object path handlers registered with this connection */ + + char *server_guid; /**< GUID of server if we are in shared_connections, #NULL if server GUID is unknown or connection is private */ + + /* These two MUST be bools and not bitfields, because they are protected by a separate lock + * from connection->mutex and all bitfields in a word have to be read/written together. + * So you can't have a different lock for different bitfields in the same word. + */ + dbus_bool_t dispatch_acquired; /**< Someone has dispatch path (can drain incoming queue) */ + dbus_bool_t io_path_acquired; /**< Someone has transport io path (can use the transport to read/write messages) */ + + unsigned int shareable : 1; /**< #TRUE if libdbus owns a reference to the connection and can return it from dbus_connection_open() more than once */ + + unsigned int exit_on_disconnect : 1; /**< If #TRUE, exit after handling disconnect signal */ + + unsigned int builtin_filters_enabled : 1; /**< If #TRUE, handle org.freedesktop.DBus.Peer messages automatically, whether they have a bus name or not */ + + unsigned int route_peer_messages : 1; /**< If #TRUE, if org.freedesktop.DBus.Peer messages have a bus name, don't handle them automatically */ + + unsigned int disconnected_message_arrived : 1; /**< We popped or are dispatching the disconnected message. + * if the disconnect_message_link is NULL then we queued it, but + * this flag is whether it got to the head of the queue. + */ + unsigned int disconnected_message_processed : 1; /**< We did our default handling of the disconnected message, + * such as closing the connection. + */ + +#ifndef DBUS_DISABLE_CHECKS + unsigned int have_connection_lock : 1; /**< Used to check locking */ +#endif + +#if defined(DBUS_ENABLE_CHECKS) || defined(DBUS_ENABLE_ASSERT) + int generation; /**< _dbus_current_generation that should correspond to this connection */ +#endif +}; + +static DBusDispatchStatus _dbus_connection_get_dispatch_status_unlocked (DBusConnection *connection); +static void _dbus_connection_update_dispatch_status_and_unlock (DBusConnection *connection, + DBusDispatchStatus new_status); +static void _dbus_connection_last_unref (DBusConnection *connection); +static void _dbus_connection_acquire_dispatch (DBusConnection *connection); +static void _dbus_connection_release_dispatch (DBusConnection *connection); +static DBusDispatchStatus _dbus_connection_flush_unlocked (DBusConnection *connection); +static void _dbus_connection_close_possibly_shared_and_unlock (DBusConnection *connection); +static dbus_bool_t _dbus_connection_get_is_connected_unlocked (DBusConnection *connection); +static dbus_bool_t _dbus_connection_peek_for_reply_unlocked (DBusConnection *connection, + dbus_uint32_t client_serial); + +static DBusMessageFilter * +_dbus_message_filter_ref (DBusMessageFilter *filter) +{ +#ifdef DBUS_DISABLE_ASSERT + _dbus_atomic_inc (&filter->refcount); +#else + dbus_int32_t old_value; + + old_value = _dbus_atomic_inc (&filter->refcount); + _dbus_assert (old_value > 0); +#endif + + return filter; +} + +static void +_dbus_message_filter_unref (DBusMessageFilter *filter) +{ + dbus_int32_t old_value; + + old_value = _dbus_atomic_dec (&filter->refcount); + _dbus_assert (old_value > 0); + + if (old_value == 1) + { + if (filter->free_user_data_function) + (* filter->free_user_data_function) (filter->user_data); + + dbus_free (filter); + } +} + +/** + * Acquires the connection lock. + * + * @param connection the connection. + */ +void +_dbus_connection_lock (DBusConnection *connection) +{ + CONNECTION_LOCK (connection); +} + +/** + * Releases the connection lock. + * + * @param connection the connection. + */ +void +_dbus_connection_unlock (DBusConnection *connection) +{ + DBusList *expired_messages; + DBusList *iter; + + if (TRACE_LOCKS) + { + _dbus_verbose ("UNLOCK\n"); + } + + /* If we had messages that expired (fell off the incoming or outgoing + * queues) while we were locked, actually release them now */ + expired_messages = connection->expired_messages; + connection->expired_messages = NULL; + + RELEASING_LOCK_CHECK (connection); + _dbus_rmutex_unlock (connection->mutex); + + for (iter = _dbus_list_pop_first_link (&expired_messages); + iter != NULL; + iter = _dbus_list_pop_first_link (&expired_messages)) + { + DBusMessage *message = iter->data; + + dbus_message_unref (message); + _dbus_list_free_link (iter); + } +} + +/** + * Wakes up the main loop if it is sleeping + * Needed if we're e.g. queueing outgoing messages + * on a thread while the mainloop sleeps. + * + * @param connection the connection. + */ +static void +_dbus_connection_wakeup_mainloop (DBusConnection *connection) +{ + if (connection->wakeup_main_function) + (*connection->wakeup_main_function) (connection->wakeup_main_data); +} + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +/** + * Gets the locks so we can examine them + * + * @param connection the connection. + * @param mutex_loc return for the location of the main mutex pointer + * @param dispatch_mutex_loc return location of the dispatch mutex pointer + * @param io_path_mutex_loc return location of the io_path mutex pointer + * @param dispatch_cond_loc return location of the dispatch conditional + * variable pointer + * @param io_path_cond_loc return location of the io_path conditional + * variable pointer + */ +void +_dbus_connection_test_get_locks (DBusConnection *connection, + DBusMutex **mutex_loc, + DBusMutex **dispatch_mutex_loc, + DBusMutex **io_path_mutex_loc, + DBusCondVar **dispatch_cond_loc, + DBusCondVar **io_path_cond_loc) +{ + *mutex_loc = (DBusMutex *) connection->mutex; + *dispatch_mutex_loc = (DBusMutex *) connection->dispatch_mutex; + *io_path_mutex_loc = (DBusMutex *) connection->io_path_mutex; + *dispatch_cond_loc = connection->dispatch_cond; + *io_path_cond_loc = connection->io_path_cond; +} +#endif + +/** + * Adds a message-containing list link to the incoming message queue, + * taking ownership of the link and the message's current refcount. + * Cannot fail due to lack of memory. + * + * @param connection the connection. + * @param link the message link to queue. + */ +void +_dbus_connection_queue_received_message_link (DBusConnection *connection, + DBusList *link) +{ + DBusPendingCall *pending; + dbus_uint32_t reply_serial; + DBusMessage *message; + + _dbus_assert (_dbus_transport_peek_is_authenticated (connection->transport)); + + _dbus_list_append_link (&connection->incoming_messages, + link); + message = link->data; + + /* If this is a reply we're waiting on, remove timeout for it */ + reply_serial = dbus_message_get_reply_serial (message); + if (reply_serial != 0) + { + pending = _dbus_hash_table_lookup_int (connection->pending_replies, + reply_serial); + if (pending != NULL) + { + if (_dbus_pending_call_is_timeout_added_unlocked (pending)) + _dbus_connection_remove_timeout_unlocked (connection, + _dbus_pending_call_get_timeout_unlocked (pending)); + + _dbus_pending_call_set_timeout_added_unlocked (pending, FALSE); + } + } + + + + connection->n_incoming += 1; + + _dbus_connection_wakeup_mainloop (connection); + + _dbus_verbose ("Message %p (%s %s %s %s '%s' reply to %u) added to incoming queue %p, %d incoming\n", + message, + dbus_message_type_to_string (dbus_message_get_type (message)), + dbus_message_get_path (message) ? + dbus_message_get_path (message) : + "no path", + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : + "no interface", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : + "no member", + dbus_message_get_signature (message), + dbus_message_get_reply_serial (message), + connection, + connection->n_incoming); + + _dbus_message_trace_ref (message, -1, -1, + "_dbus_conection_queue_received_message_link"); +} + +/** + * Adds a link + message to the incoming message queue. + * Can't fail. Takes ownership of both link and message. + * + * @param connection the connection. + * @param link the list node and message to queue. + * + */ +void +_dbus_connection_queue_synthesized_message_link (DBusConnection *connection, + DBusList *link) +{ + HAVE_LOCK_CHECK (connection); + + _dbus_list_append_link (&connection->incoming_messages, link); + + connection->n_incoming += 1; + + _dbus_connection_wakeup_mainloop (connection); + + _dbus_message_trace_ref (link->data, -1, -1, + "_dbus_connection_queue_synthesized_message_link"); + + _dbus_verbose ("Synthesized message %p added to incoming queue %p, %d incoming\n", + link->data, connection, connection->n_incoming); +} + + +/** + * Checks whether there are messages in the outgoing message queue. + * Called with connection lock held. + * + * @param connection the connection. + * @returns #TRUE if the outgoing queue is non-empty. + */ +dbus_bool_t +_dbus_connection_has_messages_to_send_unlocked (DBusConnection *connection) +{ + HAVE_LOCK_CHECK (connection); + return connection->outgoing_messages != NULL; +} + +/** + * Checks whether there are messages in the outgoing message queue. + * Use dbus_connection_flush() to block until all outgoing + * messages have been written to the underlying transport + * (such as a socket). + * + * @param connection the connection. + * @returns #TRUE if the outgoing queue is non-empty. + */ +dbus_bool_t +dbus_connection_has_messages_to_send (DBusConnection *connection) +{ + dbus_bool_t v; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + + CONNECTION_LOCK (connection); + v = _dbus_connection_has_messages_to_send_unlocked (connection); + CONNECTION_UNLOCK (connection); + + return v; +} + +/** + * Gets the next outgoing message. The message remains in the + * queue, and the caller does not own a reference to it. + * + * @param connection the connection. + * @returns the message to be sent. + */ +DBusMessage* +_dbus_connection_get_message_to_send (DBusConnection *connection) +{ + HAVE_LOCK_CHECK (connection); + + return _dbus_list_get_last (&connection->outgoing_messages); +} + +/** + * Notifies the connection that a message has been sent, so the + * message can be removed from the outgoing queue. + * Called with the connection lock held. + * + * @param connection the connection. + * @param message the message that was sent. + */ +void +_dbus_connection_message_sent_unlocked (DBusConnection *connection, + DBusMessage *message) +{ + DBusList *link; + + HAVE_LOCK_CHECK (connection); + + /* This can be called before we even complete authentication, since + * it's called on disconnect to clean up the outgoing queue. + * It's also called as we successfully send each message. + */ + + link = _dbus_list_get_last_link (&connection->outgoing_messages); + _dbus_assert (link != NULL); + _dbus_assert (link->data == message); + + _dbus_list_unlink (&connection->outgoing_messages, + link); + _dbus_list_prepend_link (&connection->expired_messages, link); + + connection->n_outgoing -= 1; + + _dbus_verbose ("Message %p (%s %s %s %s '%s') removed from outgoing queue %p, %d left to send\n", + message, + dbus_message_type_to_string (dbus_message_get_type (message)), + dbus_message_get_path (message) ? + dbus_message_get_path (message) : + "no path", + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : + "no interface", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : + "no member", + dbus_message_get_signature (message), + connection, connection->n_outgoing); + + /* It's OK that in principle we call the notify function, because for the + * outgoing limit, there isn't one */ + _dbus_message_remove_counter (message, connection->outgoing_counter); + + /* The message will actually be unreffed when we unlock */ +} + +/** Function to be called in protected_change_watch() with refcount held */ +typedef dbus_bool_t (* DBusWatchAddFunction) (DBusWatchList *list, + DBusWatch *watch); +/** Function to be called in protected_change_watch() with refcount held */ +typedef void (* DBusWatchRemoveFunction) (DBusWatchList *list, + DBusWatch *watch); +/** Function to be called in protected_change_watch() with refcount held */ +typedef void (* DBusWatchToggleFunction) (DBusWatchList *list, + DBusWatch *watch, + dbus_bool_t enabled); + +static dbus_bool_t +protected_change_watch (DBusConnection *connection, + DBusWatch *watch, + DBusWatchAddFunction add_function, + DBusWatchRemoveFunction remove_function, + DBusWatchToggleFunction toggle_function, + dbus_bool_t enabled) +{ + dbus_bool_t retval; + + HAVE_LOCK_CHECK (connection); + + /* The original purpose of protected_change_watch() was to hold a + * ref on the connection while dropping the connection lock, then + * calling out to the app. This was a broken hack that did not + * work, since the connection was in a hosed state (no WatchList + * field) while calling out. + * + * So for now we'll just keep the lock while calling out. This means + * apps are not allowed to call DBusConnection methods inside a + * watch function or they will deadlock. + * + * The "real fix" is to use the _and_unlock() pattern found + * elsewhere in the code, to defer calling out to the app until + * we're about to drop locks and return flow of control to the app + * anyway. + * + * See http://lists.freedesktop.org/archives/dbus/2007-July/thread.html#8144 + */ + + if (connection->watches) + { + if (add_function) + retval = (* add_function) (connection->watches, watch); + else if (remove_function) + { + retval = TRUE; + (* remove_function) (connection->watches, watch); + } + else + { + retval = TRUE; + (* toggle_function) (connection->watches, watch, enabled); + } + return retval; + } + else + return FALSE; +} + + +/** + * Adds a watch using the connection's DBusAddWatchFunction if + * available. Otherwise records the watch to be added when said + * function is available. Also re-adds the watch if the + * DBusAddWatchFunction changes. May fail due to lack of memory. + * Connection lock should be held when calling this. + * + * @param connection the connection. + * @param watch the watch to add. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_connection_add_watch_unlocked (DBusConnection *connection, + DBusWatch *watch) +{ + return protected_change_watch (connection, watch, + _dbus_watch_list_add_watch, + NULL, NULL, FALSE); +} + +/** + * Removes a watch using the connection's DBusRemoveWatchFunction + * if available. It's an error to call this function on a watch + * that was not previously added. + * Connection lock should be held when calling this. + * + * @param connection the connection. + * @param watch the watch to remove. + */ +void +_dbus_connection_remove_watch_unlocked (DBusConnection *connection, + DBusWatch *watch) +{ + protected_change_watch (connection, watch, + NULL, + _dbus_watch_list_remove_watch, + NULL, FALSE); +} + +/** + * Toggles a watch and notifies app via connection's + * DBusWatchToggledFunction if available. It's an error to call this + * function on a watch that was not previously added. + * Connection lock should be held when calling this. + * + * @param connection the connection. + * @param watch the watch to toggle. + * @param enabled whether to enable or disable + */ +void +_dbus_connection_toggle_watch_unlocked (DBusConnection *connection, + DBusWatch *watch, + dbus_bool_t enabled) +{ + _dbus_assert (watch != NULL); + + protected_change_watch (connection, watch, + NULL, NULL, + _dbus_watch_list_toggle_watch, + enabled); +} + +/** Function to be called in protected_change_timeout() with refcount held */ +typedef dbus_bool_t (* DBusTimeoutAddFunction) (DBusTimeoutList *list, + DBusTimeout *timeout); +/** Function to be called in protected_change_timeout() with refcount held */ +typedef void (* DBusTimeoutRemoveFunction) (DBusTimeoutList *list, + DBusTimeout *timeout); +/** Function to be called in protected_change_timeout() with refcount held */ +typedef void (* DBusTimeoutToggleFunction) (DBusTimeoutList *list, + DBusTimeout *timeout, + dbus_bool_t enabled); + +static dbus_bool_t +protected_change_timeout (DBusConnection *connection, + DBusTimeout *timeout, + DBusTimeoutAddFunction add_function, + DBusTimeoutRemoveFunction remove_function, + DBusTimeoutToggleFunction toggle_function, + dbus_bool_t enabled) +{ + dbus_bool_t retval; + + HAVE_LOCK_CHECK (connection); + + /* The original purpose of protected_change_timeout() was to hold a + * ref on the connection while dropping the connection lock, then + * calling out to the app. This was a broken hack that did not + * work, since the connection was in a hosed state (no TimeoutList + * field) while calling out. + * + * So for now we'll just keep the lock while calling out. This means + * apps are not allowed to call DBusConnection methods inside a + * timeout function or they will deadlock. + * + * The "real fix" is to use the _and_unlock() pattern found + * elsewhere in the code, to defer calling out to the app until + * we're about to drop locks and return flow of control to the app + * anyway. + * + * See http://lists.freedesktop.org/archives/dbus/2007-July/thread.html#8144 + */ + + if (connection->timeouts) + { + if (add_function) + retval = (* add_function) (connection->timeouts, timeout); + else if (remove_function) + { + retval = TRUE; + (* remove_function) (connection->timeouts, timeout); + } + else + { + retval = TRUE; + (* toggle_function) (connection->timeouts, timeout, enabled); + } + return retval; + } + else + return FALSE; +} + +/** + * Adds a timeout using the connection's DBusAddTimeoutFunction if + * available. Otherwise records the timeout to be added when said + * function is available. Also re-adds the timeout if the + * DBusAddTimeoutFunction changes. May fail due to lack of memory. + * The timeout will fire repeatedly until removed. + * Connection lock should be held when calling this. + * + * @param connection the connection. + * @param timeout the timeout to add. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_connection_add_timeout_unlocked (DBusConnection *connection, + DBusTimeout *timeout) +{ + return protected_change_timeout (connection, timeout, + _dbus_timeout_list_add_timeout, + NULL, NULL, FALSE); +} + +/** + * Removes a timeout using the connection's DBusRemoveTimeoutFunction + * if available. It's an error to call this function on a timeout + * that was not previously added. + * Connection lock should be held when calling this. + * + * @param connection the connection. + * @param timeout the timeout to remove. + */ +void +_dbus_connection_remove_timeout_unlocked (DBusConnection *connection, + DBusTimeout *timeout) +{ + protected_change_timeout (connection, timeout, + NULL, + _dbus_timeout_list_remove_timeout, + NULL, FALSE); +} + +/** + * Toggles a timeout and notifies app via connection's + * DBusTimeoutToggledFunction if available. It's an error to call this + * function on a timeout that was not previously added. + * Connection lock should be held when calling this. + * + * @param connection the connection. + * @param timeout the timeout to toggle. + * @param enabled whether to enable or disable + */ +void +_dbus_connection_toggle_timeout_unlocked (DBusConnection *connection, + DBusTimeout *timeout, + dbus_bool_t enabled) +{ + protected_change_timeout (connection, timeout, + NULL, NULL, + _dbus_timeout_list_toggle_timeout, + enabled); +} + +static dbus_bool_t +_dbus_connection_attach_pending_call_unlocked (DBusConnection *connection, + DBusPendingCall *pending) +{ + dbus_uint32_t reply_serial; + DBusTimeout *timeout; + + HAVE_LOCK_CHECK (connection); + + reply_serial = _dbus_pending_call_get_reply_serial_unlocked (pending); + + _dbus_assert (reply_serial != 0); + + timeout = _dbus_pending_call_get_timeout_unlocked (pending); + + if (timeout) + { + if (!_dbus_connection_add_timeout_unlocked (connection, timeout)) + return FALSE; + + if (!_dbus_hash_table_insert_int (connection->pending_replies, + reply_serial, + pending)) + { + _dbus_connection_remove_timeout_unlocked (connection, timeout); + + _dbus_pending_call_set_timeout_added_unlocked (pending, FALSE); + HAVE_LOCK_CHECK (connection); + return FALSE; + } + + _dbus_pending_call_set_timeout_added_unlocked (pending, TRUE); + } + else + { + if (!_dbus_hash_table_insert_int (connection->pending_replies, + reply_serial, + pending)) + { + HAVE_LOCK_CHECK (connection); + return FALSE; + } + } + + _dbus_pending_call_ref_unlocked (pending); + + HAVE_LOCK_CHECK (connection); + + return TRUE; +} + +static void +free_pending_call_on_hash_removal (void *data) +{ + DBusPendingCall *pending; + DBusConnection *connection; + + if (data == NULL) + return; + + pending = data; + + connection = _dbus_pending_call_get_connection_unlocked (pending); + + HAVE_LOCK_CHECK (connection); + + if (_dbus_pending_call_is_timeout_added_unlocked (pending)) + { + _dbus_connection_remove_timeout_unlocked (connection, + _dbus_pending_call_get_timeout_unlocked (pending)); + + _dbus_pending_call_set_timeout_added_unlocked (pending, FALSE); + } + + /* FIXME 1.0? this is sort of dangerous and undesirable to drop the lock + * here, but the pending call finalizer could in principle call out to + * application code so we pretty much have to... some larger code reorg + * might be needed. + */ + _dbus_connection_ref_unlocked (connection); + _dbus_pending_call_unref_and_unlock (pending); + CONNECTION_LOCK (connection); + _dbus_connection_unref_unlocked (connection); +} + +static void +_dbus_connection_detach_pending_call_unlocked (DBusConnection *connection, + DBusPendingCall *pending) +{ + /* This ends up unlocking to call the pending call finalizer, which is unexpected to + * say the least. + */ + _dbus_hash_table_remove_int (connection->pending_replies, + _dbus_pending_call_get_reply_serial_unlocked (pending)); +} + +static void +_dbus_connection_detach_pending_call_and_unlock (DBusConnection *connection, + DBusPendingCall *pending) +{ + /* The idea here is to avoid finalizing the pending call + * with the lock held, since there's a destroy notifier + * in pending call that goes out to application code. + * + * There's an extra unlock inside the hash table + * "free pending call" function FIXME... + */ + _dbus_pending_call_ref_unlocked (pending); + _dbus_hash_table_remove_int (connection->pending_replies, + _dbus_pending_call_get_reply_serial_unlocked (pending)); + + if (_dbus_pending_call_is_timeout_added_unlocked (pending)) + _dbus_connection_remove_timeout_unlocked (connection, + _dbus_pending_call_get_timeout_unlocked (pending)); + + _dbus_pending_call_set_timeout_added_unlocked (pending, FALSE); + + _dbus_pending_call_unref_and_unlock (pending); +} + +/** + * Removes a pending call from the connection, such that + * the pending reply will be ignored. May drop the last + * reference to the pending call. + * + * @param connection the connection + * @param pending the pending call + */ +void +_dbus_connection_remove_pending_call (DBusConnection *connection, + DBusPendingCall *pending) +{ + CONNECTION_LOCK (connection); + _dbus_connection_detach_pending_call_and_unlock (connection, pending); +} + +/** + * Acquire the transporter I/O path. This must be done before + * doing any I/O in the transporter. May sleep and drop the + * IO path mutex while waiting for the I/O path. + * + * @param connection the connection. + * @param timeout_milliseconds maximum blocking time, or -1 for no limit. + * @returns TRUE if the I/O path was acquired. + */ +static dbus_bool_t +_dbus_connection_acquire_io_path (DBusConnection *connection, + int timeout_milliseconds) +{ + dbus_bool_t we_acquired; + + HAVE_LOCK_CHECK (connection); + + /* We don't want the connection to vanish */ + _dbus_connection_ref_unlocked (connection); + + /* We will only touch io_path_acquired which is protected by our mutex */ + CONNECTION_UNLOCK (connection); + + _dbus_verbose ("locking io_path_mutex\n"); + _dbus_cmutex_lock (connection->io_path_mutex); + + _dbus_verbose ("start connection->io_path_acquired = %d timeout = %d\n", + connection->io_path_acquired, timeout_milliseconds); + + we_acquired = FALSE; + + if (connection->io_path_acquired) + { + if (timeout_milliseconds != -1) + { + _dbus_verbose ("waiting %d for IO path to be acquirable\n", + timeout_milliseconds); + + if (!_dbus_condvar_wait_timeout (connection->io_path_cond, + connection->io_path_mutex, + timeout_milliseconds)) + { + /* We timed out before anyone signaled. */ + /* (writing the loop to handle the !timedout case by + * waiting longer if needed is a pain since dbus + * wraps pthread_cond_timedwait to take a relative + * time instead of absolute, something kind of stupid + * on our part. for now it doesn't matter, we will just + * end up back here eventually.) + */ + } + } + else + { + while (connection->io_path_acquired) + { + _dbus_verbose ("waiting for IO path to be acquirable\n"); + _dbus_condvar_wait (connection->io_path_cond, + connection->io_path_mutex); + } + } + } + + if (!connection->io_path_acquired) + { + we_acquired = TRUE; + connection->io_path_acquired = TRUE; + } + + _dbus_verbose ("end connection->io_path_acquired = %d we_acquired = %d\n", + connection->io_path_acquired, we_acquired); + + _dbus_verbose ("unlocking io_path_mutex\n"); + _dbus_cmutex_unlock (connection->io_path_mutex); + + CONNECTION_LOCK (connection); + + HAVE_LOCK_CHECK (connection); + + _dbus_connection_unref_unlocked (connection); + + return we_acquired; +} + +/** + * Release the I/O path when you're done with it. Only call + * after you've acquired the I/O. Wakes up at most one thread + * currently waiting to acquire the I/O path. + * + * @param connection the connection. + */ +static void +_dbus_connection_release_io_path (DBusConnection *connection) +{ + HAVE_LOCK_CHECK (connection); + + _dbus_verbose ("locking io_path_mutex\n"); + _dbus_cmutex_lock (connection->io_path_mutex); + + _dbus_assert (connection->io_path_acquired); + + _dbus_verbose ("start connection->io_path_acquired = %d\n", + connection->io_path_acquired); + + connection->io_path_acquired = FALSE; + _dbus_condvar_wake_one (connection->io_path_cond); + + _dbus_verbose ("unlocking io_path_mutex\n"); + _dbus_cmutex_unlock (connection->io_path_mutex); +} + +/** + * Queues incoming messages and sends outgoing messages for this + * connection, optionally blocking in the process. Each call to + * _dbus_connection_do_iteration_unlocked() will call select() or poll() one + * time and then read or write data if possible. + * + * The purpose of this function is to be able to flush outgoing + * messages or queue up incoming messages without returning + * control to the application and causing reentrancy weirdness. + * + * The flags parameter allows you to specify whether to + * read incoming messages, write outgoing messages, or both, + * and whether to block if no immediate action is possible. + * + * The timeout_milliseconds parameter does nothing unless the + * iteration is blocking. + * + * If there are no outgoing messages and DBUS_ITERATION_DO_READING + * wasn't specified, then it's impossible to block, even if + * you specify DBUS_ITERATION_BLOCK; in that case the function + * returns immediately. + * + * If pending is not NULL then a check is made if the pending call + * is completed after the io path has been required. If the call + * has been completed nothing is done. This must be done since + * the _dbus_connection_acquire_io_path releases the connection + * lock for a while. + * + * Called with connection lock held. + * + * @param connection the connection. + * @param pending the pending call that should be checked or NULL + * @param flags iteration flags. + * @param timeout_milliseconds maximum blocking time, or -1 for no limit. + */ +void +_dbus_connection_do_iteration_unlocked (DBusConnection *connection, + DBusPendingCall *pending, + unsigned int flags, + int timeout_milliseconds) +{ + _dbus_verbose ("start\n"); + + HAVE_LOCK_CHECK (connection); + + if (connection->n_outgoing == 0) + flags &= ~DBUS_ITERATION_DO_WRITING; + + if (_dbus_connection_acquire_io_path (connection, + (flags & DBUS_ITERATION_BLOCK) ? timeout_milliseconds : 0)) + { + HAVE_LOCK_CHECK (connection); + + if ( (pending != NULL) && _dbus_pending_call_get_completed_unlocked(pending)) + { + _dbus_verbose ("pending call completed while acquiring I/O path"); + } + else if ( (pending != NULL) && + _dbus_connection_peek_for_reply_unlocked (connection, + _dbus_pending_call_get_reply_serial_unlocked (pending))) + { + _dbus_verbose ("pending call completed while acquiring I/O path (reply found in queue)"); + } + else + { + _dbus_transport_do_iteration (connection->transport, + flags, timeout_milliseconds); + } + + _dbus_connection_release_io_path (connection); + } + + HAVE_LOCK_CHECK (connection); + + _dbus_verbose ("end\n"); +} + +/** + * Creates a new connection for the given transport. A transport + * represents a message stream that uses some concrete mechanism, such + * as UNIX domain sockets. May return #NULL if insufficient + * memory exists to create the connection. + * + * @param transport the transport. + * @returns the new connection, or #NULL on failure. + */ +DBusConnection* +_dbus_connection_new_for_transport (DBusTransport *transport) +{ + DBusConnection *connection; + DBusWatchList *watch_list; + DBusTimeoutList *timeout_list; + DBusHashTable *pending_replies; + DBusList *disconnect_link; + DBusMessage *disconnect_message; + DBusCounter *outgoing_counter; + DBusObjectTree *objects; + + watch_list = NULL; + connection = NULL; + pending_replies = NULL; + timeout_list = NULL; + disconnect_link = NULL; + disconnect_message = NULL; + outgoing_counter = NULL; + objects = NULL; + + watch_list = _dbus_watch_list_new (); + if (watch_list == NULL) + goto error; + + timeout_list = _dbus_timeout_list_new (); + if (timeout_list == NULL) + goto error; + + pending_replies = + _dbus_hash_table_new (DBUS_HASH_INT, + NULL, + (DBusFreeFunction)free_pending_call_on_hash_removal); + if (pending_replies == NULL) + goto error; + + connection = dbus_new0 (DBusConnection, 1); + if (connection == NULL) + goto error; + + _dbus_rmutex_new_at_location (&connection->mutex); + if (connection->mutex == NULL) + goto error; + + _dbus_cmutex_new_at_location (&connection->io_path_mutex); + if (connection->io_path_mutex == NULL) + goto error; + + _dbus_cmutex_new_at_location (&connection->dispatch_mutex); + if (connection->dispatch_mutex == NULL) + goto error; + + _dbus_condvar_new_at_location (&connection->dispatch_cond); + if (connection->dispatch_cond == NULL) + goto error; + + _dbus_condvar_new_at_location (&connection->io_path_cond); + if (connection->io_path_cond == NULL) + goto error; + + _dbus_rmutex_new_at_location (&connection->slot_mutex); + if (connection->slot_mutex == NULL) + goto error; + + disconnect_message = dbus_message_new_signal (DBUS_PATH_LOCAL, + DBUS_INTERFACE_LOCAL, + "Disconnected"); + + if (disconnect_message == NULL) + goto error; + + disconnect_link = _dbus_list_alloc_link (disconnect_message); + if (disconnect_link == NULL) + goto error; + + outgoing_counter = _dbus_counter_new (); + if (outgoing_counter == NULL) + goto error; + + objects = _dbus_object_tree_new (connection); + if (objects == NULL) + goto error; + + if (_dbus_atomic_get (&_dbus_modify_sigpipe) != 0) + _dbus_disable_sigpipe (); + + /* initialized to 0: use atomic op to avoid mixing atomic and non-atomic */ + _dbus_atomic_inc (&connection->refcount); + connection->transport = transport; + connection->watches = watch_list; + connection->timeouts = timeout_list; + connection->pending_replies = pending_replies; + connection->outgoing_counter = outgoing_counter; + connection->filter_list = NULL; + connection->last_dispatch_status = DBUS_DISPATCH_COMPLETE; /* so we're notified first time there's data */ + connection->objects = objects; + connection->exit_on_disconnect = FALSE; + connection->shareable = FALSE; + connection->builtin_filters_enabled = TRUE; + connection->route_peer_messages = FALSE; + connection->disconnected_message_arrived = FALSE; + connection->disconnected_message_processed = FALSE; + +#if defined(DBUS_ENABLE_CHECKS) || defined(DBUS_ENABLE_ASSERT) + connection->generation = _dbus_current_generation; +#endif + + _dbus_data_slot_list_init (&connection->slot_list); + + connection->client_serial = 1; + + connection->disconnect_message_link = disconnect_link; + + CONNECTION_LOCK (connection); + + if (!_dbus_transport_set_connection (transport, connection)) + { + CONNECTION_UNLOCK (connection); + + goto error; + } + + _dbus_transport_ref (transport); + + CONNECTION_UNLOCK (connection); + + _dbus_connection_trace_ref (connection, 0, 1, "new_for_transport"); + return connection; + + error: + if (disconnect_message != NULL) + dbus_message_unref (disconnect_message); + + if (disconnect_link != NULL) + _dbus_list_free_link (disconnect_link); + + if (connection != NULL) + { + _dbus_condvar_free_at_location (&connection->io_path_cond); + _dbus_condvar_free_at_location (&connection->dispatch_cond); + _dbus_rmutex_free_at_location (&connection->mutex); + _dbus_cmutex_free_at_location (&connection->io_path_mutex); + _dbus_cmutex_free_at_location (&connection->dispatch_mutex); + _dbus_rmutex_free_at_location (&connection->slot_mutex); + dbus_free (connection); + } + if (pending_replies) + _dbus_hash_table_unref (pending_replies); + + if (watch_list) + _dbus_watch_list_free (watch_list); + + if (timeout_list) + _dbus_timeout_list_free (timeout_list); + + if (outgoing_counter) + _dbus_counter_unref (outgoing_counter); + + if (objects) + _dbus_object_tree_unref (objects); + + return NULL; +} + +/** + * Increments the reference count of a DBusConnection. + * Requires that the caller already holds the connection lock. + * + * @param connection the connection. + * @returns the connection. + */ +DBusConnection * +_dbus_connection_ref_unlocked (DBusConnection *connection) +{ + dbus_int32_t old_refcount; + + _dbus_assert (connection != NULL); + _dbus_assert (connection->generation == _dbus_current_generation); + + HAVE_LOCK_CHECK (connection); + + old_refcount = _dbus_atomic_inc (&connection->refcount); + _dbus_connection_trace_ref (connection, old_refcount, old_refcount + 1, + "ref_unlocked"); + + return connection; +} + +/** + * Decrements the reference count of a DBusConnection. + * Requires that the caller already holds the connection lock. + * + * @param connection the connection. + */ +void +_dbus_connection_unref_unlocked (DBusConnection *connection) +{ + dbus_int32_t old_refcount; + + HAVE_LOCK_CHECK (connection); + + _dbus_assert (connection != NULL); + + old_refcount = _dbus_atomic_dec (&connection->refcount); + + _dbus_connection_trace_ref (connection, old_refcount, old_refcount - 1, + "unref_unlocked"); + + if (old_refcount == 1) + _dbus_connection_last_unref (connection); +} + +/** + * Allocate and return the next non-zero serial number for outgoing messages. + * + * This method is only valid to call from single-threaded code, such as + * the dbus-daemon, or with the connection lock held. + * + * @param connection the connection + * @returns A suitable serial number for the next message to be sent on the connection. + */ +dbus_uint32_t +_dbus_connection_get_next_client_serial (DBusConnection *connection) +{ + dbus_uint32_t serial; + + serial = connection->client_serial++; + + if (connection->client_serial == 0) + connection->client_serial = 1; + + return serial; +} + +/** + * A callback for use with dbus_watch_new() to create a DBusWatch. + * + * @todo This is basically a hack - we could delete _dbus_transport_handle_watch() + * and the virtual handle_watch in DBusTransport if we got rid of it. + * The reason this is some work is threading, see the _dbus_connection_handle_watch() + * implementation. + * + * @param watch the watch. + * @param condition the current condition of the file descriptors being watched. + * @param data must be a pointer to a #DBusConnection + * @returns #FALSE if the IO condition may not have been fully handled due to lack of memory + */ +dbus_bool_t +_dbus_connection_handle_watch (DBusWatch *watch, + unsigned int condition, + void *data) +{ + DBusConnection *connection; + dbus_bool_t retval; + DBusDispatchStatus status; + + connection = data; + + _dbus_verbose ("start\n"); + + CONNECTION_LOCK (connection); + + if (!_dbus_connection_acquire_io_path (connection, 1)) + { + /* another thread is handling the message */ + CONNECTION_UNLOCK (connection); + return TRUE; + } + + HAVE_LOCK_CHECK (connection); + retval = _dbus_transport_handle_watch (connection->transport, + watch, condition); + + _dbus_connection_release_io_path (connection); + + HAVE_LOCK_CHECK (connection); + + _dbus_verbose ("middle\n"); + + status = _dbus_connection_get_dispatch_status_unlocked (connection); + + /* this calls out to user code */ + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + + _dbus_verbose ("end\n"); + + return retval; +} + +/* Protected by _DBUS_LOCK (shared_connections) */ +static DBusHashTable *shared_connections = NULL; +static DBusList *shared_connections_no_guid = NULL; + +static void +close_connection_on_shutdown (DBusConnection *connection) +{ + DBusMessage *message; + + dbus_connection_ref (connection); + _dbus_connection_close_possibly_shared (connection); + + /* Churn through to the Disconnected message */ + while ((message = dbus_connection_pop_message (connection))) + { + dbus_message_unref (message); + } + dbus_connection_unref (connection); +} + +static void +shared_connections_shutdown (void *data) +{ + int n_entries; + + if (!_DBUS_LOCK (shared_connections)) + { + /* We'd have initialized locks before adding anything, so there + * can't be anything there. */ + return; + } + + /* This is a little bit unpleasant... better ideas? */ + while ((n_entries = _dbus_hash_table_get_n_entries (shared_connections)) > 0) + { + DBusConnection *connection; + DBusHashIter iter; + + _dbus_hash_iter_init (shared_connections, &iter); + _dbus_hash_iter_next (&iter); + + connection = _dbus_hash_iter_get_value (&iter); + + _DBUS_UNLOCK (shared_connections); + close_connection_on_shutdown (connection); + if (!_DBUS_LOCK (shared_connections)) + _dbus_assert_not_reached ("global locks were already initialized"); + + /* The connection should now be dead and not in our hash ... */ + _dbus_assert (_dbus_hash_table_get_n_entries (shared_connections) < n_entries); + } + + _dbus_assert (_dbus_hash_table_get_n_entries (shared_connections) == 0); + + _dbus_hash_table_unref (shared_connections); + shared_connections = NULL; + + if (shared_connections_no_guid != NULL) + { + DBusConnection *connection; + connection = _dbus_list_pop_first (&shared_connections_no_guid); + while (connection != NULL) + { + _DBUS_UNLOCK (shared_connections); + close_connection_on_shutdown (connection); + if (!_DBUS_LOCK (shared_connections)) + _dbus_assert_not_reached ("global locks were already initialized"); + connection = _dbus_list_pop_first (&shared_connections_no_guid); + } + } + + shared_connections_no_guid = NULL; + + _DBUS_UNLOCK (shared_connections); +} + +static dbus_bool_t +connection_lookup_shared (DBusAddressEntry *entry, + DBusConnection **result) +{ + _dbus_verbose ("checking for existing connection\n"); + + *result = NULL; + + if (!_DBUS_LOCK (shared_connections)) + { + /* If it was shared, we'd have initialized global locks when we put + * it in shared_connections. */ + return FALSE; + } + + if (shared_connections == NULL) + { + _dbus_verbose ("creating shared_connections hash table\n"); + + shared_connections = _dbus_hash_table_new (DBUS_HASH_STRING, + dbus_free, + NULL); + if (shared_connections == NULL) + { + _DBUS_UNLOCK (shared_connections); + return FALSE; + } + + if (!_dbus_register_shutdown_func (shared_connections_shutdown, NULL)) + { + _dbus_hash_table_unref (shared_connections); + shared_connections = NULL; + _DBUS_UNLOCK (shared_connections); + return FALSE; + } + + _dbus_verbose (" successfully created shared_connections\n"); + + _DBUS_UNLOCK (shared_connections); + return TRUE; /* no point looking up in the hash we just made */ + } + else + { + const char *guid; + + guid = dbus_address_entry_get_value (entry, "guid"); + + if (guid != NULL) + { + DBusConnection *connection; + + connection = _dbus_hash_table_lookup_string (shared_connections, + guid); + + if (connection) + { + /* The DBusConnection can't be finalized without taking + * the shared_connections lock to remove it from the + * hash. So it's safe to ref the connection here. + * However, it may be disconnected if the Disconnected + * message hasn't been processed yet, in which case we + * want to pretend it isn't in the hash and avoid + * returning it. + * + * The idea is to avoid ever returning a disconnected connection + * from dbus_connection_open(). We could just synchronously + * drop our shared ref to the connection on connection disconnect, + * and then assert here that the connection is connected, but + * that causes reentrancy headaches. + */ + CONNECTION_LOCK (connection); + if (_dbus_connection_get_is_connected_unlocked (connection)) + { + _dbus_connection_ref_unlocked (connection); + *result = connection; + _dbus_verbose ("looked up existing connection to server guid %s\n", + guid); + } + else + { + _dbus_verbose ("looked up existing connection to server guid %s but it was disconnected so ignoring it\n", + guid); + } + CONNECTION_UNLOCK (connection); + } + } + + _DBUS_UNLOCK (shared_connections); + return TRUE; + } +} + +static dbus_bool_t +connection_record_shared_unlocked (DBusConnection *connection, + const char *guid) +{ + char *guid_key; + char *guid_in_connection; + + HAVE_LOCK_CHECK (connection); + _dbus_assert (connection->server_guid == NULL); + _dbus_assert (connection->shareable); + + /* get a hard ref on this connection, even if + * we won't in fact store it in the hash, we still + * need to hold a ref on it until it's disconnected. + */ + _dbus_connection_ref_unlocked (connection); + + if (guid == NULL) + { + if (!_DBUS_LOCK (shared_connections)) + return FALSE; + + if (!_dbus_list_prepend (&shared_connections_no_guid, connection)) + { + _DBUS_UNLOCK (shared_connections); + return FALSE; + } + + _DBUS_UNLOCK (shared_connections); + return TRUE; /* don't store in the hash */ + } + + /* A separate copy of the key is required in the hash table, because + * we don't have a lock on the connection when we are doing a hash + * lookup. + */ + + guid_key = _dbus_strdup (guid); + if (guid_key == NULL) + return FALSE; + + guid_in_connection = _dbus_strdup (guid); + if (guid_in_connection == NULL) + { + dbus_free (guid_key); + return FALSE; + } + + if (!_DBUS_LOCK (shared_connections)) + { + dbus_free (guid_in_connection); + dbus_free (guid_key); + return FALSE; + } + + _dbus_assert (shared_connections != NULL); + + if (!_dbus_hash_table_insert_string (shared_connections, + guid_key, connection)) + { + dbus_free (guid_key); + dbus_free (guid_in_connection); + _DBUS_UNLOCK (shared_connections); + return FALSE; + } + + connection->server_guid = guid_in_connection; + + _dbus_verbose ("stored connection to %s to be shared\n", + connection->server_guid); + + _DBUS_UNLOCK (shared_connections); + + _dbus_assert (connection->server_guid != NULL); + + return TRUE; +} + +static void +connection_forget_shared_unlocked (DBusConnection *connection) +{ + HAVE_LOCK_CHECK (connection); + + if (!connection->shareable) + return; + + if (!_DBUS_LOCK (shared_connections)) + { + /* If it was shared, we'd have initialized global locks when we put + * it in the table; so it can't be there. */ + return; + } + + if (connection->server_guid != NULL) + { + _dbus_verbose ("dropping connection to %s out of the shared table\n", + connection->server_guid); + + if (!_dbus_hash_table_remove_string (shared_connections, + connection->server_guid)) + _dbus_assert_not_reached ("connection was not in the shared table"); + + dbus_free (connection->server_guid); + connection->server_guid = NULL; + } + else + { + _dbus_list_remove (&shared_connections_no_guid, connection); + } + + _DBUS_UNLOCK (shared_connections); + + /* remove our reference held on all shareable connections */ + _dbus_connection_unref_unlocked (connection); +} + +static DBusConnection* +connection_try_from_address_entry (DBusAddressEntry *entry, + DBusError *error) +{ + DBusTransport *transport; + DBusConnection *connection; + + transport = _dbus_transport_open (entry, error); + + if (transport == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return NULL; + } + + connection = _dbus_connection_new_for_transport (transport); + + _dbus_transport_unref (transport); + + if (connection == NULL) + { + _DBUS_SET_OOM (error); + return NULL; + } + +#ifndef DBUS_DISABLE_CHECKS + _dbus_assert (!connection->have_connection_lock); +#endif + return connection; +} + +/* + * If the shared parameter is true, then any existing connection will + * be used (and if a new connection is created, it will be available + * for use by others). If the shared parameter is false, a new + * connection will always be created, and the new connection will + * never be returned to other callers. + * + * @param address the address + * @param shared whether the connection is shared or private + * @param error error return + * @returns the connection or #NULL on error + */ +static DBusConnection* +_dbus_connection_open_internal (const char *address, + dbus_bool_t shared, + DBusError *error) +{ + DBusConnection *connection; + DBusAddressEntry **entries; + DBusError tmp_error = DBUS_ERROR_INIT; + DBusError first_error = DBUS_ERROR_INIT; + int len, i; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_verbose ("opening %s connection to: %s\n", + shared ? "shared" : "private", address); + + if (!dbus_parse_address (address, &entries, &len, error)) + return NULL; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + connection = NULL; + + for (i = 0; i < len; i++) + { + if (shared) + { + if (!connection_lookup_shared (entries[i], &connection)) + _DBUS_SET_OOM (&tmp_error); + } + + if (connection == NULL) + { + connection = connection_try_from_address_entry (entries[i], + &tmp_error); + + if (connection != NULL && shared) + { + const char *guid; + + connection->shareable = TRUE; + + /* guid may be NULL */ + guid = dbus_address_entry_get_value (entries[i], "guid"); + + CONNECTION_LOCK (connection); + + if (!connection_record_shared_unlocked (connection, guid)) + { + _DBUS_SET_OOM (&tmp_error); + _dbus_connection_close_possibly_shared_and_unlock (connection); + dbus_connection_unref (connection); + connection = NULL; + } + else + CONNECTION_UNLOCK (connection); + } + } + + if (connection) + break; + + _DBUS_ASSERT_ERROR_IS_SET (&tmp_error); + + if (i == 0) + dbus_move_error (&tmp_error, &first_error); + else + dbus_error_free (&tmp_error); + } + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error); + + if (connection == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (&first_error); + dbus_move_error (&first_error, error); + } + else + dbus_error_free (&first_error); + + dbus_address_entries_free (entries); + return connection; +} + +/** + * Closes a shared OR private connection, while dbus_connection_close() can + * only be used on private connections. Should only be called by the + * dbus code that owns the connection - an owner must be known, + * the open/close state is like malloc/free, not like ref/unref. + * + * @param connection the connection + */ +void +_dbus_connection_close_possibly_shared (DBusConnection *connection) +{ + _dbus_assert (connection != NULL); + _dbus_assert (connection->generation == _dbus_current_generation); + + CONNECTION_LOCK (connection); + _dbus_connection_close_possibly_shared_and_unlock (connection); +} + +static DBusPreallocatedSend* +_dbus_connection_preallocate_send_unlocked (DBusConnection *connection) +{ + DBusPreallocatedSend *preallocated; + + HAVE_LOCK_CHECK (connection); + + _dbus_assert (connection != NULL); + + preallocated = dbus_new (DBusPreallocatedSend, 1); + if (preallocated == NULL) + return NULL; + + preallocated->queue_link = _dbus_list_alloc_link (NULL); + if (preallocated->queue_link == NULL) + goto failed_0; + + preallocated->counter_link = _dbus_list_alloc_link (connection->outgoing_counter); + if (preallocated->counter_link == NULL) + goto failed_1; + + _dbus_counter_ref (preallocated->counter_link->data); + + preallocated->connection = connection; + + return preallocated; + + failed_1: + _dbus_list_free_link (preallocated->queue_link); + failed_0: + dbus_free (preallocated); + + return NULL; +} + +/* Called with lock held, does not update dispatch status */ +static void +_dbus_connection_send_preallocated_unlocked_no_update (DBusConnection *connection, + DBusPreallocatedSend *preallocated, + DBusMessage *message, + dbus_uint32_t *client_serial) +{ + dbus_uint32_t serial; + + preallocated->queue_link->data = message; + _dbus_list_prepend_link (&connection->outgoing_messages, + preallocated->queue_link); + + /* It's OK that we'll never call the notify function, because for the + * outgoing limit, there isn't one */ + _dbus_message_add_counter_link (message, + preallocated->counter_link); + + dbus_free (preallocated); + preallocated = NULL; + + dbus_message_ref (message); + + connection->n_outgoing += 1; + + _dbus_verbose ("Message %p (%s %s %s %s '%s') for %s added to outgoing queue %p, %d pending to send\n", + message, + dbus_message_type_to_string (dbus_message_get_type (message)), + dbus_message_get_path (message) ? + dbus_message_get_path (message) : + "no path", + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : + "no interface", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : + "no member", + dbus_message_get_signature (message), + dbus_message_get_destination (message) ? + dbus_message_get_destination (message) : + "null", + connection, + connection->n_outgoing); + + if (dbus_message_get_serial (message) == 0) + { + serial = _dbus_connection_get_next_client_serial (connection); + dbus_message_set_serial (message, serial); + if (client_serial) + *client_serial = serial; + } + else + { + if (client_serial) + *client_serial = dbus_message_get_serial (message); + } + + _dbus_verbose ("Message %p serial is %u\n", + message, dbus_message_get_serial (message)); + + dbus_message_lock (message); + + /* Now we need to run an iteration to hopefully just write the messages + * out immediately, and otherwise get them queued up + */ + _dbus_connection_do_iteration_unlocked (connection, + NULL, + DBUS_ITERATION_DO_WRITING, + -1); + + /* If stuff is still queued up, be sure we wake up the main loop */ + if (connection->n_outgoing > 0) + _dbus_connection_wakeup_mainloop (connection); +} + +static void +_dbus_connection_send_preallocated_and_unlock (DBusConnection *connection, + DBusPreallocatedSend *preallocated, + DBusMessage *message, + dbus_uint32_t *client_serial) +{ + DBusDispatchStatus status; + + HAVE_LOCK_CHECK (connection); + + _dbus_connection_send_preallocated_unlocked_no_update (connection, + preallocated, + message, client_serial); + + _dbus_verbose ("middle\n"); + status = _dbus_connection_get_dispatch_status_unlocked (connection); + + /* this calls out to user code */ + _dbus_connection_update_dispatch_status_and_unlock (connection, status); +} + +/** + * Like dbus_connection_send(), but assumes the connection + * is already locked on function entry, and unlocks before returning. + * + * @param connection the connection + * @param message the message to send + * @param client_serial return location for client serial of sent message + * @returns #FALSE on out-of-memory + */ +dbus_bool_t +_dbus_connection_send_and_unlock (DBusConnection *connection, + DBusMessage *message, + dbus_uint32_t *client_serial) +{ + DBusPreallocatedSend *preallocated; + + _dbus_assert (connection != NULL); + _dbus_assert (message != NULL); + + preallocated = _dbus_connection_preallocate_send_unlocked (connection); + if (preallocated == NULL) + { + CONNECTION_UNLOCK (connection); + return FALSE; + } + + _dbus_connection_send_preallocated_and_unlock (connection, + preallocated, + message, + client_serial); + return TRUE; +} + +/** + * Used internally to handle the semantics of dbus_server_set_new_connection_function(). + * If the new connection function does not ref the connection, we want to close it. + * + * A bit of a hack, probably the new connection function should have returned a value + * for whether to close, or should have had to close the connection itself if it + * didn't want it. + * + * But, this works OK as long as the new connection function doesn't do anything + * crazy like keep the connection around without ref'ing it. + * + * We have to lock the connection across refcount check and close in case + * the new connection function spawns a thread that closes and unrefs. + * In that case, if the app thread + * closes and unrefs first, we'll harmlessly close again; if the app thread + * still has the ref, we'll close and then the app will close harmlessly. + * If the app unrefs without closing, the app is broken since if the + * app refs from the new connection function it is supposed to also close. + * + * If we didn't atomically check the refcount and close with the lock held + * though, we could screw this up. + * + * @param connection the connection + */ +void +_dbus_connection_close_if_only_one_ref (DBusConnection *connection) +{ + dbus_int32_t refcount; + + CONNECTION_LOCK (connection); + + refcount = _dbus_atomic_get (&connection->refcount); + /* The caller should have at least one ref */ + _dbus_assert (refcount >= 1); + + if (refcount == 1) + _dbus_connection_close_possibly_shared_and_unlock (connection); + else + CONNECTION_UNLOCK (connection); +} + + +/** + * When a function that blocks has been called with a timeout, and we + * run out of memory, the time to wait for memory is based on the + * timeout. If the caller was willing to block a long time we wait a + * relatively long time for memory, if they were only willing to block + * briefly then we retry for memory at a rapid rate. + * + * @param timeout_milliseconds the timeout requested for blocking + */ +static void +_dbus_memory_pause_based_on_timeout (int timeout_milliseconds) +{ + if (timeout_milliseconds == -1) + _dbus_sleep_milliseconds (1000); + else if (timeout_milliseconds < 100) + ; /* just busy loop */ + else if (timeout_milliseconds <= 1000) + _dbus_sleep_milliseconds (timeout_milliseconds / 3); + else + _dbus_sleep_milliseconds (1000); +} + +static DBusMessage * +generate_local_error_message (dbus_uint32_t serial, + const char *error_name, + const char *error_msg) +{ + DBusMessage *message; + message = dbus_message_new (DBUS_MESSAGE_TYPE_ERROR); + if (!message) + goto out; + + if (!dbus_message_set_error_name (message, error_name)) + { + dbus_message_unref (message); + message = NULL; + goto out; + } + + dbus_message_set_no_reply (message, TRUE); + + if (!dbus_message_set_reply_serial (message, + serial)) + { + dbus_message_unref (message); + message = NULL; + goto out; + } + + if (error_msg != NULL) + { + DBusMessageIter iter; + + dbus_message_iter_init_append (message, &iter); + if (!dbus_message_iter_append_basic (&iter, + DBUS_TYPE_STRING, + &error_msg)) + { + dbus_message_unref (message); + message = NULL; + goto out; + } + } + + out: + return message; +} + +/* + * Peek the incoming queue to see if we got reply for a specific serial + */ +static dbus_bool_t +_dbus_connection_peek_for_reply_unlocked (DBusConnection *connection, + dbus_uint32_t client_serial) +{ + DBusList *link; + HAVE_LOCK_CHECK (connection); + + link = _dbus_list_get_first_link (&connection->incoming_messages); + + while (link != NULL) + { + DBusMessage *reply = link->data; + + if (dbus_message_get_reply_serial (reply) == client_serial) + { + _dbus_verbose ("%s reply to %d found in queue\n", _DBUS_FUNCTION_NAME, client_serial); + return TRUE; + } + link = _dbus_list_get_next_link (&connection->incoming_messages, link); + } + + return FALSE; +} + +/* This is slightly strange since we can pop a message here without + * the dispatch lock. + */ +static DBusMessage* +check_for_reply_unlocked (DBusConnection *connection, + dbus_uint32_t client_serial) +{ + DBusList *link; + + HAVE_LOCK_CHECK (connection); + + link = _dbus_list_get_first_link (&connection->incoming_messages); + + while (link != NULL) + { + DBusMessage *reply = link->data; + + if (dbus_message_get_reply_serial (reply) == client_serial) + { + _dbus_list_remove_link (&connection->incoming_messages, link); + connection->n_incoming -= 1; + return reply; + } + link = _dbus_list_get_next_link (&connection->incoming_messages, link); + } + + return NULL; +} + +static void +connection_timeout_and_complete_all_pending_calls_unlocked (DBusConnection *connection) +{ + /* We can't iterate over the hash in the normal way since we'll be + * dropping the lock for each item. So we restart the + * iter each time as we drain the hash table. + */ + + while (_dbus_hash_table_get_n_entries (connection->pending_replies) > 0) + { + DBusPendingCall *pending; + DBusHashIter iter; + + _dbus_hash_iter_init (connection->pending_replies, &iter); + _dbus_hash_iter_next (&iter); + + pending = _dbus_hash_iter_get_value (&iter); + _dbus_pending_call_ref_unlocked (pending); + + _dbus_pending_call_queue_timeout_error_unlocked (pending, + connection); + + if (_dbus_pending_call_is_timeout_added_unlocked (pending)) + _dbus_connection_remove_timeout_unlocked (connection, + _dbus_pending_call_get_timeout_unlocked (pending)); + _dbus_pending_call_set_timeout_added_unlocked (pending, FALSE); + _dbus_hash_iter_remove_entry (&iter); + + _dbus_pending_call_unref_and_unlock (pending); + CONNECTION_LOCK (connection); + } + HAVE_LOCK_CHECK (connection); +} + +static void +complete_pending_call_and_unlock (DBusConnection *connection, + DBusPendingCall *pending, + DBusMessage *message) +{ + _dbus_pending_call_set_reply_unlocked (pending, message); + _dbus_pending_call_ref_unlocked (pending); /* in case there's no app with a ref held */ + _dbus_pending_call_start_completion_unlocked(pending); + _dbus_connection_detach_pending_call_and_unlock (connection, pending); + + /* Must be called unlocked since it invokes app callback */ + _dbus_pending_call_finish_completion (pending); + dbus_pending_call_unref (pending); +} + +static dbus_bool_t +check_for_reply_and_update_dispatch_unlocked (DBusConnection *connection, + DBusPendingCall *pending) +{ + DBusMessage *reply; + DBusDispatchStatus status; + + reply = check_for_reply_unlocked (connection, + _dbus_pending_call_get_reply_serial_unlocked (pending)); + if (reply != NULL) + { + _dbus_verbose ("checked for reply\n"); + + _dbus_verbose ("dbus_connection_send_with_reply_and_block(): got reply\n"); + + complete_pending_call_and_unlock (connection, pending, reply); + dbus_message_unref (reply); + + CONNECTION_LOCK (connection); + status = _dbus_connection_get_dispatch_status_unlocked (connection); + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + dbus_pending_call_unref (pending); + + return TRUE; + } + + return FALSE; +} + +/** + * Blocks until a pending call times out or gets a reply. + * + * Does not re-enter the main loop or run filter/path-registered + * callbacks. The reply to the message will not be seen by + * filter callbacks. + * + * Returns immediately if pending call already got a reply. + * + * @todo could use performance improvements (it keeps scanning + * the whole message queue for example) + * + * @param pending the pending call we block for a reply on + */ +void +_dbus_connection_block_pending_call (DBusPendingCall *pending) +{ + dbus_int64_t start_tv_sec; + long start_tv_usec; + dbus_int64_t tv_sec; + long tv_usec; + DBusDispatchStatus status; + DBusConnection *connection; + dbus_uint32_t client_serial; + DBusTimeout *timeout; + int timeout_milliseconds, elapsed_milliseconds; + + _dbus_assert (pending != NULL); + + if (dbus_pending_call_get_completed (pending)) + return; + + dbus_pending_call_ref (pending); /* necessary because the call could be canceled */ + + connection = _dbus_pending_call_get_connection_and_lock (pending); + + /* Flush message queue - note, can affect dispatch status */ + _dbus_connection_flush_unlocked (connection); + + client_serial = _dbus_pending_call_get_reply_serial_unlocked (pending); + + /* note that timeout_milliseconds is limited to a smallish value + * in _dbus_pending_call_new() so overflows aren't possible + * below + */ + timeout = _dbus_pending_call_get_timeout_unlocked (pending); + _dbus_get_monotonic_time (&start_tv_sec, &start_tv_usec); + if (timeout) + { + timeout_milliseconds = dbus_timeout_get_interval (timeout); + + _dbus_verbose ("dbus_connection_send_with_reply_and_block(): will block %d milliseconds for reply serial %u from %" DBUS_INT64_MODIFIER "d sec %ld usec\n", + timeout_milliseconds, + client_serial, + start_tv_sec, start_tv_usec); + } + else + { + timeout_milliseconds = -1; + + _dbus_verbose ("dbus_connection_send_with_reply_and_block(): will block for reply serial %u\n", client_serial); + } + + /* check to see if we already got the data off the socket */ + /* from another blocked pending call */ + if (check_for_reply_and_update_dispatch_unlocked (connection, pending)) + return; + + /* Now we wait... */ + /* always block at least once as we know we don't have the reply yet */ + _dbus_connection_do_iteration_unlocked (connection, + pending, + DBUS_ITERATION_DO_READING | + DBUS_ITERATION_BLOCK, + timeout_milliseconds); + + recheck_status: + + _dbus_verbose ("top of recheck\n"); + + HAVE_LOCK_CHECK (connection); + + /* queue messages and get status */ + + status = _dbus_connection_get_dispatch_status_unlocked (connection); + + /* the get_completed() is in case a dispatch() while we were blocking + * got the reply instead of us. + */ + if (_dbus_pending_call_get_completed_unlocked (pending)) + { + _dbus_verbose ("Pending call completed by dispatch\n"); + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + dbus_pending_call_unref (pending); + return; + } + + if (status == DBUS_DISPATCH_DATA_REMAINS) + { + if (check_for_reply_and_update_dispatch_unlocked (connection, pending)) + return; + } + + _dbus_get_monotonic_time (&tv_sec, &tv_usec); + elapsed_milliseconds = (tv_sec - start_tv_sec) * 1000 + + (tv_usec - start_tv_usec) / 1000; + + if (!_dbus_connection_get_is_connected_unlocked (connection)) + { + DBusMessage *error_msg; + + error_msg = generate_local_error_message (client_serial, + DBUS_ERROR_DISCONNECTED, + "Connection was disconnected before a reply was received"); + + /* on OOM error_msg is set to NULL */ + complete_pending_call_and_unlock (connection, pending, error_msg); + if (error_msg != NULL) + dbus_message_unref (error_msg); + dbus_pending_call_unref (pending); + return; + } + else if (connection->disconnect_message_link == NULL) + _dbus_verbose ("dbus_connection_send_with_reply_and_block(): disconnected\n"); + else if (timeout == NULL) + { + if (status == DBUS_DISPATCH_NEED_MEMORY) + { + /* Try sleeping a bit, as we aren't sure we need to block for reading, + * we may already have a reply in the buffer and just can't process + * it. + */ + _dbus_verbose ("dbus_connection_send_with_reply_and_block() waiting for more memory\n"); + + _dbus_memory_pause_based_on_timeout (timeout_milliseconds - elapsed_milliseconds); + } + else + { + /* block again, we don't have the reply buffered yet. */ + _dbus_connection_do_iteration_unlocked (connection, + pending, + DBUS_ITERATION_DO_READING | + DBUS_ITERATION_BLOCK, + timeout_milliseconds - elapsed_milliseconds); + } + + goto recheck_status; + } + else if (tv_sec < start_tv_sec) + _dbus_verbose ("dbus_connection_send_with_reply_and_block(): clock set backward\n"); + else if (elapsed_milliseconds < timeout_milliseconds) + { + _dbus_verbose ("dbus_connection_send_with_reply_and_block(): %d milliseconds remain\n", timeout_milliseconds - elapsed_milliseconds); + + if (status == DBUS_DISPATCH_NEED_MEMORY) + { + /* Try sleeping a bit, as we aren't sure we need to block for reading, + * we may already have a reply in the buffer and just can't process + * it. + */ + _dbus_verbose ("dbus_connection_send_with_reply_and_block() waiting for more memory\n"); + + _dbus_memory_pause_based_on_timeout (timeout_milliseconds - elapsed_milliseconds); + } + else + { + /* block again, we don't have the reply buffered yet. */ + _dbus_connection_do_iteration_unlocked (connection, + pending, + DBUS_ITERATION_DO_READING | + DBUS_ITERATION_BLOCK, + timeout_milliseconds - elapsed_milliseconds); + } + + goto recheck_status; + } + + _dbus_verbose ("dbus_connection_send_with_reply_and_block(): Waited %d milliseconds and got no reply\n", + elapsed_milliseconds); + + _dbus_assert (!_dbus_pending_call_get_completed_unlocked (pending)); + + /* unlock and call user code */ + complete_pending_call_and_unlock (connection, pending, NULL); + + /* update user code on dispatch status */ + CONNECTION_LOCK (connection); + status = _dbus_connection_get_dispatch_status_unlocked (connection); + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + dbus_pending_call_unref (pending); +} + +/** + * Return how many file descriptors are pending in the loader + * + * @param connection the connection + */ +int +_dbus_connection_get_pending_fds_count (DBusConnection *connection) +{ + return _dbus_transport_get_pending_fds_count (connection->transport); +} + +/** + * Register a function to be called whenever the number of pending file + * descriptors in the loader change. + * + * @param connection the connection + * @param callback the callback + */ +void +_dbus_connection_set_pending_fds_function (DBusConnection *connection, + DBusPendingFdsChangeFunction callback, + void *data) +{ + _dbus_transport_set_pending_fds_function (connection->transport, + callback, data); +} + +/** @} */ + +/** + * @addtogroup DBusConnection + * + * @{ + */ + +/** + * Gets a connection to a remote address. If a connection to the given + * address already exists, returns the existing connection with its + * reference count incremented. Otherwise, returns a new connection + * and saves the new connection for possible re-use if a future call + * to dbus_connection_open() asks to connect to the same server. + * + * Use dbus_connection_open_private() to get a dedicated connection + * not shared with other callers of dbus_connection_open(). + * + * If the open fails, the function returns #NULL, and provides a + * reason for the failure in the error parameter. Pass #NULL for the + * error parameter if you aren't interested in the reason for + * failure. + * + * Because this connection is shared, no user of the connection + * may call dbus_connection_close(). However, when you are done with the + * connection you should call dbus_connection_unref(). + * + * @note Prefer dbus_connection_open() to dbus_connection_open_private() + * unless you have good reason; connections are expensive enough + * that it's wasteful to create lots of connections to the same + * server. + * + * @param address the address. + * @param error address where an error can be returned. + * @returns new connection, or #NULL on failure. + */ +DBusConnection* +dbus_connection_open (const char *address, + DBusError *error) +{ + DBusConnection *connection; + + _dbus_return_val_if_fail (address != NULL, NULL); + _dbus_return_val_if_error_is_set (error, NULL); + + connection = _dbus_connection_open_internal (address, + TRUE, + error); + + return connection; +} + +/** + * Opens a new, dedicated connection to a remote address. Unlike + * dbus_connection_open(), always creates a new connection. + * This connection will not be saved or recycled by libdbus. + * + * If the open fails, the function returns #NULL, and provides a + * reason for the failure in the error parameter. Pass #NULL for the + * error parameter if you aren't interested in the reason for + * failure. + * + * When you are done with this connection, you must + * dbus_connection_close() to disconnect it, + * and dbus_connection_unref() to free the connection object. + * + * (The dbus_connection_close() can be skipped if the + * connection is already known to be disconnected, for example + * if you are inside a handler for the Disconnected signal.) + * + * @note Prefer dbus_connection_open() to dbus_connection_open_private() + * unless you have good reason; connections are expensive enough + * that it's wasteful to create lots of connections to the same + * server. + * + * @param address the address. + * @param error address where an error can be returned. + * @returns new connection, or #NULL on failure. + */ +DBusConnection* +dbus_connection_open_private (const char *address, + DBusError *error) +{ + DBusConnection *connection; + + _dbus_return_val_if_fail (address != NULL, NULL); + _dbus_return_val_if_error_is_set (error, NULL); + + connection = _dbus_connection_open_internal (address, + FALSE, + error); + + return connection; +} + +/** + * Increments the reference count of a DBusConnection. + * + * @param connection the connection. + * @returns the connection. + */ +DBusConnection * +dbus_connection_ref (DBusConnection *connection) +{ + dbus_int32_t old_refcount; + + _dbus_return_val_if_fail (connection != NULL, NULL); + _dbus_return_val_if_fail (connection->generation == _dbus_current_generation, NULL); + old_refcount = _dbus_atomic_inc (&connection->refcount); + _dbus_connection_trace_ref (connection, old_refcount, old_refcount + 1, + "ref"); + + return connection; +} + +static void +free_outgoing_message (void *element, + void *data) +{ + DBusMessage *message = element; + DBusConnection *connection = data; + + _dbus_message_remove_counter (message, connection->outgoing_counter); + dbus_message_unref (message); +} + +/* This is run without the mutex held, but after the last reference + * to the connection has been dropped we should have no thread-related + * problems + */ +static void +_dbus_connection_last_unref (DBusConnection *connection) +{ + DBusList *link; + + _dbus_verbose ("Finalizing connection %p\n", connection); + + _dbus_assert (_dbus_atomic_get (&connection->refcount) == 0); + + /* You have to disconnect the connection before unref:ing it. Otherwise + * you won't get the disconnected message. + */ + _dbus_assert (!_dbus_transport_get_is_connected (connection->transport)); + _dbus_assert (connection->server_guid == NULL); + + /* ---- We're going to call various application callbacks here, hope it doesn't break anything... */ + _dbus_object_tree_free_all_unlocked (connection->objects); + + dbus_connection_set_dispatch_status_function (connection, NULL, NULL, NULL); + dbus_connection_set_wakeup_main_function (connection, NULL, NULL, NULL); + dbus_connection_set_unix_user_function (connection, NULL, NULL, NULL); + dbus_connection_set_windows_user_function (connection, NULL, NULL, NULL); + + _dbus_watch_list_free (connection->watches); + connection->watches = NULL; + + _dbus_timeout_list_free (connection->timeouts); + connection->timeouts = NULL; + + _dbus_data_slot_list_free (&connection->slot_list); + + link = _dbus_list_get_first_link (&connection->filter_list); + while (link != NULL) + { + DBusMessageFilter *filter = link->data; + DBusList *next = _dbus_list_get_next_link (&connection->filter_list, link); + + filter->function = NULL; + _dbus_message_filter_unref (filter); /* calls app callback */ + link->data = NULL; + + link = next; + } + _dbus_list_clear (&connection->filter_list); + + /* ---- Done with stuff that invokes application callbacks */ + + _dbus_object_tree_unref (connection->objects); + + _dbus_hash_table_unref (connection->pending_replies); + connection->pending_replies = NULL; + + _dbus_list_foreach (&connection->outgoing_messages, + free_outgoing_message, + connection); + _dbus_list_clear (&connection->outgoing_messages); + + _dbus_list_clear_full (&connection->incoming_messages, + (DBusFreeFunction) dbus_message_unref); + + _dbus_counter_unref (connection->outgoing_counter); + + _dbus_transport_unref (connection->transport); + + if (connection->disconnect_message_link) + { + DBusMessage *message = connection->disconnect_message_link->data; + dbus_message_unref (message); + _dbus_list_free_link (connection->disconnect_message_link); + } + + _dbus_condvar_free_at_location (&connection->dispatch_cond); + _dbus_condvar_free_at_location (&connection->io_path_cond); + + _dbus_cmutex_free_at_location (&connection->io_path_mutex); + _dbus_cmutex_free_at_location (&connection->dispatch_mutex); + + _dbus_rmutex_free_at_location (&connection->slot_mutex); + + _dbus_rmutex_free_at_location (&connection->mutex); + + dbus_free (connection); +} + +/** + * Decrements the reference count of a DBusConnection, and finalizes + * it if the count reaches zero. + * + * Note: it is a bug to drop the last reference to a connection that + * is still connected. + * + * For shared connections, libdbus will own a reference + * as long as the connection is connected, so you can know that either + * you don't have the last reference, or it's OK to drop the last reference. + * Most connections are shared. dbus_connection_open() and dbus_bus_get() + * return shared connections. + * + * For private connections, the creator of the connection must arrange for + * dbus_connection_close() to be called prior to dropping the last reference. + * Private connections come from dbus_connection_open_private() or dbus_bus_get_private(). + * + * @param connection the connection. + */ +void +dbus_connection_unref (DBusConnection *connection) +{ + dbus_int32_t old_refcount; + + _dbus_return_if_fail (connection != NULL); + _dbus_return_if_fail (connection->generation == _dbus_current_generation); + + old_refcount = _dbus_atomic_dec (&connection->refcount); + + _dbus_connection_trace_ref (connection, old_refcount, old_refcount - 1, + "unref"); + + if (old_refcount == 1) + { +#ifndef DBUS_DISABLE_CHECKS + if (_dbus_transport_get_is_connected (connection->transport)) + { + _dbus_warn_check_failed ("The last reference on a connection was dropped without closing the connection. This is a bug in an application. See dbus_connection_unref() documentation for details.\n%s", + connection->shareable ? + "Most likely, the application called unref() too many times and removed a reference belonging to libdbus, since this is a shared connection." : + "Most likely, the application was supposed to call dbus_connection_close(), since this is a private connection."); + return; + } +#endif + _dbus_connection_last_unref (connection); + } +} + +/* + * Note that the transport can disconnect itself (other end drops us) + * and in that case this function never runs. So this function must + * not do anything more than disconnect the transport and update the + * dispatch status. + * + * If the transport self-disconnects, then we assume someone will + * dispatch the connection to cause the dispatch status update. + */ +static void +_dbus_connection_close_possibly_shared_and_unlock (DBusConnection *connection) +{ + DBusDispatchStatus status; + + HAVE_LOCK_CHECK (connection); + + _dbus_verbose ("Disconnecting %p\n", connection); + + /* We need to ref because update_dispatch_status_and_unlock will unref + * the connection if it was shared and libdbus was the only remaining + * refcount holder. + */ + _dbus_connection_ref_unlocked (connection); + + _dbus_transport_disconnect (connection->transport); + + /* This has the side effect of queuing the disconnect message link + * (unless we don't have enough memory, possibly, so don't assert it). + * After the disconnect message link is queued, dbus_bus_get/dbus_connection_open + * should never again return the newly-disconnected connection. + * + * However, we only unref the shared connection and exit_on_disconnect when + * the disconnect message reaches the head of the message queue, + * NOT when it's first queued. + */ + status = _dbus_connection_get_dispatch_status_unlocked (connection); + + /* This calls out to user code */ + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + + /* Could also call out to user code */ + dbus_connection_unref (connection); +} + +/** + * Closes a private connection, so no further data can be sent or received. + * This disconnects the transport (such as a socket) underlying the + * connection. + * + * Attempts to send messages after closing a connection are safe, but will result in + * error replies generated locally in libdbus. + * + * This function does not affect the connection's reference count. It's + * safe to close a connection more than once; all calls after the + * first do nothing. It's impossible to "reopen" a connection, a + * new connection must be created. This function may result in a call + * to the DBusDispatchStatusFunction set with + * dbus_connection_set_dispatch_status_function(), as the disconnect + * message it generates needs to be dispatched. + * + * If a connection is dropped by the remote application, it will + * close itself. + * + * You must close a connection prior to releasing the last reference to + * the connection. If you dbus_connection_unref() for the last time + * without closing the connection, the results are undefined; it + * is a bug in your program and libdbus will try to print a warning. + * + * You may not close a shared connection. Connections created with + * dbus_connection_open() or dbus_bus_get() are shared. + * These connections are owned by libdbus, and applications should + * only unref them, never close them. Applications can know it is + * safe to unref these connections because libdbus will be holding a + * reference as long as the connection is open. Thus, either the + * connection is closed and it is OK to drop the last reference, + * or the connection is open and the app knows it does not have the + * last reference. + * + * Connections created with dbus_connection_open_private() or + * dbus_bus_get_private() are not kept track of or referenced by + * libdbus. The creator of these connections is responsible for + * calling dbus_connection_close() prior to releasing the last + * reference, if the connection is not already disconnected. + * + * @param connection the private (unshared) connection to close + */ +void +dbus_connection_close (DBusConnection *connection) +{ + _dbus_return_if_fail (connection != NULL); + _dbus_return_if_fail (connection->generation == _dbus_current_generation); + + CONNECTION_LOCK (connection); + +#ifndef DBUS_DISABLE_CHECKS + if (connection->shareable) + { + CONNECTION_UNLOCK (connection); + + _dbus_warn_check_failed ("Applications must not close shared connections - see dbus_connection_close() docs. This is a bug in the application."); + return; + } +#endif + + _dbus_connection_close_possibly_shared_and_unlock (connection); +} + +static dbus_bool_t +_dbus_connection_get_is_connected_unlocked (DBusConnection *connection) +{ + HAVE_LOCK_CHECK (connection); + return _dbus_transport_get_is_connected (connection->transport); +} + +/** + * Gets whether the connection is currently open. A connection may + * become disconnected when the remote application closes its end, or + * exits; a connection may also be disconnected with + * dbus_connection_close(). + * + * There are not separate states for "closed" and "disconnected," the two + * terms are synonymous. This function should really be called + * get_is_open() but for historical reasons is not. + * + * @param connection the connection. + * @returns #TRUE if the connection is still alive. + */ +dbus_bool_t +dbus_connection_get_is_connected (DBusConnection *connection) +{ + dbus_bool_t res; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + + CONNECTION_LOCK (connection); + res = _dbus_connection_get_is_connected_unlocked (connection); + CONNECTION_UNLOCK (connection); + + return res; +} + +/** + * Gets whether the connection was authenticated. (Note that + * if the connection was authenticated then disconnected, + * this function still returns #TRUE) + * + * @param connection the connection + * @returns #TRUE if the connection was ever authenticated + */ +dbus_bool_t +dbus_connection_get_is_authenticated (DBusConnection *connection) +{ + dbus_bool_t res; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + + CONNECTION_LOCK (connection); + res = _dbus_transport_try_to_authenticate (connection->transport); + CONNECTION_UNLOCK (connection); + + return res; +} + +/** + * Gets whether the connection is not authenticated as a specific + * user. If the connection is not authenticated, this function + * returns #TRUE, and if it is authenticated but as an anonymous user, + * it returns #TRUE. If it is authenticated as a specific user, then + * this returns #FALSE. (Note that if the connection was authenticated + * as anonymous then disconnected, this function still returns #TRUE.) + * + * If the connection is not anonymous, you can use + * dbus_connection_get_unix_user() and + * dbus_connection_get_windows_user() to see who it's authorized as. + * + * If you want to prevent non-anonymous authorization, use + * dbus_server_set_auth_mechanisms() to remove the mechanisms that + * allow proving user identity (i.e. only allow the ANONYMOUS + * mechanism). + * + * @param connection the connection + * @returns #TRUE if not authenticated or authenticated as anonymous + */ +dbus_bool_t +dbus_connection_get_is_anonymous (DBusConnection *connection) +{ + dbus_bool_t res; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + + CONNECTION_LOCK (connection); + res = _dbus_transport_get_is_anonymous (connection->transport); + CONNECTION_UNLOCK (connection); + + return res; +} + +/** + * Gets the ID of the server address we are authenticated to, if this + * connection is on the client side. If the connection is on the + * server side, this will always return #NULL - use dbus_server_get_id() + * to get the ID of your own server, if you are the server side. + * + * If a client-side connection is not authenticated yet, the ID may be + * available if it was included in the server address, but may not be + * available. The only way to be sure the server ID is available + * is to wait for authentication to complete. + * + * In general, each mode of connecting to a given server will have + * its own ID. So for example, if the session bus daemon is listening + * on UNIX domain sockets and on TCP, then each of those modalities + * will have its own server ID. + * + * If you want an ID that identifies an entire session bus, look at + * dbus_bus_get_id() instead (which is just a convenience wrapper + * around the org.freedesktop.DBus.GetId method invoked on the bus). + * + * You can also get a machine ID; see dbus_try_get_local_machine_id() to + * get the machine you are on. There isn't a convenience wrapper, but + * you can invoke org.freedesktop.DBus.Peer.GetMachineId on any peer + * to get the machine ID on the other end. + * + * The D-Bus specification describes the server ID and other IDs in a + * bit more detail. + * + * @param connection the connection + * @returns the server ID or #NULL if no memory or the connection is server-side + */ +char* +dbus_connection_get_server_id (DBusConnection *connection) +{ + char *id; + + _dbus_return_val_if_fail (connection != NULL, NULL); + + CONNECTION_LOCK (connection); + id = _dbus_strdup (_dbus_transport_get_server_id (connection->transport)); + CONNECTION_UNLOCK (connection); + + return id; +} + +/** + * Tests whether a certain type can be send via the connection. This + * will always return TRUE for all types, with the exception of + * DBUS_TYPE_UNIX_FD. The function will return TRUE for + * DBUS_TYPE_UNIX_FD only on systems that know Unix file descriptors + * and can send them via the chosen transport and when the remote side + * supports this. + * + * This function can be used to do runtime checking for types that + * might be unknown to the specific D-Bus client implementation + * version, i.e. it will return FALSE for all types this + * implementation does not know, including invalid or reserved types. + * + * @param connection the connection + * @param type the type to check + * @returns TRUE if the type may be send via the connection + */ +dbus_bool_t +dbus_connection_can_send_type(DBusConnection *connection, + int type) +{ + _dbus_return_val_if_fail (connection != NULL, FALSE); + + if (!dbus_type_is_valid (type)) + return FALSE; + + if (type != DBUS_TYPE_UNIX_FD) + return TRUE; + +#ifdef HAVE_UNIX_FD_PASSING + { + dbus_bool_t b; + + CONNECTION_LOCK(connection); + b = _dbus_transport_can_pass_unix_fd(connection->transport); + CONNECTION_UNLOCK(connection); + + return b; + } +#endif + + return FALSE; +} + +/** + * Set whether _exit() should be called when the connection receives a + * disconnect signal. The call to _exit() comes after any handlers for + * the disconnect signal run; handlers can cancel the exit by calling + * this function. + * + * By default, exit_on_disconnect is #FALSE; but for message bus + * connections returned from dbus_bus_get() it will be toggled on + * by default. + * + * @param connection the connection + * @param exit_on_disconnect #TRUE if _exit() should be called after a disconnect signal + */ +void +dbus_connection_set_exit_on_disconnect (DBusConnection *connection, + dbus_bool_t exit_on_disconnect) +{ + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + connection->exit_on_disconnect = exit_on_disconnect != FALSE; + CONNECTION_UNLOCK (connection); +} + +/** + * Preallocates resources needed to send a message, allowing the message + * to be sent without the possibility of memory allocation failure. + * Allows apps to create a future guarantee that they can send + * a message regardless of memory shortages. + * + * @param connection the connection we're preallocating for. + * @returns the preallocated resources, or #NULL + */ +DBusPreallocatedSend* +dbus_connection_preallocate_send (DBusConnection *connection) +{ + DBusPreallocatedSend *preallocated; + + _dbus_return_val_if_fail (connection != NULL, NULL); + + CONNECTION_LOCK (connection); + + preallocated = + _dbus_connection_preallocate_send_unlocked (connection); + + CONNECTION_UNLOCK (connection); + + return preallocated; +} + +/** + * Frees preallocated message-sending resources from + * dbus_connection_preallocate_send(). Should only + * be called if the preallocated resources are not used + * to send a message. + * + * @param connection the connection + * @param preallocated the resources + */ +void +dbus_connection_free_preallocated_send (DBusConnection *connection, + DBusPreallocatedSend *preallocated) +{ + _dbus_return_if_fail (connection != NULL); + _dbus_return_if_fail (preallocated != NULL); + _dbus_return_if_fail (connection == preallocated->connection); + + _dbus_list_free_link (preallocated->queue_link); + _dbus_counter_unref (preallocated->counter_link->data); + _dbus_list_free_link (preallocated->counter_link); + dbus_free (preallocated); +} + +/** + * Sends a message using preallocated resources. This function cannot fail. + * It works identically to dbus_connection_send() in other respects. + * Preallocated resources comes from dbus_connection_preallocate_send(). + * This function "consumes" the preallocated resources, they need not + * be freed separately. + * + * @param connection the connection + * @param preallocated the preallocated resources + * @param message the message to send + * @param client_serial return location for client serial assigned to the message + */ +void +dbus_connection_send_preallocated (DBusConnection *connection, + DBusPreallocatedSend *preallocated, + DBusMessage *message, + dbus_uint32_t *client_serial) +{ + _dbus_return_if_fail (connection != NULL); + _dbus_return_if_fail (preallocated != NULL); + _dbus_return_if_fail (message != NULL); + _dbus_return_if_fail (preallocated->connection == connection); + _dbus_return_if_fail (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL || + dbus_message_get_member (message) != NULL); + _dbus_return_if_fail (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_SIGNAL || + (dbus_message_get_interface (message) != NULL && + dbus_message_get_member (message) != NULL)); + + CONNECTION_LOCK (connection); + +#ifdef HAVE_UNIX_FD_PASSING + + if (!_dbus_transport_can_pass_unix_fd(connection->transport) && + message->n_unix_fds > 0) + { + /* Refuse to send fds on a connection that cannot handle + them. Unfortunately we cannot return a proper error here, so + the best we can is just return. */ + CONNECTION_UNLOCK (connection); + return; + } + +#endif + + _dbus_connection_send_preallocated_and_unlock (connection, + preallocated, + message, client_serial); +} + +static dbus_bool_t +_dbus_connection_send_unlocked_no_update (DBusConnection *connection, + DBusMessage *message, + dbus_uint32_t *client_serial) +{ + DBusPreallocatedSend *preallocated; + + _dbus_assert (connection != NULL); + _dbus_assert (message != NULL); + + preallocated = _dbus_connection_preallocate_send_unlocked (connection); + if (preallocated == NULL) + return FALSE; + + _dbus_connection_send_preallocated_unlocked_no_update (connection, + preallocated, + message, + client_serial); + return TRUE; +} + +/** + * Adds a message to the outgoing message queue. Does not block to + * write the message to the network; that happens asynchronously. To + * force the message to be written, call dbus_connection_flush() however + * it is not necessary to call dbus_connection_flush() by hand; the + * message will be sent the next time the main loop is run. + * dbus_connection_flush() should only be used, for example, if + * the application was expected to exit before running the main loop. + * + * Because this only queues the message, the only reason it can + * fail is lack of memory. Even if the connection is disconnected, + * no error will be returned. If the function fails due to lack of memory, + * it returns #FALSE. The function will never fail for other reasons; even + * if the connection is disconnected, you can queue an outgoing message, + * though obviously it won't be sent. + * + * The message serial is used by the remote application to send a + * reply; see dbus_message_get_serial() or the D-Bus specification. + * + * dbus_message_unref() can be called as soon as this method returns + * as the message queue will hold its own ref until the message is sent. + * + * @param connection the connection. + * @param message the message to write. + * @param serial return location for message serial, or #NULL if you don't care + * @returns #TRUE on success. + */ +dbus_bool_t +dbus_connection_send (DBusConnection *connection, + DBusMessage *message, + dbus_uint32_t *serial) +{ + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (message != NULL, FALSE); + + CONNECTION_LOCK (connection); + +#ifdef HAVE_UNIX_FD_PASSING + + if (!_dbus_transport_can_pass_unix_fd(connection->transport) && + message->n_unix_fds > 0) + { + /* Refuse to send fds on a connection that cannot handle + them. Unfortunately we cannot return a proper error here, so + the best we can is just return. */ + CONNECTION_UNLOCK (connection); + return FALSE; + } + +#endif + + return _dbus_connection_send_and_unlock (connection, + message, + serial); +} + +static dbus_bool_t +reply_handler_timeout (void *data) +{ + DBusConnection *connection; + DBusDispatchStatus status; + DBusPendingCall *pending = data; + + connection = _dbus_pending_call_get_connection_and_lock (pending); + _dbus_connection_ref_unlocked (connection); + + _dbus_pending_call_queue_timeout_error_unlocked (pending, + connection); + _dbus_connection_remove_timeout_unlocked (connection, + _dbus_pending_call_get_timeout_unlocked (pending)); + _dbus_pending_call_set_timeout_added_unlocked (pending, FALSE); + + _dbus_verbose ("middle\n"); + status = _dbus_connection_get_dispatch_status_unlocked (connection); + + /* Unlocks, and calls out to user code */ + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + dbus_connection_unref (connection); + + return TRUE; +} + +/** + * Queues a message to send, as with dbus_connection_send(), + * but also returns a #DBusPendingCall used to receive a reply to the + * message. If no reply is received in the given timeout_milliseconds, + * this function expires the pending reply and generates a synthetic + * error reply (generated in-process, not by the remote application) + * indicating that a timeout occurred. + * + * A #DBusPendingCall will see a reply message before any filters or + * registered object path handlers. See dbus_connection_dispatch() for + * details on when handlers are run. + * + * A #DBusPendingCall will always see exactly one reply message, + * unless it's cancelled with dbus_pending_call_cancel(). + * + * If #NULL is passed for the pending_return, the #DBusPendingCall + * will still be generated internally, and used to track + * the message reply timeout. This means a timeout error will + * occur if no reply arrives, unlike with dbus_connection_send(). + * + * If -1 is passed for the timeout, a sane default timeout is used. -1 + * is typically the best value for the timeout for this reason, unless + * you want a very short or very long timeout. If #DBUS_TIMEOUT_INFINITE is + * passed for the timeout, no timeout will be set and the call will block + * forever. + * + * @warning if the connection is disconnected or you try to send Unix + * file descriptors on a connection that does not support them, the + * #DBusPendingCall will be set to #NULL, so be careful with this. + * + * @param connection the connection + * @param message the message to send + * @param pending_return return location for a #DBusPendingCall + * object, or #NULL if connection is disconnected or when you try to + * send Unix file descriptors on a connection that does not support + * them. + * @param timeout_milliseconds timeout in milliseconds, -1 (or + * #DBUS_TIMEOUT_USE_DEFAULT) for default or #DBUS_TIMEOUT_INFINITE for no + * timeout + * @returns #FALSE if no memory, #TRUE otherwise. + * + */ +dbus_bool_t +dbus_connection_send_with_reply (DBusConnection *connection, + DBusMessage *message, + DBusPendingCall **pending_return, + int timeout_milliseconds) +{ + DBusPendingCall *pending; + dbus_int32_t serial = -1; + DBusDispatchStatus status; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (timeout_milliseconds >= 0 || timeout_milliseconds == -1, FALSE); + + if (pending_return) + *pending_return = NULL; + + CONNECTION_LOCK (connection); + +#ifdef HAVE_UNIX_FD_PASSING + + if (!_dbus_transport_can_pass_unix_fd(connection->transport) && + message->n_unix_fds > 0) + { + /* Refuse to send fds on a connection that cannot handle + them. Unfortunately we cannot return a proper error here, so + the best we can do is return TRUE but leave *pending_return + as NULL. */ + CONNECTION_UNLOCK (connection); + return TRUE; + } + +#endif + + if (!_dbus_connection_get_is_connected_unlocked (connection)) + { + CONNECTION_UNLOCK (connection); + + return TRUE; + } + + pending = _dbus_pending_call_new_unlocked (connection, + timeout_milliseconds, + reply_handler_timeout); + + if (pending == NULL) + { + CONNECTION_UNLOCK (connection); + return FALSE; + } + + /* Assign a serial to the message */ + serial = dbus_message_get_serial (message); + if (serial == 0) + { + serial = _dbus_connection_get_next_client_serial (connection); + dbus_message_set_serial (message, serial); + } + + if (!_dbus_pending_call_set_timeout_error_unlocked (pending, message, serial)) + goto error; + + /* Insert the serial in the pending replies hash; + * hash takes a refcount on DBusPendingCall. + * Also, add the timeout. + */ + if (!_dbus_connection_attach_pending_call_unlocked (connection, + pending)) + goto error; + + if (!_dbus_connection_send_unlocked_no_update (connection, message, NULL)) + { + _dbus_connection_detach_pending_call_and_unlock (connection, + pending); + goto error_unlocked; + } + + if (pending_return) + *pending_return = pending; /* hand off refcount */ + else + { + _dbus_connection_detach_pending_call_unlocked (connection, pending); + /* we still have a ref to the pending call in this case, we unref + * after unlocking, below + */ + } + + status = _dbus_connection_get_dispatch_status_unlocked (connection); + + /* this calls out to user code */ + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + + if (pending_return == NULL) + dbus_pending_call_unref (pending); + + return TRUE; + + error: + CONNECTION_UNLOCK (connection); + error_unlocked: + dbus_pending_call_unref (pending); + return FALSE; +} + +/** + * Sends a message and blocks a certain time period while waiting for + * a reply. This function does not reenter the main loop, + * i.e. messages other than the reply are queued up but not + * processed. This function is used to invoke method calls on a + * remote object. + * + * If a normal reply is received, it is returned, and removed from the + * incoming message queue. If it is not received, #NULL is returned + * and the error is set to #DBUS_ERROR_NO_REPLY. If an error reply is + * received, it is converted to a #DBusError and returned as an error, + * then the reply message is deleted and #NULL is returned. If + * something else goes wrong, result is set to whatever is + * appropriate, such as #DBUS_ERROR_NO_MEMORY or + * #DBUS_ERROR_DISCONNECTED. + * + * @warning While this function blocks the calling thread will not be + * processing the incoming message queue. This means you can end up + * deadlocked if the application you're talking to needs you to reply + * to a method. To solve this, either avoid the situation, block in a + * separate thread from the main connection-dispatching thread, or use + * dbus_pending_call_set_notify() to avoid blocking. + * + * @param connection the connection + * @param message the message to send + * @param timeout_milliseconds timeout in milliseconds, -1 (or + * #DBUS_TIMEOUT_USE_DEFAULT) for default or #DBUS_TIMEOUT_INFINITE for no + * timeout + * @param error return location for error message + * @returns the message that is the reply or #NULL with an error code if the + * function fails. + */ +DBusMessage* +dbus_connection_send_with_reply_and_block (DBusConnection *connection, + DBusMessage *message, + int timeout_milliseconds, + DBusError *error) +{ + DBusMessage *reply; + DBusPendingCall *pending; + + _dbus_return_val_if_fail (connection != NULL, NULL); + _dbus_return_val_if_fail (message != NULL, NULL); + _dbus_return_val_if_fail (timeout_milliseconds >= 0 || timeout_milliseconds == -1, NULL); + _dbus_return_val_if_error_is_set (error, NULL); + +#ifdef HAVE_UNIX_FD_PASSING + + CONNECTION_LOCK (connection); + if (!_dbus_transport_can_pass_unix_fd(connection->transport) && + message->n_unix_fds > 0) + { + CONNECTION_UNLOCK (connection); + dbus_set_error(error, DBUS_ERROR_FAILED, "Cannot send file descriptors on this connection."); + return NULL; + } + CONNECTION_UNLOCK (connection); + +#endif + + if (!dbus_connection_send_with_reply (connection, message, + &pending, timeout_milliseconds)) + { + _DBUS_SET_OOM (error); + return NULL; + } + + if (pending == NULL) + { + dbus_set_error (error, DBUS_ERROR_DISCONNECTED, "Connection is closed"); + return NULL; + } + + dbus_pending_call_block (pending); + + reply = dbus_pending_call_steal_reply (pending); + dbus_pending_call_unref (pending); + + /* call_complete_and_unlock() called from pending_call_block() should + * always fill this in. + */ + _dbus_assert (reply != NULL); + + if (dbus_set_error_from_message (error, reply)) + { + dbus_message_unref (reply); + return NULL; + } + else + return reply; +} + +/** + * Blocks until the outgoing message queue is empty. + * Assumes connection lock already held. + * + * If you call this, you MUST call update_dispatch_status afterword... + * + * @param connection the connection. + */ +static DBusDispatchStatus +_dbus_connection_flush_unlocked (DBusConnection *connection) +{ + /* We have to specify DBUS_ITERATION_DO_READING here because + * otherwise we could have two apps deadlock if they are both doing + * a flush(), and the kernel buffers fill up. This could change the + * dispatch status. + */ + DBusDispatchStatus status; + + HAVE_LOCK_CHECK (connection); + + while (connection->n_outgoing > 0 && + _dbus_connection_get_is_connected_unlocked (connection)) + { + _dbus_verbose ("doing iteration in\n"); + HAVE_LOCK_CHECK (connection); + _dbus_connection_do_iteration_unlocked (connection, + NULL, + DBUS_ITERATION_DO_READING | + DBUS_ITERATION_DO_WRITING | + DBUS_ITERATION_BLOCK, + -1); + } + + HAVE_LOCK_CHECK (connection); + _dbus_verbose ("middle\n"); + status = _dbus_connection_get_dispatch_status_unlocked (connection); + + HAVE_LOCK_CHECK (connection); + return status; +} + +/** + * Blocks until the outgoing message queue is empty. + * + * @param connection the connection. + */ +void +dbus_connection_flush (DBusConnection *connection) +{ + /* We have to specify DBUS_ITERATION_DO_READING here because + * otherwise we could have two apps deadlock if they are both doing + * a flush(), and the kernel buffers fill up. This could change the + * dispatch status. + */ + DBusDispatchStatus status; + + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + + status = _dbus_connection_flush_unlocked (connection); + + HAVE_LOCK_CHECK (connection); + /* Unlocks and calls out to user code */ + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + + _dbus_verbose ("end\n"); +} + +/** + * This function implements dbus_connection_read_write_dispatch() and + * dbus_connection_read_write() (they pass a different value for the + * dispatch parameter). + * + * @param connection the connection + * @param timeout_milliseconds max time to block or -1 for infinite + * @param dispatch dispatch new messages or leave them on the incoming queue + * @returns #TRUE if the disconnect message has not been processed + */ +static dbus_bool_t +_dbus_connection_read_write_dispatch (DBusConnection *connection, + int timeout_milliseconds, + dbus_bool_t dispatch) +{ + DBusDispatchStatus dstatus; + dbus_bool_t progress_possible; + + /* Need to grab a ref here in case we're a private connection and + * the user drops the last ref in a handler we call; see bug + * https://bugs.freedesktop.org/show_bug.cgi?id=15635 + */ + dbus_connection_ref (connection); + dstatus = dbus_connection_get_dispatch_status (connection); + + if (dispatch && dstatus == DBUS_DISPATCH_DATA_REMAINS) + { + _dbus_verbose ("doing dispatch\n"); + dbus_connection_dispatch (connection); + CONNECTION_LOCK (connection); + } + else if (dstatus == DBUS_DISPATCH_NEED_MEMORY) + { + _dbus_verbose ("pausing for memory\n"); + _dbus_memory_pause_based_on_timeout (timeout_milliseconds); + CONNECTION_LOCK (connection); + } + else + { + CONNECTION_LOCK (connection); + if (_dbus_connection_get_is_connected_unlocked (connection)) + { + _dbus_verbose ("doing iteration\n"); + _dbus_connection_do_iteration_unlocked (connection, + NULL, + DBUS_ITERATION_DO_READING | + DBUS_ITERATION_DO_WRITING | + DBUS_ITERATION_BLOCK, + timeout_milliseconds); + } + } + + HAVE_LOCK_CHECK (connection); + /* If we can dispatch, we can make progress until the Disconnected message + * has been processed; if we can only read/write, we can make progress + * as long as the transport is open. + */ + if (dispatch) + progress_possible = connection->n_incoming != 0 || + connection->disconnect_message_link != NULL; + else + progress_possible = _dbus_connection_get_is_connected_unlocked (connection); + + CONNECTION_UNLOCK (connection); + + dbus_connection_unref (connection); + + return progress_possible; /* TRUE if we can make more progress */ +} + + +/** + * This function is intended for use with applications that don't want + * to write a main loop and deal with #DBusWatch and #DBusTimeout. An + * example usage would be: + * + * @code + * while (dbus_connection_read_write_dispatch (connection, -1)) + * ; // empty loop body + * @endcode + * + * In this usage you would normally have set up a filter function to look + * at each message as it is dispatched. The loop terminates when the last + * message from the connection (the disconnected signal) is processed. + * + * If there are messages to dispatch, this function will + * dbus_connection_dispatch() once, and return. If there are no + * messages to dispatch, this function will block until it can read or + * write, then read or write, then return. + * + * The way to think of this function is that it either makes some sort + * of progress, or it blocks. Note that, while it is blocked on I/O, it + * cannot be interrupted (even by other threads), which makes this function + * unsuitable for applications that do more than just react to received + * messages. + * + * The return value indicates whether the disconnect message has been + * processed, NOT whether the connection is connected. This is + * important because even after disconnecting, you want to process any + * messages you received prior to the disconnect. + * + * @param connection the connection + * @param timeout_milliseconds max time to block or -1 for infinite + * @returns #TRUE if the disconnect message has not been processed + */ +dbus_bool_t +dbus_connection_read_write_dispatch (DBusConnection *connection, + int timeout_milliseconds) +{ + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (timeout_milliseconds >= 0 || timeout_milliseconds == -1, FALSE); + return _dbus_connection_read_write_dispatch(connection, timeout_milliseconds, TRUE); +} + +/** + * This function is intended for use with applications that don't want to + * write a main loop and deal with #DBusWatch and #DBusTimeout. See also + * dbus_connection_read_write_dispatch(). + * + * As long as the connection is open, this function will block until it can + * read or write, then read or write, then return #TRUE. + * + * If the connection is closed, the function returns #FALSE. + * + * The return value indicates whether reading or writing is still + * possible, i.e. whether the connection is connected. + * + * Note that even after disconnection, messages may remain in the + * incoming queue that need to be + * processed. dbus_connection_read_write_dispatch() dispatches + * incoming messages for you; with dbus_connection_read_write() you + * have to arrange to drain the incoming queue yourself. + * + * @param connection the connection + * @param timeout_milliseconds max time to block or -1 for infinite + * @returns #TRUE if still connected + */ +dbus_bool_t +dbus_connection_read_write (DBusConnection *connection, + int timeout_milliseconds) +{ + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (timeout_milliseconds >= 0 || timeout_milliseconds == -1, FALSE); + return _dbus_connection_read_write_dispatch(connection, timeout_milliseconds, FALSE); +} + +/* We need to call this anytime we pop the head of the queue, and then + * update_dispatch_status_and_unlock needs to be called afterward + * which will "process" the disconnected message and set + * disconnected_message_processed. + */ +static void +check_disconnected_message_arrived_unlocked (DBusConnection *connection, + DBusMessage *head_of_queue) +{ + HAVE_LOCK_CHECK (connection); + + /* checking that the link is NULL is an optimization to avoid the is_signal call */ + if (connection->disconnect_message_link == NULL && + dbus_message_is_signal (head_of_queue, + DBUS_INTERFACE_LOCAL, + "Disconnected")) + { + connection->disconnected_message_arrived = TRUE; + } +} + +/** + * Returns the first-received message from the incoming message queue, + * leaving it in the queue. If the queue is empty, returns #NULL. + * + * The caller does not own a reference to the returned message, and + * must either return it using dbus_connection_return_message() or + * keep it after calling dbus_connection_steal_borrowed_message(). No + * one can get at the message while its borrowed, so return it as + * quickly as possible and don't keep a reference to it after + * returning it. If you need to keep the message, make a copy of it. + * + * dbus_connection_dispatch() will block if called while a borrowed + * message is outstanding; only one piece of code can be playing with + * the incoming queue at a time. This function will block if called + * during a dbus_connection_dispatch(). + * + * @param connection the connection. + * @returns next message in the incoming queue. + */ +DBusMessage* +dbus_connection_borrow_message (DBusConnection *connection) +{ + DBusDispatchStatus status; + DBusMessage *message; + + _dbus_return_val_if_fail (connection != NULL, NULL); + + _dbus_verbose ("start\n"); + + /* this is called for the side effect that it queues + * up any messages from the transport + */ + status = dbus_connection_get_dispatch_status (connection); + if (status != DBUS_DISPATCH_DATA_REMAINS) + return NULL; + + CONNECTION_LOCK (connection); + + _dbus_connection_acquire_dispatch (connection); + + /* While a message is outstanding, the dispatch lock is held */ + _dbus_assert (connection->message_borrowed == NULL); + + connection->message_borrowed = _dbus_list_get_first (&connection->incoming_messages); + + message = connection->message_borrowed; + + check_disconnected_message_arrived_unlocked (connection, message); + + /* Note that we KEEP the dispatch lock until the message is returned */ + if (message == NULL) + _dbus_connection_release_dispatch (connection); + + CONNECTION_UNLOCK (connection); + + _dbus_message_trace_ref (message, -1, -1, "dbus_connection_borrow_message"); + + /* We don't update dispatch status until it's returned or stolen */ + + return message; +} + +/** + * Used to return a message after peeking at it using + * dbus_connection_borrow_message(). Only called if + * message from dbus_connection_borrow_message() was non-#NULL. + * + * @param connection the connection + * @param message the message from dbus_connection_borrow_message() + */ +void +dbus_connection_return_message (DBusConnection *connection, + DBusMessage *message) +{ + DBusDispatchStatus status; + + _dbus_return_if_fail (connection != NULL); + _dbus_return_if_fail (message != NULL); + _dbus_return_if_fail (message == connection->message_borrowed); + _dbus_return_if_fail (connection->dispatch_acquired); + + CONNECTION_LOCK (connection); + + _dbus_assert (message == connection->message_borrowed); + + connection->message_borrowed = NULL; + + _dbus_connection_release_dispatch (connection); + + status = _dbus_connection_get_dispatch_status_unlocked (connection); + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + + _dbus_message_trace_ref (message, -1, -1, "dbus_connection_return_message"); +} + +/** + * Used to keep a message after peeking at it using + * dbus_connection_borrow_message(). Before using this function, see + * the caveats/warnings in the documentation for + * dbus_connection_pop_message(). + * + * @param connection the connection + * @param message the message from dbus_connection_borrow_message() + */ +void +dbus_connection_steal_borrowed_message (DBusConnection *connection, + DBusMessage *message) +{ + DBusMessage *pop_message; + DBusDispatchStatus status; + + _dbus_return_if_fail (connection != NULL); + _dbus_return_if_fail (message != NULL); + _dbus_return_if_fail (message == connection->message_borrowed); + _dbus_return_if_fail (connection->dispatch_acquired); + + CONNECTION_LOCK (connection); + + _dbus_assert (message == connection->message_borrowed); + + pop_message = _dbus_list_pop_first (&connection->incoming_messages); + _dbus_assert (message == pop_message); + (void) pop_message; /* unused unless asserting */ + + connection->n_incoming -= 1; + + _dbus_verbose ("Incoming message %p stolen from queue, %d incoming\n", + message, connection->n_incoming); + + connection->message_borrowed = NULL; + + _dbus_connection_release_dispatch (connection); + + status = _dbus_connection_get_dispatch_status_unlocked (connection); + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + _dbus_message_trace_ref (message, -1, -1, + "dbus_connection_steal_borrowed_message"); +} + +/* See dbus_connection_pop_message, but requires the caller to own + * the lock before calling. May drop the lock while running. + */ +static DBusList* +_dbus_connection_pop_message_link_unlocked (DBusConnection *connection) +{ + HAVE_LOCK_CHECK (connection); + + _dbus_assert (connection->message_borrowed == NULL); + + if (connection->n_incoming > 0) + { + DBusList *link; + + link = _dbus_list_pop_first_link (&connection->incoming_messages); + connection->n_incoming -= 1; + + _dbus_verbose ("Message %p (%s %s %s %s sig:'%s' serial:%u) removed from incoming queue %p, %d incoming\n", + link->data, + dbus_message_type_to_string (dbus_message_get_type (link->data)), + dbus_message_get_path (link->data) ? + dbus_message_get_path (link->data) : + "no path", + dbus_message_get_interface (link->data) ? + dbus_message_get_interface (link->data) : + "no interface", + dbus_message_get_member (link->data) ? + dbus_message_get_member (link->data) : + "no member", + dbus_message_get_signature (link->data), + dbus_message_get_serial (link->data), + connection, connection->n_incoming); + + _dbus_message_trace_ref (link->data, -1, -1, + "_dbus_connection_pop_message_link_unlocked"); + + check_disconnected_message_arrived_unlocked (connection, link->data); + + return link; + } + else + return NULL; +} + +/* See dbus_connection_pop_message, but requires the caller to own + * the lock before calling. May drop the lock while running. + */ +static DBusMessage* +_dbus_connection_pop_message_unlocked (DBusConnection *connection) +{ + DBusList *link; + + HAVE_LOCK_CHECK (connection); + + link = _dbus_connection_pop_message_link_unlocked (connection); + + if (link != NULL) + { + DBusMessage *message; + + message = link->data; + + _dbus_list_free_link (link); + + return message; + } + else + return NULL; +} + +static void +_dbus_connection_putback_message_link_unlocked (DBusConnection *connection, + DBusList *message_link) +{ + HAVE_LOCK_CHECK (connection); + + _dbus_assert (message_link != NULL); + /* You can't borrow a message while a link is outstanding */ + _dbus_assert (connection->message_borrowed == NULL); + /* We had to have the dispatch lock across the pop/putback */ + _dbus_assert (connection->dispatch_acquired); + + _dbus_list_prepend_link (&connection->incoming_messages, + message_link); + connection->n_incoming += 1; + + _dbus_verbose ("Message %p (%s %s %s '%s') put back into queue %p, %d incoming\n", + message_link->data, + dbus_message_type_to_string (dbus_message_get_type (message_link->data)), + dbus_message_get_interface (message_link->data) ? + dbus_message_get_interface (message_link->data) : + "no interface", + dbus_message_get_member (message_link->data) ? + dbus_message_get_member (message_link->data) : + "no member", + dbus_message_get_signature (message_link->data), + connection, connection->n_incoming); + + _dbus_message_trace_ref (message_link->data, -1, -1, + "_dbus_connection_putback_message_link_unlocked"); +} + +/** + * Returns the first-received message from the incoming message queue, + * removing it from the queue. The caller owns a reference to the + * returned message. If the queue is empty, returns #NULL. + * + * This function bypasses any message handlers that are registered, + * and so using it is usually wrong. Instead, let the main loop invoke + * dbus_connection_dispatch(). Popping messages manually is only + * useful in very simple programs that don't share a #DBusConnection + * with any libraries or other modules. + * + * There is a lock that covers all ways of accessing the incoming message + * queue, so dbus_connection_dispatch(), dbus_connection_pop_message(), + * dbus_connection_borrow_message(), etc. will all block while one of the others + * in the group is running. + * + * @param connection the connection. + * @returns next message in the incoming queue. + */ +DBusMessage* +dbus_connection_pop_message (DBusConnection *connection) +{ + DBusMessage *message; + DBusDispatchStatus status; + + _dbus_verbose ("start\n"); + + /* this is called for the side effect that it queues + * up any messages from the transport + */ + status = dbus_connection_get_dispatch_status (connection); + if (status != DBUS_DISPATCH_DATA_REMAINS) + return NULL; + + CONNECTION_LOCK (connection); + _dbus_connection_acquire_dispatch (connection); + HAVE_LOCK_CHECK (connection); + + message = _dbus_connection_pop_message_unlocked (connection); + + _dbus_verbose ("Returning popped message %p\n", message); + + _dbus_connection_release_dispatch (connection); + + status = _dbus_connection_get_dispatch_status_unlocked (connection); + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + + return message; +} + +/** + * Acquire the dispatcher. This is a separate lock so the main + * connection lock can be dropped to call out to application dispatch + * handlers. + * + * @param connection the connection. + */ +static void +_dbus_connection_acquire_dispatch (DBusConnection *connection) +{ + HAVE_LOCK_CHECK (connection); + + _dbus_connection_ref_unlocked (connection); + CONNECTION_UNLOCK (connection); + + _dbus_verbose ("locking dispatch_mutex\n"); + _dbus_cmutex_lock (connection->dispatch_mutex); + + while (connection->dispatch_acquired) + { + _dbus_verbose ("waiting for dispatch to be acquirable\n"); + _dbus_condvar_wait (connection->dispatch_cond, + connection->dispatch_mutex); + } + + _dbus_assert (!connection->dispatch_acquired); + + connection->dispatch_acquired = TRUE; + + _dbus_verbose ("unlocking dispatch_mutex\n"); + _dbus_cmutex_unlock (connection->dispatch_mutex); + + CONNECTION_LOCK (connection); + _dbus_connection_unref_unlocked (connection); +} + +/** + * Release the dispatcher when you're done with it. Only call + * after you've acquired the dispatcher. Wakes up at most one + * thread currently waiting to acquire the dispatcher. + * + * @param connection the connection. + */ +static void +_dbus_connection_release_dispatch (DBusConnection *connection) +{ + HAVE_LOCK_CHECK (connection); + + _dbus_verbose ("locking dispatch_mutex\n"); + _dbus_cmutex_lock (connection->dispatch_mutex); + + _dbus_assert (connection->dispatch_acquired); + + connection->dispatch_acquired = FALSE; + _dbus_condvar_wake_one (connection->dispatch_cond); + + _dbus_verbose ("unlocking dispatch_mutex\n"); + _dbus_cmutex_unlock (connection->dispatch_mutex); +} + +static void +_dbus_connection_failed_pop (DBusConnection *connection, + DBusList *message_link) +{ + _dbus_list_prepend_link (&connection->incoming_messages, + message_link); + connection->n_incoming += 1; +} + +/* Note this may be called multiple times since we don't track whether we already did it */ +static void +notify_disconnected_unlocked (DBusConnection *connection) +{ + HAVE_LOCK_CHECK (connection); + + /* Set the weakref in dbus-bus.c to NULL, so nobody will get a disconnected + * connection from dbus_bus_get(). We make the same guarantee for + * dbus_connection_open() but in a different way since we don't want to + * unref right here; we instead check for connectedness before returning + * the connection from the hash. + */ + _dbus_bus_notify_shared_connection_disconnected_unlocked (connection); + + /* Dump the outgoing queue, we aren't going to be able to + * send it now, and we'd like accessors like + * dbus_connection_get_outgoing_size() to be accurate. + */ + if (connection->n_outgoing > 0) + { + DBusList *link; + + _dbus_verbose ("Dropping %d outgoing messages since we're disconnected\n", + connection->n_outgoing); + + while ((link = _dbus_list_get_last_link (&connection->outgoing_messages))) + { + _dbus_connection_message_sent_unlocked (connection, link->data); + } + } +} + +/* Note this may be called multiple times since we don't track whether we already did it */ +static DBusDispatchStatus +notify_disconnected_and_dispatch_complete_unlocked (DBusConnection *connection) +{ + HAVE_LOCK_CHECK (connection); + + if (connection->disconnect_message_link != NULL) + { + _dbus_verbose ("Sending disconnect message\n"); + + /* If we have pending calls, queue their timeouts - we want the Disconnected + * to be the last message, after these timeouts. + */ + connection_timeout_and_complete_all_pending_calls_unlocked (connection); + + /* We haven't sent the disconnect message already, + * and all real messages have been queued up. + */ + _dbus_connection_queue_synthesized_message_link (connection, + connection->disconnect_message_link); + connection->disconnect_message_link = NULL; + + return DBUS_DISPATCH_DATA_REMAINS; + } + + return DBUS_DISPATCH_COMPLETE; +} + +static DBusDispatchStatus +_dbus_connection_get_dispatch_status_unlocked (DBusConnection *connection) +{ + HAVE_LOCK_CHECK (connection); + + if (connection->n_incoming > 0) + return DBUS_DISPATCH_DATA_REMAINS; + else if (!_dbus_transport_queue_messages (connection->transport)) + return DBUS_DISPATCH_NEED_MEMORY; + else + { + DBusDispatchStatus status; + dbus_bool_t is_connected; + + status = _dbus_transport_get_dispatch_status (connection->transport); + is_connected = _dbus_transport_get_is_connected (connection->transport); + + _dbus_verbose ("dispatch status = %s is_connected = %d\n", + DISPATCH_STATUS_NAME (status), is_connected); + + if (!is_connected) + { + /* It's possible this would be better done by having an explicit + * notification from _dbus_transport_disconnect() that would + * synchronously do this, instead of waiting for the next dispatch + * status check. However, probably not good to change until it causes + * a problem. + */ + notify_disconnected_unlocked (connection); + + /* I'm not sure this is needed; the idea is that we want to + * queue the Disconnected only after we've read all the + * messages, but if we're disconnected maybe we are guaranteed + * to have read them all ? + */ + if (status == DBUS_DISPATCH_COMPLETE) + status = notify_disconnected_and_dispatch_complete_unlocked (connection); + } + + if (status != DBUS_DISPATCH_COMPLETE) + return status; + else if (connection->n_incoming > 0) + return DBUS_DISPATCH_DATA_REMAINS; + else + return DBUS_DISPATCH_COMPLETE; + } +} + +static void +_dbus_connection_update_dispatch_status_and_unlock (DBusConnection *connection, + DBusDispatchStatus new_status) +{ + dbus_bool_t changed; + DBusDispatchStatusFunction function; + void *data; + + HAVE_LOCK_CHECK (connection); + + _dbus_connection_ref_unlocked (connection); + + changed = new_status != connection->last_dispatch_status; + + connection->last_dispatch_status = new_status; + + function = connection->dispatch_status_function; + data = connection->dispatch_status_data; + + if (connection->disconnected_message_arrived && + !connection->disconnected_message_processed) + { + connection->disconnected_message_processed = TRUE; + + /* this does an unref, but we have a ref + * so we should not run the finalizer here + * inside the lock. + */ + connection_forget_shared_unlocked (connection); + + if (connection->exit_on_disconnect) + { + CONNECTION_UNLOCK (connection); + + _dbus_verbose ("Exiting on Disconnected signal\n"); + _dbus_exit (1); + _dbus_assert_not_reached ("Call to exit() returned"); + } + } + + /* We drop the lock */ + CONNECTION_UNLOCK (connection); + + if (changed && function) + { + _dbus_verbose ("Notifying of change to dispatch status of %p now %d (%s)\n", + connection, new_status, + DISPATCH_STATUS_NAME (new_status)); + (* function) (connection, new_status, data); + } + + dbus_connection_unref (connection); +} + +/** + * Gets the current state of the incoming message queue. + * #DBUS_DISPATCH_DATA_REMAINS indicates that the message queue + * may contain messages. #DBUS_DISPATCH_COMPLETE indicates that the + * incoming queue is empty. #DBUS_DISPATCH_NEED_MEMORY indicates that + * there could be data, but we can't know for sure without more + * memory. + * + * To process the incoming message queue, use dbus_connection_dispatch() + * or (in rare cases) dbus_connection_pop_message(). + * + * Note, #DBUS_DISPATCH_DATA_REMAINS really means that either we + * have messages in the queue, or we have raw bytes buffered up + * that need to be parsed. When these bytes are parsed, they + * may not add up to an entire message. Thus, it's possible + * to see a status of #DBUS_DISPATCH_DATA_REMAINS but not + * have a message yet. + * + * In particular this happens on initial connection, because all sorts + * of authentication protocol stuff has to be parsed before the + * first message arrives. + * + * @param connection the connection. + * @returns current dispatch status + */ +DBusDispatchStatus +dbus_connection_get_dispatch_status (DBusConnection *connection) +{ + DBusDispatchStatus status; + + _dbus_return_val_if_fail (connection != NULL, DBUS_DISPATCH_COMPLETE); + + _dbus_verbose ("start\n"); + + CONNECTION_LOCK (connection); + + status = _dbus_connection_get_dispatch_status_unlocked (connection); + + CONNECTION_UNLOCK (connection); + + return status; +} + +/** + * Filter funtion for handling the Peer standard interface. + */ +static DBusHandlerResult +_dbus_connection_peer_filter_unlocked_no_update (DBusConnection *connection, + DBusMessage *message) +{ + dbus_bool_t sent = FALSE; + DBusMessage *ret = NULL; + DBusList *expire_link; + + if (connection->route_peer_messages && dbus_message_get_destination (message) != NULL) + { + /* This means we're letting the bus route this message */ + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (!dbus_message_has_interface (message, DBUS_INTERFACE_PEER)) + { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + /* Preallocate a linked-list link, so that if we need to dispose of a + * message, we can attach it to the expired list */ + expire_link = _dbus_list_alloc_link (NULL); + + if (!expire_link) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (dbus_message_is_method_call (message, + DBUS_INTERFACE_PEER, + "Ping")) + { + ret = dbus_message_new_method_return (message); + if (ret == NULL) + goto out; + + sent = _dbus_connection_send_unlocked_no_update (connection, ret, NULL); + } + else if (dbus_message_is_method_call (message, + DBUS_INTERFACE_PEER, + "GetMachineId")) + { + DBusString uuid; + DBusError error = DBUS_ERROR_INIT; + + if (!_dbus_string_init (&uuid)) + goto out; + + if (_dbus_get_local_machine_uuid_encoded (&uuid, &error)) + { + const char *v_STRING; + + ret = dbus_message_new_method_return (message); + + if (ret == NULL) + { + _dbus_string_free (&uuid); + goto out; + } + + v_STRING = _dbus_string_get_const_data (&uuid); + if (dbus_message_append_args (ret, + DBUS_TYPE_STRING, &v_STRING, + DBUS_TYPE_INVALID)) + { + sent = _dbus_connection_send_unlocked_no_update (connection, ret, NULL); + } + } + else if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + goto out; + } + else + { + ret = dbus_message_new_error (message, error.name, error.message); + dbus_error_free (&error); + + if (ret == NULL) + goto out; + + sent = _dbus_connection_send_unlocked_no_update (connection, ret, + NULL); + } + + _dbus_string_free (&uuid); + } + else + { + /* We need to bounce anything else with this interface, otherwise apps + * could start extending the interface and when we added extensions + * here to DBusConnection we'd break those apps. + */ + ret = dbus_message_new_error (message, + DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method invoked on org.freedesktop.DBus.Peer interface"); + if (ret == NULL) + goto out; + + sent = _dbus_connection_send_unlocked_no_update (connection, ret, NULL); + } + +out: + if (ret == NULL) + { + _dbus_list_free_link (expire_link); + } + else + { + /* It'll be safe to unref the reply when we unlock */ + expire_link->data = ret; + _dbus_list_prepend_link (&connection->expired_messages, expire_link); + } + + if (!sent) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + return DBUS_HANDLER_RESULT_HANDLED; +} + +/** +* Processes all builtin filter functions +* +* If the spec specifies a standard interface +* they should be processed from this method +**/ +static DBusHandlerResult +_dbus_connection_run_builtin_filters_unlocked_no_update (DBusConnection *connection, + DBusMessage *message) +{ + /* We just run one filter for now but have the option to run more + if the spec calls for it in the future */ + + return _dbus_connection_peer_filter_unlocked_no_update (connection, message); +} + +/** + * Processes any incoming data. + * + * If there's incoming raw data that has not yet been parsed, it is + * parsed, which may or may not result in adding messages to the + * incoming queue. + * + * The incoming data buffer is filled when the connection reads from + * its underlying transport (such as a socket). Reading usually + * happens in dbus_watch_handle() or dbus_connection_read_write(). + * + * If there are complete messages in the incoming queue, + * dbus_connection_dispatch() removes one message from the queue and + * processes it. Processing has three steps. + * + * First, any method replies are passed to #DBusPendingCall or + * dbus_connection_send_with_reply_and_block() in order to + * complete the pending method call. + * + * Second, any filters registered with dbus_connection_add_filter() + * are run. If any filter returns #DBUS_HANDLER_RESULT_HANDLED + * then processing stops after that filter. + * + * Third, if the message is a method call it is forwarded to + * any registered object path handlers added with + * dbus_connection_register_object_path() or + * dbus_connection_register_fallback(). + * + * A single call to dbus_connection_dispatch() will process at most + * one message; it will not clear the entire message queue. + * + * Be careful about calling dbus_connection_dispatch() from inside a + * message handler, i.e. calling dbus_connection_dispatch() + * recursively. If threads have been initialized with a recursive + * mutex function, then this will not deadlock; however, it can + * certainly confuse your application. + * + * @todo some FIXME in here about handling DBUS_HANDLER_RESULT_NEED_MEMORY + * + * @param connection the connection + * @returns dispatch status, see dbus_connection_get_dispatch_status() + */ +DBusDispatchStatus +dbus_connection_dispatch (DBusConnection *connection) +{ + DBusMessage *message; + DBusList *link, *filter_list_copy, *message_link; + DBusHandlerResult result; + DBusPendingCall *pending; + dbus_int32_t reply_serial; + DBusDispatchStatus status; + dbus_bool_t found_object; + + _dbus_return_val_if_fail (connection != NULL, DBUS_DISPATCH_COMPLETE); + + _dbus_verbose ("\n"); + + CONNECTION_LOCK (connection); + status = _dbus_connection_get_dispatch_status_unlocked (connection); + if (status != DBUS_DISPATCH_DATA_REMAINS) + { + /* unlocks and calls out to user code */ + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + return status; + } + + /* We need to ref the connection since the callback could potentially + * drop the last ref to it + */ + _dbus_connection_ref_unlocked (connection); + + _dbus_connection_acquire_dispatch (connection); + HAVE_LOCK_CHECK (connection); + + message_link = _dbus_connection_pop_message_link_unlocked (connection); + if (message_link == NULL) + { + /* another thread dispatched our stuff */ + + _dbus_verbose ("another thread dispatched message (during acquire_dispatch above)\n"); + + _dbus_connection_release_dispatch (connection); + + status = _dbus_connection_get_dispatch_status_unlocked (connection); + + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + + dbus_connection_unref (connection); + + return status; + } + + message = message_link->data; + + _dbus_verbose (" dispatching message %p (%s %s %s '%s')\n", + message, + dbus_message_type_to_string (dbus_message_get_type (message)), + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : + "no interface", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : + "no member", + dbus_message_get_signature (message)); + + result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + /* Pending call handling must be first, because if you do + * dbus_connection_send_with_reply_and_block() or + * dbus_pending_call_block() then no handlers/filters will be run on + * the reply. We want consistent semantics in the case where we + * dbus_connection_dispatch() the reply. + */ + + reply_serial = dbus_message_get_reply_serial (message); + pending = _dbus_hash_table_lookup_int (connection->pending_replies, + reply_serial); + if (pending) + { + _dbus_verbose ("Dispatching a pending reply\n"); + complete_pending_call_and_unlock (connection, pending, message); + pending = NULL; /* it's probably unref'd */ + + CONNECTION_LOCK (connection); + _dbus_verbose ("pending call completed in dispatch\n"); + result = DBUS_HANDLER_RESULT_HANDLED; + goto out; + } + + /* If skipping builtin filters, we are probably a monitor. */ + if (connection->builtin_filters_enabled) + { + result = _dbus_connection_run_builtin_filters_unlocked_no_update (connection, message); + if (result != DBUS_HANDLER_RESULT_NOT_YET_HANDLED) + goto out; + } + + if (!_dbus_list_copy (&connection->filter_list, &filter_list_copy)) + { + _dbus_connection_release_dispatch (connection); + HAVE_LOCK_CHECK (connection); + + _dbus_connection_failed_pop (connection, message_link); + + /* unlocks and calls user code */ + _dbus_connection_update_dispatch_status_and_unlock (connection, + DBUS_DISPATCH_NEED_MEMORY); + dbus_connection_unref (connection); + + return DBUS_DISPATCH_NEED_MEMORY; + } + + for (link = _dbus_list_get_first_link (&filter_list_copy); + link != NULL; + link = _dbus_list_get_next_link (&filter_list_copy, link)) + _dbus_message_filter_ref (link->data); + + /* We're still protected from dispatch() reentrancy here + * since we acquired the dispatcher + */ + CONNECTION_UNLOCK (connection); + + link = _dbus_list_get_first_link (&filter_list_copy); + while (link != NULL) + { + DBusMessageFilter *filter = link->data; + DBusList *next = _dbus_list_get_next_link (&filter_list_copy, link); + + if (filter->function == NULL) + { + _dbus_verbose (" filter was removed in a callback function\n"); + link = next; + continue; + } + + _dbus_verbose (" running filter on message %p\n", message); + result = (* filter->function) (connection, message, filter->user_data); + + if (result != DBUS_HANDLER_RESULT_NOT_YET_HANDLED) + break; + + link = next; + } + + _dbus_list_clear_full (&filter_list_copy, + (DBusFreeFunction) _dbus_message_filter_unref); + + CONNECTION_LOCK (connection); + + if (result == DBUS_HANDLER_RESULT_NEED_MEMORY) + { + _dbus_verbose ("No memory\n"); + goto out; + } + else if (result == DBUS_HANDLER_RESULT_HANDLED) + { + _dbus_verbose ("filter handled message in dispatch\n"); + goto out; + } + + /* We're still protected from dispatch() reentrancy here + * since we acquired the dispatcher + */ + _dbus_verbose (" running object path dispatch on message %p (%s %s %s '%s')\n", + message, + dbus_message_type_to_string (dbus_message_get_type (message)), + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : + "no interface", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : + "no member", + dbus_message_get_signature (message)); + + HAVE_LOCK_CHECK (connection); + result = _dbus_object_tree_dispatch_and_unlock (connection->objects, + message, + &found_object); + + CONNECTION_LOCK (connection); + + if (result != DBUS_HANDLER_RESULT_NOT_YET_HANDLED) + { + _dbus_verbose ("object tree handled message in dispatch\n"); + goto out; + } + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_CALL) + { + DBusMessage *reply; + DBusString str; + DBusPreallocatedSend *preallocated; + DBusList *expire_link; + + _dbus_verbose (" sending error %s\n", + DBUS_ERROR_UNKNOWN_METHOD); + + if (!_dbus_string_init (&str)) + { + result = DBUS_HANDLER_RESULT_NEED_MEMORY; + _dbus_verbose ("no memory for error string in dispatch\n"); + goto out; + } + + if (!_dbus_string_append_printf (&str, + "Method \"%s\" with signature \"%s\" on interface \"%s\" doesn't exist\n", + dbus_message_get_member (message), + dbus_message_get_signature (message), + dbus_message_get_interface (message))) + { + _dbus_string_free (&str); + result = DBUS_HANDLER_RESULT_NEED_MEMORY; + _dbus_verbose ("no memory for error string in dispatch\n"); + goto out; + } + + reply = dbus_message_new_error (message, + found_object ? DBUS_ERROR_UNKNOWN_METHOD : DBUS_ERROR_UNKNOWN_OBJECT, + _dbus_string_get_const_data (&str)); + _dbus_string_free (&str); + + if (reply == NULL) + { + result = DBUS_HANDLER_RESULT_NEED_MEMORY; + _dbus_verbose ("no memory for error reply in dispatch\n"); + goto out; + } + + expire_link = _dbus_list_alloc_link (reply); + + if (expire_link == NULL) + { + dbus_message_unref (reply); + result = DBUS_HANDLER_RESULT_NEED_MEMORY; + _dbus_verbose ("no memory for error send in dispatch\n"); + goto out; + } + + preallocated = _dbus_connection_preallocate_send_unlocked (connection); + + if (preallocated == NULL) + { + _dbus_list_free_link (expire_link); + /* It's OK that this is finalized, because it hasn't been seen by + * anything that could attach user callbacks */ + dbus_message_unref (reply); + result = DBUS_HANDLER_RESULT_NEED_MEMORY; + _dbus_verbose ("no memory for error send in dispatch\n"); + goto out; + } + + _dbus_connection_send_preallocated_unlocked_no_update (connection, preallocated, + reply, NULL); + /* reply will be freed when we release the lock */ + _dbus_list_prepend_link (&connection->expired_messages, expire_link); + + result = DBUS_HANDLER_RESULT_HANDLED; + } + + _dbus_verbose (" done dispatching %p (%s %s %s '%s') on connection %p\n", message, + dbus_message_type_to_string (dbus_message_get_type (message)), + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : + "no interface", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : + "no member", + dbus_message_get_signature (message), + connection); + + out: + if (result == DBUS_HANDLER_RESULT_NEED_MEMORY) + { + _dbus_verbose ("out of memory\n"); + + /* Put message back, and we'll start over. + * Yes this means handlers must be idempotent if they + * don't return HANDLED; c'est la vie. + */ + _dbus_connection_putback_message_link_unlocked (connection, + message_link); + /* now we don't want to free them */ + message_link = NULL; + message = NULL; + } + else + { + _dbus_verbose (" ... done dispatching\n"); + } + + _dbus_connection_release_dispatch (connection); + HAVE_LOCK_CHECK (connection); + + if (message != NULL) + { + /* We don't want this message to count in maximum message limits when + * computing the dispatch status, below. We have to drop the lock + * temporarily, because finalizing a message can trigger callbacks. + * + * We have a reference to the connection, and we don't use any cached + * pointers to the connection's internals below this point, so it should + * be safe to drop the lock and take it back. */ + CONNECTION_UNLOCK (connection); + dbus_message_unref (message); + CONNECTION_LOCK (connection); + } + + if (message_link != NULL) + _dbus_list_free_link (message_link); + + _dbus_verbose ("before final status update\n"); + status = _dbus_connection_get_dispatch_status_unlocked (connection); + + /* unlocks and calls user code */ + _dbus_connection_update_dispatch_status_and_unlock (connection, status); + + dbus_connection_unref (connection); + + return status; +} + +/** + * Sets the watch functions for the connection. These functions are + * responsible for making the application's main loop aware of file + * descriptors that need to be monitored for events, using select() or + * poll(). When using Qt, typically the DBusAddWatchFunction would + * create a QSocketNotifier. When using GLib, the DBusAddWatchFunction + * could call g_io_add_watch(), or could be used as part of a more + * elaborate GSource. Note that when a watch is added, it may + * not be enabled. + * + * The DBusWatchToggledFunction notifies the application that the + * watch has been enabled or disabled. Call dbus_watch_get_enabled() + * to check this. A disabled watch should have no effect, and enabled + * watch should be added to the main loop. This feature is used + * instead of simply adding/removing the watch because + * enabling/disabling can be done without memory allocation. The + * toggled function may be NULL if a main loop re-queries + * dbus_watch_get_enabled() every time anyway. + * + * The DBusWatch can be queried for the file descriptor to watch using + * dbus_watch_get_unix_fd() or dbus_watch_get_socket(), and for the + * events to watch for using dbus_watch_get_flags(). The flags + * returned by dbus_watch_get_flags() will only contain + * DBUS_WATCH_READABLE and DBUS_WATCH_WRITABLE, never + * DBUS_WATCH_HANGUP or DBUS_WATCH_ERROR; all watches implicitly + * include a watch for hangups, errors, and other exceptional + * conditions. + * + * Once a file descriptor becomes readable or writable, or an exception + * occurs, dbus_watch_handle() should be called to + * notify the connection of the file descriptor's condition. + * + * dbus_watch_handle() cannot be called during the + * DBusAddWatchFunction, as the connection will not be ready to handle + * that watch yet. + * + * It is not allowed to reference a DBusWatch after it has been passed + * to remove_function. + * + * If #FALSE is returned due to lack of memory, the failure may be due + * to a #FALSE return from the new add_function. If so, the + * add_function may have been called successfully one or more times, + * but the remove_function will also have been called to remove any + * successful adds. i.e. if #FALSE is returned the net result + * should be that dbus_connection_set_watch_functions() has no effect, + * but the add_function and remove_function may have been called. + * + * @note The thread lock on DBusConnection is held while + * watch functions are invoked, so inside these functions you + * may not invoke any methods on DBusConnection or it will deadlock. + * See the comments in the code or http://lists.freedesktop.org/archives/dbus/2007-July/tread.html#8144 + * if you encounter this issue and want to attempt writing a patch. + * + * @param connection the connection. + * @param add_function function to begin monitoring a new descriptor. + * @param remove_function function to stop monitoring a descriptor. + * @param toggled_function function to notify of enable/disable + * @param data data to pass to add_function and remove_function. + * @param free_data_function function to be called to free the data. + * @returns #FALSE on failure (no memory) + */ +dbus_bool_t +dbus_connection_set_watch_functions (DBusConnection *connection, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + DBusWatchToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function) +{ + dbus_bool_t retval; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + + CONNECTION_LOCK (connection); + + retval = _dbus_watch_list_set_functions (connection->watches, + add_function, remove_function, + toggled_function, + data, free_data_function); + + CONNECTION_UNLOCK (connection); + + return retval; +} + +/** + * Sets the timeout functions for the connection. These functions are + * responsible for making the application's main loop aware of timeouts. + * When using Qt, typically the DBusAddTimeoutFunction would create a + * QTimer. When using GLib, the DBusAddTimeoutFunction would call + * g_timeout_add. + * + * The DBusTimeoutToggledFunction notifies the application that the + * timeout has been enabled or disabled. Call + * dbus_timeout_get_enabled() to check this. A disabled timeout should + * have no effect, and enabled timeout should be added to the main + * loop. This feature is used instead of simply adding/removing the + * timeout because enabling/disabling can be done without memory + * allocation. With Qt, QTimer::start() and QTimer::stop() can be used + * to enable and disable. The toggled function may be NULL if a main + * loop re-queries dbus_timeout_get_enabled() every time anyway. + * Whenever a timeout is toggled, its interval may change. + * + * The DBusTimeout can be queried for the timer interval using + * dbus_timeout_get_interval(). dbus_timeout_handle() should be called + * repeatedly, each time the interval elapses, starting after it has + * elapsed once. The timeout stops firing when it is removed with the + * given remove_function. The timer interval may change whenever the + * timeout is added, removed, or toggled. + * + * @note The thread lock on DBusConnection is held while + * timeout functions are invoked, so inside these functions you + * may not invoke any methods on DBusConnection or it will deadlock. + * See the comments in the code or http://lists.freedesktop.org/archives/dbus/2007-July/thread.html#8144 + * if you encounter this issue and want to attempt writing a patch. + * + * @param connection the connection. + * @param add_function function to add a timeout. + * @param remove_function function to remove a timeout. + * @param toggled_function function to notify of enable/disable + * @param data data to pass to add_function and remove_function. + * @param free_data_function function to be called to free the data. + * @returns #FALSE on failure (no memory) + */ +dbus_bool_t +dbus_connection_set_timeout_functions (DBusConnection *connection, + DBusAddTimeoutFunction add_function, + DBusRemoveTimeoutFunction remove_function, + DBusTimeoutToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function) +{ + dbus_bool_t retval; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + + CONNECTION_LOCK (connection); + + retval = _dbus_timeout_list_set_functions (connection->timeouts, + add_function, remove_function, + toggled_function, + data, free_data_function); + + CONNECTION_UNLOCK (connection); + + return retval; +} + +/** + * Sets the mainloop wakeup function for the connection. This function + * is responsible for waking up the main loop (if its sleeping in + * another thread) when some some change has happened to the + * connection that the mainloop needs to reconsider (e.g. a message + * has been queued for writing). When using Qt, this typically + * results in a call to QEventLoop::wakeUp(). When using GLib, it + * would call g_main_context_wakeup(). + * + * @param connection the connection. + * @param wakeup_main_function function to wake up the mainloop + * @param data data to pass wakeup_main_function + * @param free_data_function function to be called to free the data. + */ +void +dbus_connection_set_wakeup_main_function (DBusConnection *connection, + DBusWakeupMainFunction wakeup_main_function, + void *data, + DBusFreeFunction free_data_function) +{ + void *old_data; + DBusFreeFunction old_free_data; + + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + old_data = connection->wakeup_main_data; + old_free_data = connection->free_wakeup_main_data; + + connection->wakeup_main_function = wakeup_main_function; + connection->wakeup_main_data = data; + connection->free_wakeup_main_data = free_data_function; + + CONNECTION_UNLOCK (connection); + + /* Callback outside the lock */ + if (old_free_data) + (*old_free_data) (old_data); +} + +/** + * Set a function to be invoked when the dispatch status changes. + * If the dispatch status is #DBUS_DISPATCH_DATA_REMAINS, then + * dbus_connection_dispatch() needs to be called to process incoming + * messages. However, dbus_connection_dispatch() MUST NOT BE CALLED + * from inside the DBusDispatchStatusFunction. Indeed, almost + * any reentrancy in this function is a bad idea. Instead, + * the DBusDispatchStatusFunction should simply save an indication + * that messages should be dispatched later, when the main loop + * is re-entered. + * + * If you don't set a dispatch status function, you have to be sure to + * dispatch on every iteration of your main loop, especially if + * dbus_watch_handle() or dbus_timeout_handle() were called. + * + * @param connection the connection + * @param function function to call on dispatch status changes + * @param data data for function + * @param free_data_function free the function data + */ +void +dbus_connection_set_dispatch_status_function (DBusConnection *connection, + DBusDispatchStatusFunction function, + void *data, + DBusFreeFunction free_data_function) +{ + void *old_data; + DBusFreeFunction old_free_data; + + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + old_data = connection->dispatch_status_data; + old_free_data = connection->free_dispatch_status_data; + + connection->dispatch_status_function = function; + connection->dispatch_status_data = data; + connection->free_dispatch_status_data = free_data_function; + + CONNECTION_UNLOCK (connection); + + /* Callback outside the lock */ + if (old_free_data) + (*old_free_data) (old_data); +} + +/** + * Get the UNIX file descriptor of the connection, if any. This can + * be used for SELinux access control checks with getpeercon() for + * example. DO NOT read or write to the file descriptor, or try to + * select() on it; use DBusWatch for main loop integration. Not all + * connections will have a file descriptor. So for adding descriptors + * to the main loop, use dbus_watch_get_unix_fd() and so forth. + * + * If the connection is socket-based, you can also use + * dbus_connection_get_socket(), which will work on Windows too. + * This function always fails on Windows. + * + * Right now the returned descriptor is always a socket, but + * that is not guaranteed. + * + * @param connection the connection + * @param fd return location for the file descriptor. + * @returns #TRUE if fd is successfully obtained. + */ +dbus_bool_t +dbus_connection_get_unix_fd (DBusConnection *connection, + int *fd) +{ + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (connection->transport != NULL, FALSE); + +#ifdef DBUS_WIN + /* FIXME do this on a lower level */ + return FALSE; +#endif + + return dbus_connection_get_socket(connection, fd); +} + +/** + * Gets the underlying Windows or UNIX socket file descriptor + * of the connection, if any. DO NOT read or write to the file descriptor, or try to + * select() on it; use DBusWatch for main loop integration. Not all + * connections will have a socket. So for adding descriptors + * to the main loop, use dbus_watch_get_socket() and so forth. + * + * If the connection is not socket-based, this function will return FALSE, + * even if the connection does have a file descriptor of some kind. + * i.e. this function always returns specifically a socket file descriptor. + * + * @param connection the connection + * @param fd return location for the file descriptor. + * @returns #TRUE if fd is successfully obtained. + */ +dbus_bool_t +dbus_connection_get_socket(DBusConnection *connection, + int *fd) +{ + dbus_bool_t retval; + DBusSocket s = DBUS_SOCKET_INIT; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (connection->transport != NULL, FALSE); + + CONNECTION_LOCK (connection); + + retval = _dbus_transport_get_socket_fd (connection->transport, &s); + + if (retval) + { + *fd = _dbus_socket_get_int (s); + } + + CONNECTION_UNLOCK (connection); + + return retval; +} + + +/** + * Gets the UNIX user ID of the connection if known. Returns #TRUE if + * the uid is filled in. Always returns #FALSE on non-UNIX platforms + * for now, though in theory someone could hook Windows to NIS or + * something. Always returns #FALSE prior to authenticating the + * connection. + * + * The UID is only read by servers from clients; clients can't usually + * get the UID of servers, because servers do not authenticate to + * clients. The returned UID is the UID the connection authenticated + * as. + * + * The message bus is a server and the apps connecting to the bus + * are clients. + * + * You can ask the bus to tell you the UID of another connection though + * if you like; this is done with dbus_bus_get_unix_user(). + * + * @param connection the connection + * @param uid return location for the user ID + * @returns #TRUE if uid is filled in with a valid user ID + */ +dbus_bool_t +dbus_connection_get_unix_user (DBusConnection *connection, + unsigned long *uid) +{ + dbus_bool_t result; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (uid != NULL, FALSE); + + CONNECTION_LOCK (connection); + + if (!_dbus_transport_try_to_authenticate (connection->transport)) + result = FALSE; + else + result = _dbus_transport_get_unix_user (connection->transport, + uid); + +#ifdef DBUS_WIN + _dbus_assert (!result); +#endif + + CONNECTION_UNLOCK (connection); + + return result; +} + +/** + * Gets the process ID of the connection if any. + * Returns #TRUE if the pid is filled in. + * Always returns #FALSE prior to authenticating the + * connection. + * + * @param connection the connection + * @param pid return location for the process ID + * @returns #TRUE if uid is filled in with a valid process ID + */ +dbus_bool_t +dbus_connection_get_unix_process_id (DBusConnection *connection, + unsigned long *pid) +{ + dbus_bool_t result; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (pid != NULL, FALSE); + + CONNECTION_LOCK (connection); + + if (!_dbus_transport_try_to_authenticate (connection->transport)) + result = FALSE; + else + result = _dbus_transport_get_unix_process_id (connection->transport, + pid); + + CONNECTION_UNLOCK (connection); + + return result; +} + +/** + * Gets the ADT audit data of the connection if any. + * Returns #TRUE if the structure pointer is returned. + * Always returns #FALSE prior to authenticating the + * connection. + * + * @param connection the connection + * @param data return location for audit data + * @param data_size return location for length of audit data + * @returns #TRUE if audit data is filled in with a valid ucred pointer + */ +dbus_bool_t +dbus_connection_get_adt_audit_session_data (DBusConnection *connection, + void **data, + dbus_int32_t *data_size) +{ + dbus_bool_t result; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (data != NULL, FALSE); + _dbus_return_val_if_fail (data_size != NULL, FALSE); + + CONNECTION_LOCK (connection); + + if (!_dbus_transport_try_to_authenticate (connection->transport)) + result = FALSE; + else + result = _dbus_transport_get_adt_audit_session_data (connection->transport, + data, + data_size); + CONNECTION_UNLOCK (connection); + + return result; +} + +/** + * Sets a predicate function used to determine whether a given user ID + * is allowed to connect. When an incoming connection has + * authenticated with a particular user ID, this function is called; + * if it returns #TRUE, the connection is allowed to proceed, + * otherwise the connection is disconnected. + * + * If the function is set to #NULL (as it is by default), then + * only the same UID as the server process will be allowed to + * connect. Also, root is always allowed to connect. + * + * On Windows, the function will be set and its free_data_function will + * be invoked when the connection is freed or a new function is set. + * However, the function will never be called, because there are + * no UNIX user ids to pass to it, or at least none of the existing + * auth protocols would allow authenticating as a UNIX user on Windows. + * + * @param connection the connection + * @param function the predicate + * @param data data to pass to the predicate + * @param free_data_function function to free the data + */ +void +dbus_connection_set_unix_user_function (DBusConnection *connection, + DBusAllowUnixUserFunction function, + void *data, + DBusFreeFunction free_data_function) +{ + void *old_data = NULL; + DBusFreeFunction old_free_function = NULL; + + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + _dbus_transport_set_unix_user_function (connection->transport, + function, data, free_data_function, + &old_data, &old_free_function); + CONNECTION_UNLOCK (connection); + + if (old_free_function != NULL) + (* old_free_function) (old_data); +} + +/* Same calling convention as dbus_connection_get_windows_user */ +dbus_bool_t +_dbus_connection_get_linux_security_label (DBusConnection *connection, + char **label_p) +{ + dbus_bool_t result; + + _dbus_assert (connection != NULL); + _dbus_assert (label_p != NULL); + + CONNECTION_LOCK (connection); + + if (!_dbus_transport_try_to_authenticate (connection->transport)) + result = FALSE; + else + result = _dbus_transport_get_linux_security_label (connection->transport, + label_p); +#ifndef __linux__ + _dbus_assert (!result); +#endif + + CONNECTION_UNLOCK (connection); + + return result; +} + +DBusCredentials * +_dbus_connection_get_credentials (DBusConnection *connection) +{ + DBusCredentials *result; + + _dbus_assert (connection != NULL); + + CONNECTION_LOCK (connection); + + if (!_dbus_transport_try_to_authenticate (connection->transport)) + result = NULL; + else + result = _dbus_transport_get_credentials (connection->transport); + + CONNECTION_UNLOCK (connection); + + return result; +} + +/** + * Gets the Windows user SID of the connection if known. Returns + * #TRUE if the ID is filled in. Always returns #FALSE on non-Windows + * platforms for now, though in theory someone could hook UNIX to + * Active Directory or something. Always returns #FALSE prior to + * authenticating the connection. + * + * The user is only read by servers from clients; clients can't usually + * get the user of servers, because servers do not authenticate to + * clients. The returned user is the user the connection authenticated + * as. + * + * The message bus is a server and the apps connecting to the bus + * are clients. + * + * The returned user string has to be freed with dbus_free(). + * + * The return value indicates whether the user SID is available; + * if it's available but we don't have the memory to copy it, + * then the return value is #TRUE and #NULL is given as the SID. + * + * @todo We would like to be able to say "You can ask the bus to tell + * you the user of another connection though if you like; this is done + * with dbus_bus_get_windows_user()." But this has to be implemented + * in bus/driver.c and dbus/dbus-bus.c, and is pointless anyway + * since on Windows we only use the session bus for now. + * + * @param connection the connection + * @param windows_sid_p return location for an allocated copy of the user ID, or #NULL if no memory + * @returns #TRUE if user is available (returned value may be #NULL anyway if no memory) + */ +dbus_bool_t +dbus_connection_get_windows_user (DBusConnection *connection, + char **windows_sid_p) +{ + dbus_bool_t result; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (windows_sid_p != NULL, FALSE); + + CONNECTION_LOCK (connection); + + if (!_dbus_transport_try_to_authenticate (connection->transport)) + result = FALSE; + else + result = _dbus_transport_get_windows_user (connection->transport, + windows_sid_p); + +#ifdef DBUS_UNIX + _dbus_assert (!result); +#endif + + CONNECTION_UNLOCK (connection); + + return result; +} + +/** + * Sets a predicate function used to determine whether a given user ID + * is allowed to connect. When an incoming connection has + * authenticated with a particular user ID, this function is called; + * if it returns #TRUE, the connection is allowed to proceed, + * otherwise the connection is disconnected. + * + * If the function is set to #NULL (as it is by default), then + * only the same user owning the server process will be allowed to + * connect. + * + * On UNIX, the function will be set and its free_data_function will + * be invoked when the connection is freed or a new function is set. + * However, the function will never be called, because there is no + * way right now to authenticate as a Windows user on UNIX. + * + * @param connection the connection + * @param function the predicate + * @param data data to pass to the predicate + * @param free_data_function function to free the data + */ +void +dbus_connection_set_windows_user_function (DBusConnection *connection, + DBusAllowWindowsUserFunction function, + void *data, + DBusFreeFunction free_data_function) +{ + void *old_data = NULL; + DBusFreeFunction old_free_function = NULL; + + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + _dbus_transport_set_windows_user_function (connection->transport, + function, data, free_data_function, + &old_data, &old_free_function); + CONNECTION_UNLOCK (connection); + + if (old_free_function != NULL) + (* old_free_function) (old_data); +} + +/** + * This function must be called on the server side of a connection when the + * connection is first seen in the #DBusNewConnectionFunction. If set to + * #TRUE (the default is #FALSE), then the connection can proceed even if + * the client does not authenticate as some user identity, i.e. clients + * can connect anonymously. + * + * This setting interacts with the available authorization mechanisms + * (see dbus_server_set_auth_mechanisms()). Namely, an auth mechanism + * such as ANONYMOUS that supports anonymous auth must be included in + * the list of available mechanisms for anonymous login to work. + * + * This setting also changes the default rule for connections + * authorized as a user; normally, if a connection authorizes as + * a user identity, it is permitted if the user identity is + * root or the user identity matches the user identity of the server + * process. If anonymous connections are allowed, however, + * then any user identity is allowed. + * + * You can override the rules for connections authorized as a + * user identity with dbus_connection_set_unix_user_function() + * and dbus_connection_set_windows_user_function(). + * + * @param connection the connection + * @param value whether to allow authentication as an anonymous user + */ +void +dbus_connection_set_allow_anonymous (DBusConnection *connection, + dbus_bool_t value) +{ + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + _dbus_transport_set_allow_anonymous (connection->transport, value); + CONNECTION_UNLOCK (connection); +} + +/** + * Enables the builtin filtering of messages. + * + * Currently the only filtering implemented by libdbus and mandated by the spec + * is that of peer messages. + * + * If #TRUE, #DBusConnection automatically handles all messages to the + * org.freedesktop.DBus.Peer interface. For monitors this can break the + * specification if the response is sending a message. + * + * If #FALSE, the result is similar to calling + * dbus_connection_set_route_peer_messages() with argument TRUE, but + * messages with a NULL destination are also dispatched to the + * application instead of being passed to the built-in filters. + * + * If a normal application disables this flag, it can break things badly. So + * only unset this if you are a monitor. + * + * @param connection the connection + * @param value #TRUE to pass through org.freedesktop.DBus.Peer messages + */ +void +dbus_connection_set_builtin_filters_enabled (DBusConnection *connection, + dbus_bool_t value) +{ + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + connection->builtin_filters_enabled = value; + CONNECTION_UNLOCK (connection); +} + +/** + * + * Normally #DBusConnection automatically handles all messages to the + * org.freedesktop.DBus.Peer interface. However, the message bus wants + * to be able to route methods on that interface through the bus and + * to other applications. If routing peer messages is enabled, then + * messages with the org.freedesktop.DBus.Peer interface that also + * have a bus destination name set will not be automatically + * handled by the #DBusConnection and instead will be dispatched + * normally to the application. + * + * If a normal application sets this flag, it can break things badly. + * So don't set this unless you are the message bus. + * + * @param connection the connection + * @param value #TRUE to pass through org.freedesktop.DBus.Peer messages with a bus name set + */ +void +dbus_connection_set_route_peer_messages (DBusConnection *connection, + dbus_bool_t value) +{ + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + connection->route_peer_messages = value; + CONNECTION_UNLOCK (connection); +} + +/** + * Adds a message filter. Filters are handlers that are run on all + * incoming messages, prior to the objects registered with + * dbus_connection_register_object_path(). Filters are run in the + * order that they were added. The same handler can be added as a + * filter more than once, in which case it will be run more than once. + * Filters added during a filter callback won't be run on the message + * being processed. + * + * @todo we don't run filters on messages while blocking without + * entering the main loop, since filters are run as part of + * dbus_connection_dispatch(). This is probably a feature, as filters + * could create arbitrary reentrancy. But kind of sucks if you're + * trying to filter METHOD_RETURN for some reason. + * + * @param connection the connection + * @param function function to handle messages + * @param user_data user data to pass to the function + * @param free_data_function function to use for freeing user data + * @returns #TRUE on success, #FALSE if not enough memory. + */ +dbus_bool_t +dbus_connection_add_filter (DBusConnection *connection, + DBusHandleMessageFunction function, + void *user_data, + DBusFreeFunction free_data_function) +{ + DBusMessageFilter *filter; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (function != NULL, FALSE); + + filter = dbus_new0 (DBusMessageFilter, 1); + if (filter == NULL) + return FALSE; + + _dbus_atomic_inc (&filter->refcount); + + CONNECTION_LOCK (connection); + + if (!_dbus_list_append (&connection->filter_list, + filter)) + { + _dbus_message_filter_unref (filter); + CONNECTION_UNLOCK (connection); + return FALSE; + } + + /* Fill in filter after all memory allocated, + * so we don't run the free_user_data_function + * if the add_filter() fails + */ + + filter->function = function; + filter->user_data = user_data; + filter->free_user_data_function = free_data_function; + + CONNECTION_UNLOCK (connection); + return TRUE; +} + +/** + * Removes a previously-added message filter. It is a programming + * error to call this function for a handler that has not been added + * as a filter. If the given handler was added more than once, only + * one instance of it will be removed (the most recently-added + * instance). + * + * @param connection the connection + * @param function the handler to remove + * @param user_data user data for the handler to remove + * + */ +void +dbus_connection_remove_filter (DBusConnection *connection, + DBusHandleMessageFunction function, + void *user_data) +{ + DBusList *link; + DBusMessageFilter *filter; + + _dbus_return_if_fail (connection != NULL); + _dbus_return_if_fail (function != NULL); + + CONNECTION_LOCK (connection); + + filter = NULL; + + link = _dbus_list_get_last_link (&connection->filter_list); + while (link != NULL) + { + filter = link->data; + + if (filter->function == function && + filter->user_data == user_data) + { + _dbus_list_remove_link (&connection->filter_list, link); + filter->function = NULL; + + break; + } + + link = _dbus_list_get_prev_link (&connection->filter_list, link); + filter = NULL; + } + + CONNECTION_UNLOCK (connection); + +#ifndef DBUS_DISABLE_CHECKS + if (filter == NULL) + { + _dbus_warn_check_failed ("Attempt to remove filter function %p user data %p, but no such filter has been added", + function, user_data); + return; + } +#endif + + /* Call application code */ + if (filter->free_user_data_function) + (* filter->free_user_data_function) (filter->user_data); + + filter->free_user_data_function = NULL; + filter->user_data = NULL; + + _dbus_message_filter_unref (filter); +} + +/** + * Registers a handler for a given path or subsection in the object + * hierarchy. The given vtable handles messages sent to exactly the + * given path or also for paths bellow that, depending on fallback + * parameter. + * + * @param connection the connection + * @param fallback whether to handle messages also for "subdirectory" + * @param path a '/' delimited string of path elements + * @param vtable the virtual table + * @param user_data data to pass to functions in the vtable + * @param error address where an error can be returned + * @returns #FALSE if an error (#DBUS_ERROR_NO_MEMORY or + * #DBUS_ERROR_OBJECT_PATH_IN_USE) is reported + */ +static dbus_bool_t +_dbus_connection_register_object_path (DBusConnection *connection, + dbus_bool_t fallback, + const char *path, + const DBusObjectPathVTable *vtable, + void *user_data, + DBusError *error) +{ + char **decomposed_path; + dbus_bool_t retval; + + if (!_dbus_decompose_path (path, strlen (path), &decomposed_path, NULL)) + return FALSE; + + CONNECTION_LOCK (connection); + + retval = _dbus_object_tree_register (connection->objects, + fallback, + (const char **) decomposed_path, vtable, + user_data, error); + + CONNECTION_UNLOCK (connection); + + dbus_free_string_array (decomposed_path); + + return retval; +} + +/** + * Registers a handler for a given path in the object hierarchy. + * The given vtable handles messages sent to exactly the given path. + * + * @param connection the connection + * @param path a '/' delimited string of path elements + * @param vtable the virtual table + * @param user_data data to pass to functions in the vtable + * @param error address where an error can be returned + * @returns #FALSE if an error (#DBUS_ERROR_NO_MEMORY or + * #DBUS_ERROR_OBJECT_PATH_IN_USE) is reported + */ +dbus_bool_t +dbus_connection_try_register_object_path (DBusConnection *connection, + const char *path, + const DBusObjectPathVTable *vtable, + void *user_data, + DBusError *error) +{ + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (path != NULL, FALSE); + _dbus_return_val_if_fail (path[0] == '/', FALSE); + _dbus_return_val_if_fail (vtable != NULL, FALSE); + + return _dbus_connection_register_object_path (connection, FALSE, path, vtable, user_data, error); +} + +/** + * Registers a handler for a given path in the object hierarchy. + * The given vtable handles messages sent to exactly the given path. + * + * It is a bug to call this function for object paths which already + * have a handler. Use dbus_connection_try_register_object_path() if this + * might be the case. + * + * @param connection the connection + * @param path a '/' delimited string of path elements + * @param vtable the virtual table + * @param user_data data to pass to functions in the vtable + * @returns #FALSE if an error (#DBUS_ERROR_NO_MEMORY or + * #DBUS_ERROR_OBJECT_PATH_IN_USE) ocurred + */ +dbus_bool_t +dbus_connection_register_object_path (DBusConnection *connection, + const char *path, + const DBusObjectPathVTable *vtable, + void *user_data) +{ + dbus_bool_t retval; + DBusError error = DBUS_ERROR_INIT; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (path != NULL, FALSE); + _dbus_return_val_if_fail (path[0] == '/', FALSE); + _dbus_return_val_if_fail (vtable != NULL, FALSE); + + retval = _dbus_connection_register_object_path (connection, FALSE, path, vtable, user_data, &error); + + if (dbus_error_has_name (&error, DBUS_ERROR_OBJECT_PATH_IN_USE)) + { + _dbus_warn ("%s", error.message); + dbus_error_free (&error); + return FALSE; + } + + return retval; +} + +/** + * Registers a fallback handler for a given subsection of the object + * hierarchy. The given vtable handles messages at or below the given + * path. You can use this to establish a default message handling + * policy for a whole "subdirectory." + * + * @param connection the connection + * @param path a '/' delimited string of path elements + * @param vtable the virtual table + * @param user_data data to pass to functions in the vtable + * @param error address where an error can be returned + * @returns #FALSE if an error (#DBUS_ERROR_NO_MEMORY or + * #DBUS_ERROR_OBJECT_PATH_IN_USE) is reported + */ +dbus_bool_t +dbus_connection_try_register_fallback (DBusConnection *connection, + const char *path, + const DBusObjectPathVTable *vtable, + void *user_data, + DBusError *error) +{ + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (path != NULL, FALSE); + _dbus_return_val_if_fail (path[0] == '/', FALSE); + _dbus_return_val_if_fail (vtable != NULL, FALSE); + + return _dbus_connection_register_object_path (connection, TRUE, path, vtable, user_data, error); +} + +/** + * Registers a fallback handler for a given subsection of the object + * hierarchy. The given vtable handles messages at or below the given + * path. You can use this to establish a default message handling + * policy for a whole "subdirectory." + * + * It is a bug to call this function for object paths which already + * have a handler. Use dbus_connection_try_register_fallback() if this + * might be the case. + * + * @param connection the connection + * @param path a '/' delimited string of path elements + * @param vtable the virtual table + * @param user_data data to pass to functions in the vtable + * @returns #FALSE if an error (#DBUS_ERROR_NO_MEMORY or + * #DBUS_ERROR_OBJECT_PATH_IN_USE) occured + */ +dbus_bool_t +dbus_connection_register_fallback (DBusConnection *connection, + const char *path, + const DBusObjectPathVTable *vtable, + void *user_data) +{ + dbus_bool_t retval; + DBusError error = DBUS_ERROR_INIT; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (path != NULL, FALSE); + _dbus_return_val_if_fail (path[0] == '/', FALSE); + _dbus_return_val_if_fail (vtable != NULL, FALSE); + + retval = _dbus_connection_register_object_path (connection, TRUE, path, vtable, user_data, &error); + + if (dbus_error_has_name (&error, DBUS_ERROR_OBJECT_PATH_IN_USE)) + { + _dbus_warn ("%s", error.message); + dbus_error_free (&error); + return FALSE; + } + + return retval; +} + +/** + * Unregisters the handler registered with exactly the given path. + * It's a bug to call this function for a path that isn't registered. + * Can unregister both fallback paths and object paths. + * + * @param connection the connection + * @param path a '/' delimited string of path elements + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_connection_unregister_object_path (DBusConnection *connection, + const char *path) +{ + char **decomposed_path; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (path != NULL, FALSE); + _dbus_return_val_if_fail (path[0] == '/', FALSE); + + if (!_dbus_decompose_path (path, strlen (path), &decomposed_path, NULL)) + return FALSE; + + CONNECTION_LOCK (connection); + + _dbus_object_tree_unregister_and_unlock (connection->objects, (const char **) decomposed_path); + + dbus_free_string_array (decomposed_path); + + return TRUE; +} + +/** + * Gets the user data passed to dbus_connection_register_object_path() + * or dbus_connection_register_fallback(). If nothing was registered + * at this path, the data is filled in with #NULL. + * + * @param connection the connection + * @param path the path you registered with + * @param data_p location to store the user data, or #NULL + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_connection_get_object_path_data (DBusConnection *connection, + const char *path, + void **data_p) +{ + char **decomposed_path; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (path != NULL, FALSE); + _dbus_return_val_if_fail (data_p != NULL, FALSE); + + *data_p = NULL; + + if (!_dbus_decompose_path (path, strlen (path), &decomposed_path, NULL)) + return FALSE; + + CONNECTION_LOCK (connection); + + *data_p = _dbus_object_tree_get_user_data_unlocked (connection->objects, (const char**) decomposed_path); + + CONNECTION_UNLOCK (connection); + + dbus_free_string_array (decomposed_path); + + return TRUE; +} + +/** + * Lists the registered fallback handlers and object path handlers at + * the given parent_path. The returned array should be freed with + * dbus_free_string_array(). + * + * @param connection the connection + * @param parent_path the path to list the child handlers of + * @param child_entries returns #NULL-terminated array of children + * @returns #FALSE if no memory to allocate the child entries + */ +dbus_bool_t +dbus_connection_list_registered (DBusConnection *connection, + const char *parent_path, + char ***child_entries) +{ + char **decomposed_path; + dbus_bool_t retval; + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (parent_path != NULL, FALSE); + _dbus_return_val_if_fail (parent_path[0] == '/', FALSE); + _dbus_return_val_if_fail (child_entries != NULL, FALSE); + + if (!_dbus_decompose_path (parent_path, strlen (parent_path), &decomposed_path, NULL)) + return FALSE; + + CONNECTION_LOCK (connection); + + retval = _dbus_object_tree_list_registered_and_unlock (connection->objects, + (const char **) decomposed_path, + child_entries); + dbus_free_string_array (decomposed_path); + + return retval; +} + +static DBusDataSlotAllocator slot_allocator = + _DBUS_DATA_SLOT_ALLOCATOR_INIT (_DBUS_LOCK_NAME (connection_slots)); + +/** + * Allocates an integer ID to be used for storing application-specific + * data on any DBusConnection. The allocated ID may then be used + * with dbus_connection_set_data() and dbus_connection_get_data(). + * The passed-in slot must be initialized to -1, and is filled in + * with the slot ID. If the passed-in slot is not -1, it's assumed + * to be already allocated, and its refcount is incremented. + * + * The allocated slot is global, i.e. all DBusConnection objects will + * have a slot with the given integer ID reserved. + * + * @param slot_p address of a global variable storing the slot + * @returns #FALSE on failure (no memory) + */ +dbus_bool_t +dbus_connection_allocate_data_slot (dbus_int32_t *slot_p) +{ + return _dbus_data_slot_allocator_alloc (&slot_allocator, + slot_p); +} + +/** + * Deallocates a global ID for connection data slots. + * dbus_connection_get_data() and dbus_connection_set_data() may no + * longer be used with this slot. Existing data stored on existing + * DBusConnection objects will be freed when the connection is + * finalized, but may not be retrieved (and may only be replaced if + * someone else reallocates the slot). When the refcount on the + * passed-in slot reaches 0, it is set to -1. + * + * @param slot_p address storing the slot to deallocate + */ +void +dbus_connection_free_data_slot (dbus_int32_t *slot_p) +{ + _dbus_return_if_fail (*slot_p >= 0); + + _dbus_data_slot_allocator_free (&slot_allocator, slot_p); +} + +/** + * Stores a pointer on a DBusConnection, along + * with an optional function to be used for freeing + * the data when the data is set again, or when + * the connection is finalized. The slot number + * must have been allocated with dbus_connection_allocate_data_slot(). + * + * @note This function does not take the + * main thread lock on DBusConnection, which allows it to be + * used from inside watch and timeout functions. (See the + * note in docs for dbus_connection_set_watch_functions().) + * A side effect of this is that you need to know there's + * a reference held on the connection while invoking + * dbus_connection_set_data(), or the connection could be + * finalized during dbus_connection_set_data(). + * + * @param connection the connection + * @param slot the slot number + * @param data the data to store + * @param free_data_func finalizer function for the data + * @returns #TRUE if there was enough memory to store the data + */ +dbus_bool_t +dbus_connection_set_data (DBusConnection *connection, + dbus_int32_t slot, + void *data, + DBusFreeFunction free_data_func) +{ + DBusFreeFunction old_free_func; + void *old_data; + dbus_bool_t retval; + + _dbus_return_val_if_fail (connection != NULL, FALSE); + _dbus_return_val_if_fail (slot >= 0, FALSE); + + SLOTS_LOCK (connection); + + retval = _dbus_data_slot_list_set (&slot_allocator, + &connection->slot_list, + slot, data, free_data_func, + &old_free_func, &old_data); + + SLOTS_UNLOCK (connection); + + if (retval) + { + /* Do the actual free outside the connection lock */ + if (old_free_func) + (* old_free_func) (old_data); + } + + return retval; +} + +/** + * Retrieves data previously set with dbus_connection_set_data(). + * The slot must still be allocated (must not have been freed). + * + * @note This function does not take the + * main thread lock on DBusConnection, which allows it to be + * used from inside watch and timeout functions. (See the + * note in docs for dbus_connection_set_watch_functions().) + * A side effect of this is that you need to know there's + * a reference held on the connection while invoking + * dbus_connection_get_data(), or the connection could be + * finalized during dbus_connection_get_data(). + * + * @param connection the connection + * @param slot the slot to get data from + * @returns the data, or #NULL if not found + */ +void* +dbus_connection_get_data (DBusConnection *connection, + dbus_int32_t slot) +{ + void *res; + + _dbus_return_val_if_fail (connection != NULL, NULL); + _dbus_return_val_if_fail (slot >= 0, NULL); + + SLOTS_LOCK (connection); + + res = _dbus_data_slot_list_get (&slot_allocator, + &connection->slot_list, + slot); + + SLOTS_UNLOCK (connection); + + return res; +} + +/** + * This function sets a global flag for whether dbus_connection_new() + * will set SIGPIPE behavior to SIG_IGN. + * + * @param will_modify_sigpipe #TRUE to allow sigpipe to be set to SIG_IGN + */ +void +dbus_connection_set_change_sigpipe (dbus_bool_t will_modify_sigpipe) +{ + if (will_modify_sigpipe) + _dbus_atomic_set_nonzero (&_dbus_modify_sigpipe); + else + _dbus_atomic_set_zero (&_dbus_modify_sigpipe); +} + +/** + * Specifies the maximum size message this connection is allowed to + * receive. Larger messages will result in disconnecting the + * connection. + * + * @param connection a #DBusConnection + * @param size maximum message size the connection can receive, in bytes + */ +void +dbus_connection_set_max_message_size (DBusConnection *connection, + long size) +{ + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + _dbus_transport_set_max_message_size (connection->transport, + size); + CONNECTION_UNLOCK (connection); +} + +/** + * Gets the value set by dbus_connection_set_max_message_size(). + * + * @param connection the connection + * @returns the max size of a single message + */ +long +dbus_connection_get_max_message_size (DBusConnection *connection) +{ + long res; + + _dbus_return_val_if_fail (connection != NULL, 0); + + CONNECTION_LOCK (connection); + res = _dbus_transport_get_max_message_size (connection->transport); + CONNECTION_UNLOCK (connection); + return res; +} + +/** + * Specifies the maximum number of unix fds a message on this + * connection is allowed to receive. Messages with more unix fds will + * result in disconnecting the connection. + * + * @param connection a #DBusConnection + * @param n maximum message unix fds the connection can receive + */ +void +dbus_connection_set_max_message_unix_fds (DBusConnection *connection, + long n) +{ + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + _dbus_transport_set_max_message_unix_fds (connection->transport, + n); + CONNECTION_UNLOCK (connection); +} + +/** + * Gets the value set by dbus_connection_set_max_message_unix_fds(). + * + * @param connection the connection + * @returns the max numer of unix fds of a single message + */ +long +dbus_connection_get_max_message_unix_fds (DBusConnection *connection) +{ + long res; + + _dbus_return_val_if_fail (connection != NULL, 0); + + CONNECTION_LOCK (connection); + res = _dbus_transport_get_max_message_unix_fds (connection->transport); + CONNECTION_UNLOCK (connection); + return res; +} + +/** + * Sets the maximum total number of bytes that can be used for all messages + * received on this connection. Messages count toward the maximum until + * they are finalized. When the maximum is reached, the connection will + * not read more data until some messages are finalized. + * + * The semantics of the maximum are: if outstanding messages are + * already above the maximum, additional messages will not be read. + * The semantics are not: if the next message would cause us to exceed + * the maximum, we don't read it. The reason is that we don't know the + * size of a message until after we read it. + * + * Thus, the max live messages size can actually be exceeded + * by up to the maximum size of a single message. + * + * Also, if we read say 1024 bytes off the wire in a single read(), + * and that contains a half-dozen small messages, we may exceed the + * size max by that amount. But this should be inconsequential. + * + * This does imply that we can't call read() with a buffer larger + * than we're willing to exceed this limit by. + * + * @param connection the connection + * @param size the maximum size in bytes of all outstanding messages + */ +void +dbus_connection_set_max_received_size (DBusConnection *connection, + long size) +{ + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + _dbus_transport_set_max_received_size (connection->transport, + size); + CONNECTION_UNLOCK (connection); +} + +/** + * Gets the value set by dbus_connection_set_max_received_size(). + * + * @param connection the connection + * @returns the max size of all live messages + */ +long +dbus_connection_get_max_received_size (DBusConnection *connection) +{ + long res; + + _dbus_return_val_if_fail (connection != NULL, 0); + + CONNECTION_LOCK (connection); + res = _dbus_transport_get_max_received_size (connection->transport); + CONNECTION_UNLOCK (connection); + return res; +} + +/** + * Sets the maximum total number of unix fds that can be used for all messages + * received on this connection. Messages count toward the maximum until + * they are finalized. When the maximum is reached, the connection will + * not read more data until some messages are finalized. + * + * The semantics are analogous to those of dbus_connection_set_max_received_size(). + * + * @param connection the connection + * @param n the maximum size in bytes of all outstanding messages + */ +void +dbus_connection_set_max_received_unix_fds (DBusConnection *connection, + long n) +{ + _dbus_return_if_fail (connection != NULL); + + CONNECTION_LOCK (connection); + _dbus_transport_set_max_received_unix_fds (connection->transport, + n); + CONNECTION_UNLOCK (connection); +} + +/** + * Gets the value set by dbus_connection_set_max_received_unix_fds(). + * + * @param connection the connection + * @returns the max unix fds of all live messages + */ +long +dbus_connection_get_max_received_unix_fds (DBusConnection *connection) +{ + long res; + + _dbus_return_val_if_fail (connection != NULL, 0); + + CONNECTION_LOCK (connection); + res = _dbus_transport_get_max_received_unix_fds (connection->transport); + CONNECTION_UNLOCK (connection); + return res; +} + +/** + * Gets the approximate size in bytes of all messages in the outgoing + * message queue. The size is approximate in that you shouldn't use + * it to decide how many bytes to read off the network or anything + * of that nature, as optimizations may choose to tell small white lies + * to avoid performance overhead. + * + * @param connection the connection + * @returns the number of bytes that have been queued up but not sent + */ +long +dbus_connection_get_outgoing_size (DBusConnection *connection) +{ + long res; + + _dbus_return_val_if_fail (connection != NULL, 0); + + CONNECTION_LOCK (connection); + res = _dbus_counter_get_size_value (connection->outgoing_counter); + CONNECTION_UNLOCK (connection); + return res; +} + +#ifdef DBUS_ENABLE_STATS +void +_dbus_connection_get_stats (DBusConnection *connection, + dbus_uint32_t *in_messages, + dbus_uint32_t *in_bytes, + dbus_uint32_t *in_fds, + dbus_uint32_t *in_peak_bytes, + dbus_uint32_t *in_peak_fds, + dbus_uint32_t *out_messages, + dbus_uint32_t *out_bytes, + dbus_uint32_t *out_fds, + dbus_uint32_t *out_peak_bytes, + dbus_uint32_t *out_peak_fds) +{ + CONNECTION_LOCK (connection); + + if (in_messages != NULL) + *in_messages = connection->n_incoming; + + _dbus_transport_get_stats (connection->transport, + in_bytes, in_fds, in_peak_bytes, in_peak_fds); + + if (out_messages != NULL) + *out_messages = connection->n_outgoing; + + if (out_bytes != NULL) + *out_bytes = _dbus_counter_get_size_value (connection->outgoing_counter); + + if (out_fds != NULL) + *out_fds = _dbus_counter_get_unix_fd_value (connection->outgoing_counter); + + if (out_peak_bytes != NULL) + *out_peak_bytes = _dbus_counter_get_peak_size_value (connection->outgoing_counter); + + if (out_peak_fds != NULL) + *out_peak_fds = _dbus_counter_get_peak_unix_fd_value (connection->outgoing_counter); + + CONNECTION_UNLOCK (connection); +} +#endif /* DBUS_ENABLE_STATS */ + +/** + * Gets the approximate number of uni fds of all messages in the + * outgoing message queue. + * + * @param connection the connection + * @returns the number of unix fds that have been queued up but not sent + */ +long +dbus_connection_get_outgoing_unix_fds (DBusConnection *connection) +{ + long res; + + _dbus_return_val_if_fail (connection != NULL, 0); + + CONNECTION_LOCK (connection); + res = _dbus_counter_get_unix_fd_value (connection->outgoing_counter); + CONNECTION_UNLOCK (connection); + return res; +} + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +/** + * Returns the address of the transport object of this connection + * + * @param connection the connection + * @returns the address string + */ +const char* +_dbus_connection_get_address (DBusConnection *connection) +{ + return _dbus_transport_get_address (connection->transport); +} +#endif + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-connection.h b/src/3rdparty/libdbus/dbus/dbus-connection.h new file mode 100644 index 00000000..b79fda83 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-connection.h @@ -0,0 +1,531 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-connection.h DBusConnection object + * + * Copyright (C) 2002, 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_CONNECTION_H +#define DBUS_CONNECTION_H + +#include <dbus/dbus-errors.h> +#include <dbus/dbus-macros.h> +#include <dbus/dbus-memory.h> +#include <dbus/dbus-message.h> +#include <dbus/dbus-shared.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusConnection + * @{ + */ + +/* documented in dbus-watch.c */ +typedef struct DBusWatch DBusWatch; +/* documented in dbus-timeout.c */ +typedef struct DBusTimeout DBusTimeout; +/** Opaque type representing preallocated resources so a message can be sent without further memory allocation. */ +typedef struct DBusPreallocatedSend DBusPreallocatedSend; +/** Opaque type representing a method call that has not yet received a reply. */ +typedef struct DBusPendingCall DBusPendingCall; +/** Opaque type representing a connection to a remote application and associated incoming/outgoing message queues. */ +typedef struct DBusConnection DBusConnection; +/** Set of functions that must be implemented to handle messages sent to a particular object path. */ +typedef struct DBusObjectPathVTable DBusObjectPathVTable; + +/** + * Indicates the status of a #DBusWatch. + */ +typedef enum +{ + DBUS_WATCH_READABLE = 1 << 0, /**< As in POLLIN */ + DBUS_WATCH_WRITABLE = 1 << 1, /**< As in POLLOUT */ + DBUS_WATCH_ERROR = 1 << 2, /**< As in POLLERR (can't watch for + * this, but can be present in + * current state passed to + * dbus_watch_handle()). + */ + DBUS_WATCH_HANGUP = 1 << 3 /**< As in POLLHUP (can't watch for + * it, but can be present in current + * state passed to + * dbus_watch_handle()). + */ + /* Internal to libdbus, there is also _DBUS_WATCH_NVAL in dbus-watch.h */ +} DBusWatchFlags; + +/** + * Indicates the status of incoming data on a #DBusConnection. This determines whether + * dbus_connection_dispatch() needs to be called. + */ +typedef enum +{ + DBUS_DISPATCH_DATA_REMAINS, /**< There is more data to potentially convert to messages. */ + DBUS_DISPATCH_COMPLETE, /**< All currently available data has been processed. */ + DBUS_DISPATCH_NEED_MEMORY /**< More memory is needed to continue. */ +} DBusDispatchStatus; + +/** Called when libdbus needs a new watch to be monitored by the main + * loop. Returns #FALSE if it lacks enough memory to add the + * watch. Set by dbus_connection_set_watch_functions() or + * dbus_server_set_watch_functions(). + */ +typedef dbus_bool_t (* DBusAddWatchFunction) (DBusWatch *watch, + void *data); +/** Called when dbus_watch_get_enabled() may return a different value + * than it did before. Set by dbus_connection_set_watch_functions() + * or dbus_server_set_watch_functions(). + */ +typedef void (* DBusWatchToggledFunction) (DBusWatch *watch, + void *data); +/** Called when libdbus no longer needs a watch to be monitored by the + * main loop. Set by dbus_connection_set_watch_functions() or + * dbus_server_set_watch_functions(). + */ +typedef void (* DBusRemoveWatchFunction) (DBusWatch *watch, + void *data); +/** Called when libdbus needs a new timeout to be monitored by the main + * loop. Returns #FALSE if it lacks enough memory to add the + * watch. Set by dbus_connection_set_timeout_functions() or + * dbus_server_set_timeout_functions(). + */ +typedef dbus_bool_t (* DBusAddTimeoutFunction) (DBusTimeout *timeout, + void *data); +/** Called when dbus_timeout_get_enabled() may return a different + * value than it did before. + * Set by dbus_connection_set_timeout_functions() or + * dbus_server_set_timeout_functions(). + */ +typedef void (* DBusTimeoutToggledFunction) (DBusTimeout *timeout, + void *data); +/** Called when libdbus no longer needs a timeout to be monitored by the + * main loop. Set by dbus_connection_set_timeout_functions() or + * dbus_server_set_timeout_functions(). + */ +typedef void (* DBusRemoveTimeoutFunction) (DBusTimeout *timeout, + void *data); +/** Called when the return value of dbus_connection_get_dispatch_status() + * may have changed. Set with dbus_connection_set_dispatch_status_function(). + */ +typedef void (* DBusDispatchStatusFunction) (DBusConnection *connection, + DBusDispatchStatus new_status, + void *data); +/** + * Called when the main loop's thread should be notified that there's now work + * to do. Set with dbus_connection_set_wakeup_main_function(). + */ +typedef void (* DBusWakeupMainFunction) (void *data); + +/** + * Called during authentication to check whether the given UNIX user + * ID is allowed to connect, if the client tried to auth as a UNIX + * user ID. Normally on Windows this would never happen. Set with + * dbus_connection_set_unix_user_function(). + */ +typedef dbus_bool_t (* DBusAllowUnixUserFunction) (DBusConnection *connection, + unsigned long uid, + void *data); + +/** + * Called during authentication to check whether the given Windows user + * ID is allowed to connect, if the client tried to auth as a Windows + * user ID. Normally on UNIX this would never happen. Set with + * dbus_connection_set_windows_user_function(). + */ +typedef dbus_bool_t (* DBusAllowWindowsUserFunction) (DBusConnection *connection, + const char *user_sid, + void *data); + + +/** + * Called when a pending call now has a reply available. Set with + * dbus_pending_call_set_notify(). + */ +typedef void (* DBusPendingCallNotifyFunction) (DBusPendingCall *pending, + void *user_data); + +/** + * Called when a message needs to be handled. The result indicates whether or + * not more handlers should be run. Set with dbus_connection_add_filter(). + */ +typedef DBusHandlerResult (* DBusHandleMessageFunction) (DBusConnection *connection, + DBusMessage *message, + void *user_data); +DBUS_EXPORT +DBusConnection* dbus_connection_open (const char *address, + DBusError *error); +DBUS_EXPORT +DBusConnection* dbus_connection_open_private (const char *address, + DBusError *error); +DBUS_EXPORT +DBusConnection* dbus_connection_ref (DBusConnection *connection); +DBUS_EXPORT +void dbus_connection_unref (DBusConnection *connection); +DBUS_EXPORT +void dbus_connection_close (DBusConnection *connection); +DBUS_EXPORT +dbus_bool_t dbus_connection_get_is_connected (DBusConnection *connection); +DBUS_EXPORT +dbus_bool_t dbus_connection_get_is_authenticated (DBusConnection *connection); +DBUS_EXPORT +dbus_bool_t dbus_connection_get_is_anonymous (DBusConnection *connection); +DBUS_EXPORT +char* dbus_connection_get_server_id (DBusConnection *connection); +DBUS_EXPORT +dbus_bool_t dbus_connection_can_send_type (DBusConnection *connection, + int type); + +DBUS_EXPORT +void dbus_connection_set_exit_on_disconnect (DBusConnection *connection, + dbus_bool_t exit_on_disconnect); +DBUS_EXPORT +void dbus_connection_flush (DBusConnection *connection); +DBUS_EXPORT +dbus_bool_t dbus_connection_read_write_dispatch (DBusConnection *connection, + int timeout_milliseconds); +DBUS_EXPORT +dbus_bool_t dbus_connection_read_write (DBusConnection *connection, + int timeout_milliseconds); +DBUS_EXPORT +DBusMessage* dbus_connection_borrow_message (DBusConnection *connection); +DBUS_EXPORT +void dbus_connection_return_message (DBusConnection *connection, + DBusMessage *message); +DBUS_EXPORT +void dbus_connection_steal_borrowed_message (DBusConnection *connection, + DBusMessage *message); +DBUS_EXPORT +DBusMessage* dbus_connection_pop_message (DBusConnection *connection); +DBUS_EXPORT +DBusDispatchStatus dbus_connection_get_dispatch_status (DBusConnection *connection); +DBUS_EXPORT +DBusDispatchStatus dbus_connection_dispatch (DBusConnection *connection); +DBUS_EXPORT +dbus_bool_t dbus_connection_has_messages_to_send (DBusConnection *connection); +DBUS_EXPORT +dbus_bool_t dbus_connection_send (DBusConnection *connection, + DBusMessage *message, + dbus_uint32_t *client_serial); +DBUS_EXPORT +dbus_bool_t dbus_connection_send_with_reply (DBusConnection *connection, + DBusMessage *message, + DBusPendingCall **pending_return, + int timeout_milliseconds); +DBUS_EXPORT +DBusMessage * dbus_connection_send_with_reply_and_block (DBusConnection *connection, + DBusMessage *message, + int timeout_milliseconds, + DBusError *error); +DBUS_EXPORT +dbus_bool_t dbus_connection_set_watch_functions (DBusConnection *connection, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + DBusWatchToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +dbus_bool_t dbus_connection_set_timeout_functions (DBusConnection *connection, + DBusAddTimeoutFunction add_function, + DBusRemoveTimeoutFunction remove_function, + DBusTimeoutToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +void dbus_connection_set_wakeup_main_function (DBusConnection *connection, + DBusWakeupMainFunction wakeup_main_function, + void *data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +void dbus_connection_set_dispatch_status_function (DBusConnection *connection, + DBusDispatchStatusFunction function, + void *data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +dbus_bool_t dbus_connection_get_unix_user (DBusConnection *connection, + unsigned long *uid); +DBUS_EXPORT +dbus_bool_t dbus_connection_get_unix_process_id (DBusConnection *connection, + unsigned long *pid); +DBUS_EXPORT +dbus_bool_t dbus_connection_get_adt_audit_session_data (DBusConnection *connection, + void **data, + dbus_int32_t *data_size); +DBUS_EXPORT +void dbus_connection_set_unix_user_function (DBusConnection *connection, + DBusAllowUnixUserFunction function, + void *data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +dbus_bool_t dbus_connection_get_windows_user (DBusConnection *connection, + char **windows_sid_p); +DBUS_EXPORT +void dbus_connection_set_windows_user_function (DBusConnection *connection, + DBusAllowWindowsUserFunction function, + void *data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +void dbus_connection_set_allow_anonymous (DBusConnection *connection, + dbus_bool_t value); +DBUS_EXPORT +void dbus_connection_set_builtin_filters_enabled (DBusConnection *connection, + dbus_bool_t value); +DBUS_EXPORT +void dbus_connection_set_route_peer_messages (DBusConnection *connection, + dbus_bool_t value); + + +/* Filters */ + +DBUS_EXPORT +dbus_bool_t dbus_connection_add_filter (DBusConnection *connection, + DBusHandleMessageFunction function, + void *user_data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +void dbus_connection_remove_filter (DBusConnection *connection, + DBusHandleMessageFunction function, + void *user_data); + + +/* Other */ +DBUS_EXPORT +dbus_bool_t dbus_connection_allocate_data_slot (dbus_int32_t *slot_p); +DBUS_EXPORT +void dbus_connection_free_data_slot (dbus_int32_t *slot_p); +DBUS_EXPORT +dbus_bool_t dbus_connection_set_data (DBusConnection *connection, + dbus_int32_t slot, + void *data, + DBusFreeFunction free_data_func); +DBUS_EXPORT +void* dbus_connection_get_data (DBusConnection *connection, + dbus_int32_t slot); + +DBUS_EXPORT +void dbus_connection_set_change_sigpipe (dbus_bool_t will_modify_sigpipe); + +DBUS_EXPORT +void dbus_connection_set_max_message_size (DBusConnection *connection, + long size); +DBUS_EXPORT +long dbus_connection_get_max_message_size (DBusConnection *connection); +DBUS_EXPORT +void dbus_connection_set_max_received_size (DBusConnection *connection, + long size); +DBUS_EXPORT +long dbus_connection_get_max_received_size (DBusConnection *connection); + +DBUS_EXPORT +void dbus_connection_set_max_message_unix_fds (DBusConnection *connection, + long n); +DBUS_EXPORT +long dbus_connection_get_max_message_unix_fds (DBusConnection *connection); +DBUS_EXPORT +void dbus_connection_set_max_received_unix_fds(DBusConnection *connection, + long n); +DBUS_EXPORT +long dbus_connection_get_max_received_unix_fds(DBusConnection *connection); + +DBUS_EXPORT +long dbus_connection_get_outgoing_size (DBusConnection *connection); +DBUS_EXPORT +long dbus_connection_get_outgoing_unix_fds (DBusConnection *connection); + +DBUS_EXPORT +DBusPreallocatedSend* dbus_connection_preallocate_send (DBusConnection *connection); +DBUS_EXPORT +void dbus_connection_free_preallocated_send (DBusConnection *connection, + DBusPreallocatedSend *preallocated); +DBUS_EXPORT +void dbus_connection_send_preallocated (DBusConnection *connection, + DBusPreallocatedSend *preallocated, + DBusMessage *message, + dbus_uint32_t *client_serial); + + +/* Object tree functionality */ + +/** + * Called when a #DBusObjectPathVTable is unregistered (or its connection is freed). + * Found in #DBusObjectPathVTable. + */ +typedef void (* DBusObjectPathUnregisterFunction) (DBusConnection *connection, + void *user_data); +/** + * Called when a message is sent to a registered object path. Found in + * #DBusObjectPathVTable which is registered with dbus_connection_register_object_path() + * or dbus_connection_register_fallback(). + */ +typedef DBusHandlerResult (* DBusObjectPathMessageFunction) (DBusConnection *connection, + DBusMessage *message, + void *user_data); + +/** + * Virtual table that must be implemented to handle a portion of the + * object path hierarchy. Attach the vtable to a particular path using + * dbus_connection_register_object_path() or + * dbus_connection_register_fallback(). + */ +struct DBusObjectPathVTable +{ + DBusObjectPathUnregisterFunction unregister_function; /**< Function to unregister this handler */ + DBusObjectPathMessageFunction message_function; /**< Function to handle messages */ + + void (* dbus_internal_pad1) (void *); /**< Reserved for future expansion */ + void (* dbus_internal_pad2) (void *); /**< Reserved for future expansion */ + void (* dbus_internal_pad3) (void *); /**< Reserved for future expansion */ + void (* dbus_internal_pad4) (void *); /**< Reserved for future expansion */ +}; + +DBUS_EXPORT +dbus_bool_t dbus_connection_try_register_object_path (DBusConnection *connection, + const char *path, + const DBusObjectPathVTable *vtable, + void *user_data, + DBusError *error); + +DBUS_EXPORT +dbus_bool_t dbus_connection_register_object_path (DBusConnection *connection, + const char *path, + const DBusObjectPathVTable *vtable, + void *user_data); + +DBUS_EXPORT +dbus_bool_t dbus_connection_try_register_fallback (DBusConnection *connection, + const char *path, + const DBusObjectPathVTable *vtable, + void *user_data, + DBusError *error); + +DBUS_EXPORT +dbus_bool_t dbus_connection_register_fallback (DBusConnection *connection, + const char *path, + const DBusObjectPathVTable *vtable, + void *user_data); +DBUS_EXPORT +dbus_bool_t dbus_connection_unregister_object_path (DBusConnection *connection, + const char *path); + +DBUS_EXPORT +dbus_bool_t dbus_connection_get_object_path_data (DBusConnection *connection, + const char *path, + void **data_p); + +DBUS_EXPORT +dbus_bool_t dbus_connection_list_registered (DBusConnection *connection, + const char *parent_path, + char ***child_entries); + +DBUS_EXPORT +dbus_bool_t dbus_connection_get_unix_fd (DBusConnection *connection, + int *fd); +DBUS_EXPORT +dbus_bool_t dbus_connection_get_socket (DBusConnection *connection, + int *fd); + +/** + * Clear a variable or struct member that contains a #DBusConnection. + * If it does not contain #NULL, the connection that was previously + * there is unreferenced with dbus_connection_unref(). + * + * For example, this function and the similar functions for + * other reference-counted types can be used in code like this: + * + * @code + * DBusConnection *conn = NULL; + * struct { ...; DBusMessage *m; ... } *larger_structure = ...; + * + * ... code that might set conn or m to be non-NULL ... + * + * dbus_clear_connection (&conn); + * dbus_clear_message (&larger_structure->m); + * @endcode + * + * @param pointer_to_connection A pointer to a variable or struct member. + * pointer_to_connection must not be #NULL, but *pointer_to_connection + * may be #NULL. + */ +static inline void +dbus_clear_connection (DBusConnection **pointer_to_connection) +{ + _dbus_clear_pointer_impl (DBusConnection, pointer_to_connection, + dbus_connection_unref); +} + +/** @} */ + + +/** + * @addtogroup DBusWatch + * @{ + */ + +#ifndef DBUS_DISABLE_DEPRECATED +DBUS_EXPORT +DBUS_DEPRECATED int dbus_watch_get_fd (DBusWatch *watch); +#endif + +DBUS_EXPORT +int dbus_watch_get_unix_fd (DBusWatch *watch); +DBUS_EXPORT +int dbus_watch_get_socket (DBusWatch *watch); +DBUS_EXPORT +unsigned int dbus_watch_get_flags (DBusWatch *watch); +DBUS_EXPORT +void* dbus_watch_get_data (DBusWatch *watch); +DBUS_EXPORT +void dbus_watch_set_data (DBusWatch *watch, + void *data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +dbus_bool_t dbus_watch_handle (DBusWatch *watch, + unsigned int flags); +DBUS_EXPORT +dbus_bool_t dbus_watch_get_enabled (DBusWatch *watch); + +/** @} */ + +/** + * @addtogroup DBusTimeout + * @{ + */ + +DBUS_EXPORT +int dbus_timeout_get_interval (DBusTimeout *timeout); +DBUS_EXPORT +void* dbus_timeout_get_data (DBusTimeout *timeout); +DBUS_EXPORT +void dbus_timeout_set_data (DBusTimeout *timeout, + void *data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +dbus_bool_t dbus_timeout_handle (DBusTimeout *timeout); +DBUS_EXPORT +dbus_bool_t dbus_timeout_get_enabled (DBusTimeout *timeout); + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_CONNECTION_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-credentials.c b/src/3rdparty/libdbus/dbus/dbus-credentials.c new file mode 100644 index 00000000..44cd40bf --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-credentials.c @@ -0,0 +1,835 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-credentials.c Credentials provable through authentication + * + * Copyright (C) 2007 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <config.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_SYSCALL_H +#include <sys/syscall.h> +#endif +#include "dbus-credentials.h" +#include "dbus-internals.h" +#ifdef DBUS_UNIX +#include "dbus-sysdeps-unix.h" +#endif + +/** + * @defgroup DBusCredentials Credentials provable through authentication + * @ingroup DBusInternals + * @brief DBusCredentials object + * + * Credentials are what you have to prove you have in order to + * authenticate. The main credentials right now are a unix user + * account, a Windows user account, or a UNIX process ID. + */ + +/** + * @defgroup DBusCredentialsInternals Credentials implementation details + * @ingroup DBusInternals + * @brief DBusCredentials implementation details + * + * Private details of credentials code. + * + * @{ + */ + +struct DBusCredentials { + int refcount; + dbus_uid_t unix_uid; + dbus_gid_t *unix_gids; + size_t n_unix_gids; + dbus_pid_t pid; + int pid_fd; + char *windows_sid; + char *linux_security_label; + void *adt_audit_data; + dbus_int32_t adt_audit_data_size; +}; + +/** @} */ + +/** + * @addtogroup DBusCredentials + * @{ + */ + +/** + * Creates a new credentials object. + * + * @returns the new object or #NULL if no memory + */ +DBusCredentials* +_dbus_credentials_new (void) +{ + DBusCredentials *creds; + + creds = dbus_new (DBusCredentials, 1); + if (creds == NULL) + return NULL; + + creds->refcount = 1; + creds->unix_uid = DBUS_UID_UNSET; + creds->unix_gids = NULL; + creds->n_unix_gids = 0; + creds->pid = DBUS_PID_UNSET; + creds->pid_fd = -1; + creds->windows_sid = NULL; + creds->linux_security_label = NULL; + creds->adt_audit_data = NULL; + creds->adt_audit_data_size = 0; + + return creds; +} + +/** + * Creates a new object with the most important credentials (user ID and process ID) from the current process. + * @returns the new object or #NULL if no memory + */ +DBusCredentials* +_dbus_credentials_new_from_current_process (void) +{ + DBusCredentials *creds; + + creds = _dbus_credentials_new (); + if (creds == NULL) + return NULL; + + if (!_dbus_credentials_add_from_current_process (creds)) + { + _dbus_credentials_unref (creds); + return NULL; + } + + return creds; +} + +/** + * Increment refcount on credentials. + * + * @param credentials the object + */ +void +_dbus_credentials_ref (DBusCredentials *credentials) +{ + _dbus_assert (credentials->refcount > 0); + credentials->refcount += 1; +} + +/** + * Decrement refcount on credentials. + * + * @param credentials the object + */ +void +_dbus_credentials_unref (DBusCredentials *credentials) +{ + _dbus_assert (credentials->refcount > 0); + + credentials->refcount -= 1; + if (credentials->refcount == 0) + { + dbus_free (credentials->unix_gids); + dbus_free (credentials->windows_sid); + dbus_free (credentials->linux_security_label); + dbus_free (credentials->adt_audit_data); +#ifdef DBUS_UNIX + if (credentials->pid_fd >= 0) + { + close (credentials->pid_fd); + credentials->pid_fd = -1; + } +#endif + dbus_free (credentials); + } +} + +/** + * Add a UNIX process ID to the credentials. If the + * process ID FD is set, it will always take + * precendence when querying the PID of this + * credential. + * + * @param credentials the object + * @param pid the process ID + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_credentials_add_pid (DBusCredentials *credentials, + dbus_pid_t pid) +{ + credentials->pid = pid; + return TRUE; +} + +/** + * Add a UNIX process ID FD to the credentials. The + * FD is now owned by the credentials object. + * + * @param credentials the object + * @param pid_fd the process ID FD + * @returns #FALSE if no memory + */ +#ifndef DBUS_UNIX +_DBUS_GNUC_NORETURN +#endif +void +_dbus_credentials_take_pid_fd (DBusCredentials *credentials, + int pid_fd) +{ +#ifdef DBUS_UNIX + if (credentials->pid_fd >= 0) + close (credentials->pid_fd); + credentials->pid_fd = pid_fd; +#else + _dbus_assert_not_reached ("pidfd never set on non-Unix"); +#endif +} + +/** + * Add a UNIX user ID to the credentials. + * + * @param credentials the object + * @param uid the user ID + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_credentials_add_unix_uid(DBusCredentials *credentials, + dbus_uid_t uid) +{ + credentials->unix_uid = uid; + return TRUE; + +} + +static int +cmp_gidp (const void *a_, const void *b_) +{ + const dbus_gid_t *a = a_; + const dbus_gid_t *b = b_; + + if (*a < *b) + return -1; + + if (*a > *b) + return 1; + + return 0; +} + +/** + * Add UNIX group IDs to the credentials, replacing any group IDs that + * might already have been present. + * + * @param credentials the object + * @param gids the group IDs, which will be freed by the DBusCredentials object + * @param n_gids the number of group IDs + */ +void +_dbus_credentials_take_unix_gids (DBusCredentials *credentials, + dbus_gid_t *gids, + size_t n_gids) +{ + /* So we can compare arrays via a simple memcmp */ + qsort (gids, n_gids, sizeof (dbus_gid_t), cmp_gidp); + + dbus_free (credentials->unix_gids); + credentials->unix_gids = gids; + credentials->n_unix_gids = n_gids; +} + +/** + * Get the Unix group IDs. + * + * @param credentials the object + * @param gids the group IDs, which will be freed by the DBusCredentials object + * @param n_gids the number of group IDs + */ +dbus_bool_t +_dbus_credentials_get_unix_gids (DBusCredentials *credentials, + const dbus_gid_t **gids, + size_t *n_gids) +{ + if (gids != NULL) + *gids = credentials->unix_gids; + + if (n_gids != NULL) + *n_gids = credentials->n_unix_gids; + + return (credentials->unix_gids != NULL); +} + +/** + * Add a Windows user SID to the credentials. + * + * @param credentials the object + * @param windows_sid the user SID + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_credentials_add_windows_sid (DBusCredentials *credentials, + const char *windows_sid) +{ + char *copy; + + copy = _dbus_strdup (windows_sid); + if (copy == NULL) + return FALSE; + + dbus_free (credentials->windows_sid); + credentials->windows_sid = copy; + + return TRUE; +} + +/** + * Add a Linux security label, as used by LSMs such as SELinux, Smack and + * AppArmor, to the credentials. + * + * @param credentials the object + * @param label the label + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_credentials_add_linux_security_label (DBusCredentials *credentials, + const char *label) +{ + char *copy; + + copy = _dbus_strdup (label); + if (copy == NULL) + return FALSE; + + dbus_free (credentials->linux_security_label); + credentials->linux_security_label = copy; + + return TRUE; +} + +/** + * Add ADT audit data to the credentials. + * + * @param credentials the object + * @param audit_data the audit data + * @param size the length of audit data + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_credentials_add_adt_audit_data (DBusCredentials *credentials, + void *audit_data, + dbus_int32_t size) +{ + void *copy; + copy = _dbus_memdup (audit_data, size); + if (copy == NULL) + return FALSE; + + dbus_free (credentials->adt_audit_data); + credentials->adt_audit_data = copy; + credentials->adt_audit_data_size = size; + + return TRUE; +} + +/** + * Checks whether the given credential is present. + * + * @param credentials the object + * @param type the credential to check for + * @returns #TRUE if the credential is present + */ +dbus_bool_t +_dbus_credentials_include (DBusCredentials *credentials, + DBusCredentialType type) +{ + switch (type) + { + case DBUS_CREDENTIAL_UNIX_PROCESS_ID: + return credentials->pid != DBUS_PID_UNSET || + credentials->pid_fd >= 0; + case DBUS_CREDENTIAL_UNIX_PROCESS_FD: + return credentials->pid_fd >= 0; + case DBUS_CREDENTIAL_UNIX_USER_ID: + return credentials->unix_uid != DBUS_UID_UNSET; + case DBUS_CREDENTIAL_UNIX_GROUP_IDS: + return credentials->unix_gids != NULL; + case DBUS_CREDENTIAL_WINDOWS_SID: + return credentials->windows_sid != NULL; + case DBUS_CREDENTIAL_LINUX_SECURITY_LABEL: + return credentials->linux_security_label != NULL; + case DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID: + return credentials->adt_audit_data != NULL; + default: + _dbus_assert_not_reached ("Unknown credential enum value"); + return FALSE; + } +} + +/** + * Gets the UNIX process ID in the credentials, or #DBUS_PID_UNSET if + * the credentials object doesn't contain a process ID. + * If the PID FD is set, it will first try to resolve from it, and + * only return the stored PID if that fails. + * + * @param credentials the object + * @returns UNIX process ID + */ +dbus_pid_t +_dbus_credentials_get_pid (DBusCredentials *credentials) +{ +#ifdef DBUS_UNIX + dbus_pid_t pid; + + if (credentials->pid_fd >= 0) + { + pid = _dbus_resolve_pid_fd (credentials->pid_fd); + if (pid > 0) + return pid; + } +#endif + + return credentials->pid; +} + +/** + * Gets the UNIX process ID FD in the credentials as obtained by 'safe' + * means (e.g.: Linux's SO_PEERPIDFD), or -1 if the credentials object + * doesn't contain a process ID FD. The file FD is owned by the credentials + * object and must not be closed by the caller. + * + * @param credentials the object + * @returns UNIX process ID FD + */ +int +_dbus_credentials_get_pid_fd (DBusCredentials *credentials) +{ + return credentials->pid_fd; +} + +/** + * Gets the UNIX user ID in the credentials, or #DBUS_UID_UNSET if + * the credentials object doesn't contain a user ID. + * + * @param credentials the object + * @returns UNIX user ID + */ +dbus_uid_t +_dbus_credentials_get_unix_uid (DBusCredentials *credentials) +{ + return credentials->unix_uid; +} + +/** + * Gets the Windows user SID in the credentials, or #NULL if + * the credentials object doesn't contain a Windows user SID. + * + * @param credentials the object + * @returns Windows user SID + */ +const char* +_dbus_credentials_get_windows_sid (DBusCredentials *credentials) +{ + return credentials->windows_sid; +} + +/** + * Gets the Linux security label (as used by LSMs) from the credentials, + * or #NULL if the credentials object doesn't contain a security label. + * + * @param credentials the object + * @returns the security label + */ +const char * +_dbus_credentials_get_linux_security_label (DBusCredentials *credentials) +{ + return credentials->linux_security_label; +} + +/** + * Gets the ADT audit data in the credentials, or #NULL if + * the credentials object doesn't contain ADT audit data. + * + * @param credentials the object + * @returns Solaris ADT audit data + */ +void * +_dbus_credentials_get_adt_audit_data (DBusCredentials *credentials) +{ + return credentials->adt_audit_data; +} + +/** + * Gets the ADT audit data size in the credentials, or 0 if + * the credentials object doesn't contain ADT audit data. + * + * @param credentials the object + * @returns Solaris ADT audit data size + */ +dbus_int32_t +_dbus_credentials_get_adt_audit_data_size (DBusCredentials *credentials) +{ + return credentials->adt_audit_data_size; +} + +/** + * Checks whether the first credentials object contains + * all the credentials found in the second credentials object. + * + * @param credentials the object + * @param possible_subset see if credentials in here are also in the first arg + * @returns #TRUE if second arg is contained in first + */ +dbus_bool_t +_dbus_credentials_are_superset (DBusCredentials *credentials, + DBusCredentials *possible_subset) +{ + return + (possible_subset->pid == DBUS_PID_UNSET || + possible_subset->pid == credentials->pid) && + (possible_subset->unix_uid == DBUS_UID_UNSET || + possible_subset->unix_uid == credentials->unix_uid) && + (possible_subset->unix_gids == NULL || + (possible_subset->n_unix_gids == credentials->n_unix_gids && + memcmp (possible_subset->unix_gids, credentials->unix_gids, + sizeof (dbus_gid_t) * credentials->n_unix_gids) == 0)) && + (possible_subset->windows_sid == NULL || + (credentials->windows_sid && strcmp (possible_subset->windows_sid, + credentials->windows_sid) == 0)) && + (possible_subset->linux_security_label == NULL || + (credentials->linux_security_label != NULL && + strcmp (possible_subset->linux_security_label, + credentials->linux_security_label) == 0)) && + (possible_subset->adt_audit_data == NULL || + (credentials->adt_audit_data && memcmp (possible_subset->adt_audit_data, + credentials->adt_audit_data, + credentials->adt_audit_data_size) == 0)); +} + +/** + * Checks whether a credentials object contains anything. + * + * @param credentials the object + * @returns #TRUE if there are no credentials in the object + */ +dbus_bool_t +_dbus_credentials_are_empty (DBusCredentials *credentials) +{ + return + credentials->pid == DBUS_PID_UNSET && + credentials->pid_fd == -1 && + credentials->unix_uid == DBUS_UID_UNSET && + credentials->unix_gids == NULL && + credentials->n_unix_gids == 0 && + credentials->windows_sid == NULL && + credentials->linux_security_label == NULL && + credentials->adt_audit_data == NULL; +} + +/** + * Checks whether a credentials object contains a user identity. + * + * @param credentials the object + * @returns #TRUE if there are no user identities in the object + */ +dbus_bool_t +_dbus_credentials_are_anonymous (DBusCredentials *credentials) +{ + return + credentials->unix_uid == DBUS_UID_UNSET && + credentials->windows_sid == NULL; +} + +/** + * Merge all credentials found in the second object into the first object, + * overwriting the first object if there are any overlaps. + * + * @param credentials the object + * @param other_credentials credentials to merge + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_credentials_add_credentials (DBusCredentials *credentials, + DBusCredentials *other_credentials) +{ + return + _dbus_credentials_add_credential (credentials, + DBUS_CREDENTIAL_UNIX_PROCESS_FD, + other_credentials) && + _dbus_credentials_add_credential (credentials, + DBUS_CREDENTIAL_UNIX_PROCESS_ID, + other_credentials) && + _dbus_credentials_add_credential (credentials, + DBUS_CREDENTIAL_UNIX_USER_ID, + other_credentials) && + _dbus_credentials_add_credential (credentials, + DBUS_CREDENTIAL_UNIX_GROUP_IDS, + other_credentials) && + _dbus_credentials_add_credential (credentials, + DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID, + other_credentials) && + _dbus_credentials_add_credential (credentials, + DBUS_CREDENTIAL_LINUX_SECURITY_LABEL, + other_credentials) && + _dbus_credentials_add_credential (credentials, + DBUS_CREDENTIAL_WINDOWS_SID, + other_credentials); +} + +/** + * Merge the given credential found in the second object into the first object, + * overwriting the first object's value for that credential. + * + * Does nothing if the second object does not contain the specified credential. + * i.e., will never delete a credential from the first object. + * + * @param credentials the object + * @param which the credential to overwrite + * @param other_credentials credentials to merge + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_credentials_add_credential (DBusCredentials *credentials, + DBusCredentialType which, + DBusCredentials *other_credentials) +{ + if (which == DBUS_CREDENTIAL_UNIX_PROCESS_ID && + other_credentials->pid != DBUS_PID_UNSET) + { + if (!_dbus_credentials_add_pid (credentials, other_credentials->pid)) + return FALSE; + } + else if (which == DBUS_CREDENTIAL_UNIX_USER_ID && + other_credentials->unix_uid != DBUS_UID_UNSET) + { + if (!_dbus_credentials_add_unix_uid (credentials, other_credentials->unix_uid)) + return FALSE; + } + else if (which == DBUS_CREDENTIAL_UNIX_GROUP_IDS && + other_credentials->unix_gids != NULL) + { + dbus_gid_t *gids; + + gids = dbus_new (dbus_gid_t, other_credentials->n_unix_gids); + + if (gids == NULL) + return FALSE; + + memcpy (gids, other_credentials->unix_gids, + sizeof (dbus_gid_t) * other_credentials->n_unix_gids); + + _dbus_credentials_take_unix_gids (credentials, gids, + other_credentials->n_unix_gids); + } + else if (which == DBUS_CREDENTIAL_WINDOWS_SID && + other_credentials->windows_sid != NULL) + { + if (!_dbus_credentials_add_windows_sid (credentials, other_credentials->windows_sid)) + return FALSE; + } + else if (which == DBUS_CREDENTIAL_LINUX_SECURITY_LABEL && + other_credentials->linux_security_label != NULL) + { + if (!_dbus_credentials_add_linux_security_label (credentials, + other_credentials->linux_security_label)) + return FALSE; + } + else if (which == DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID && + other_credentials->adt_audit_data != NULL) + { + if (!_dbus_credentials_add_adt_audit_data (credentials, other_credentials->adt_audit_data, other_credentials->adt_audit_data_size)) + return FALSE; + } + /* _dbus_dup() is only available on UNIX platforms. */ +#ifdef DBUS_UNIX + else if (which == DBUS_CREDENTIAL_UNIX_PROCESS_FD && + other_credentials->pid_fd >= 0) + { + int pid_fd = _dbus_dup (other_credentials->pid_fd, NULL); + + if (pid_fd < 0) + return FALSE; + + _dbus_credentials_take_pid_fd (credentials, pid_fd); + } +#endif + + return TRUE; +} + +/** + * Clear all credentials in the object. + * + * @param credentials the object + */ +void +_dbus_credentials_clear (DBusCredentials *credentials) +{ + credentials->pid = DBUS_PID_UNSET; +#ifdef DBUS_UNIX + if (credentials->pid_fd >= 0) + { + close (credentials->pid_fd); + credentials->pid_fd = -1; + } +#endif + credentials->unix_uid = DBUS_UID_UNSET; + dbus_free (credentials->unix_gids); + credentials->unix_gids = NULL; + credentials->n_unix_gids = 0; + dbus_free (credentials->windows_sid); + credentials->windows_sid = NULL; + dbus_free (credentials->linux_security_label); + credentials->linux_security_label = NULL; + dbus_free (credentials->adt_audit_data); + credentials->adt_audit_data = NULL; + credentials->adt_audit_data_size = 0; +} + +/** + * Copy a credentials object. + * + * @param credentials the object + * @returns the copy or #NULL + */ +DBusCredentials* +_dbus_credentials_copy (DBusCredentials *credentials) +{ + DBusCredentials *copy; + + copy = _dbus_credentials_new (); + if (copy == NULL) + return NULL; + + if (!_dbus_credentials_add_credentials (copy, credentials)) + { + _dbus_credentials_unref (copy); + return NULL; + } + + return copy; +} + +/** + * Check whether the user-identifying credentials in two credentials + * objects are identical. Credentials that are not related to the + * user are ignored, but any kind of user ID credentials must be the + * same (UNIX user ID, Windows user SID, etc.) and present in both + * objects for the function to return #TRUE. + * + * @param credentials the object + * @param other_credentials credentials to compare + * @returns #TRUE if the two credentials refer to the same user + */ +dbus_bool_t +_dbus_credentials_same_user (DBusCredentials *credentials, + DBusCredentials *other_credentials) +{ + /* both windows and unix user must be the same (though pretty much + * in all conceivable cases, one will be unset) + */ + return credentials->unix_uid == other_credentials->unix_uid && + ((!(credentials->windows_sid || other_credentials->windows_sid)) || + (credentials->windows_sid && other_credentials->windows_sid && + strcmp (credentials->windows_sid, other_credentials->windows_sid) == 0)); +} + +/** + * Convert the credentials in this object to a human-readable + * string format, and append to the given string. + * + * @param credentials the object + * @param string append to this string + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_credentials_to_string_append (DBusCredentials *credentials, + DBusString *string) +{ + dbus_bool_t join; + + join = FALSE; + if (credentials->unix_uid != DBUS_UID_UNSET) + { + if (!_dbus_string_append_printf (string, "uid=" DBUS_UID_FORMAT, credentials->unix_uid)) + goto oom; + join = TRUE; + } + if (credentials->pid != DBUS_PID_UNSET || credentials->pid_fd >= 0) + { + if (!_dbus_string_append_printf (string, + "%spid=" DBUS_PID_FORMAT, + join ? " " : "", + _dbus_credentials_get_pid (credentials))) + goto oom; + join = TRUE; + } + + if (credentials->unix_gids != NULL) + { + size_t i; + + for (i = 0; i < credentials->n_unix_gids; i++) + { + if (!_dbus_string_append_printf (string, "%sgid=" DBUS_GID_FORMAT, + join ? " " : "", + credentials->unix_gids[i])) + goto oom; + + join = TRUE; + } + } + + if (credentials->windows_sid != NULL) + { + if (!_dbus_string_append_printf (string, "%ssid=%s", join ? " " : "", credentials->windows_sid)) + goto oom; + join = TRUE; + } + + if (credentials->linux_security_label != NULL) + { + if (!_dbus_string_append_printf (string, "%slsm='%s'", + join ? " " : "", + credentials->linux_security_label)) + goto oom; + join = TRUE; + } + + if (credentials->pid_fd >= 0) + { + if (!_dbus_string_append_printf (string, "%spidfd=%d", join ? " " : "", credentials->pid_fd)) + goto oom; + join = TRUE; + } + + return TRUE; +oom: + return FALSE; +} + +/** @} */ + +/* tests in dbus-credentials-util.c */ diff --git a/src/3rdparty/libdbus/dbus/dbus-credentials.h b/src/3rdparty/libdbus/dbus/dbus-credentials.h new file mode 100644 index 00000000..407d5cb5 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-credentials.h @@ -0,0 +1,126 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-credentials.h Credentials provable through authentication + * + * Copyright (C) 2007 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_CREDENTIALS_H +#define DBUS_CREDENTIALS_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-errors.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-sysdeps.h> + +DBUS_BEGIN_DECLS + +typedef enum { + DBUS_CREDENTIAL_UNIX_PROCESS_ID, + DBUS_CREDENTIAL_UNIX_USER_ID, + DBUS_CREDENTIAL_UNIX_GROUP_IDS, + DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID, + DBUS_CREDENTIAL_LINUX_SECURITY_LABEL, + DBUS_CREDENTIAL_WINDOWS_SID, + DBUS_CREDENTIAL_UNIX_PROCESS_FD, +} DBusCredentialType; + +DBUS_PRIVATE_EXPORT +DBusCredentials* _dbus_credentials_new_from_current_process (void); +DBUS_PRIVATE_EXPORT +DBusCredentials* _dbus_credentials_new (void); +DBUS_PRIVATE_EXPORT +void _dbus_credentials_ref (DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +void _dbus_credentials_unref (DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_credentials_add_pid (DBusCredentials *credentials, + dbus_pid_t pid); +DBUS_PRIVATE_EXPORT +void _dbus_credentials_take_pid_fd (DBusCredentials *credentials, + int pid_fd); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_credentials_add_unix_uid (DBusCredentials *credentials, + dbus_uid_t uid); +DBUS_PRIVATE_EXPORT +void _dbus_credentials_take_unix_gids (DBusCredentials *credentials, + dbus_gid_t *gids, + size_t n_gids); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_credentials_add_windows_sid (DBusCredentials *credentials, + const char *windows_sid); +dbus_bool_t _dbus_credentials_add_linux_security_label (DBusCredentials *credentials, + const char *label); +dbus_bool_t _dbus_credentials_add_adt_audit_data (DBusCredentials *credentials, + void *audit_data, + dbus_int32_t size); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_credentials_include (DBusCredentials *credentials, + DBusCredentialType type); +DBUS_PRIVATE_EXPORT +dbus_pid_t _dbus_credentials_get_pid (DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +int _dbus_credentials_get_pid_fd (DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +dbus_uid_t _dbus_credentials_get_unix_uid (DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_credentials_get_unix_gids (DBusCredentials *credentials, + const dbus_gid_t **gids, + size_t *n_gids); +DBUS_PRIVATE_EXPORT +const char* _dbus_credentials_get_windows_sid (DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +const char * _dbus_credentials_get_linux_security_label (DBusCredentials *credentials); +void * _dbus_credentials_get_adt_audit_data (DBusCredentials *credentials); +dbus_int32_t _dbus_credentials_get_adt_audit_data_size (DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_credentials_are_superset (DBusCredentials *credentials, + DBusCredentials *possible_subset); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_credentials_are_empty (DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_credentials_are_anonymous (DBusCredentials *credentials); +dbus_bool_t _dbus_credentials_add_credentials (DBusCredentials *credentials, + DBusCredentials *other_credentials); +/* must silently allow 'which' to not exist */ +dbus_bool_t _dbus_credentials_add_credential (DBusCredentials *credentials, + DBusCredentialType which, + DBusCredentials *other_credentials); +DBUS_PRIVATE_EXPORT +void _dbus_credentials_clear (DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +DBusCredentials* _dbus_credentials_copy (DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_credentials_same_user (DBusCredentials *credentials, + DBusCredentials *other_credentials); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_credentials_to_string_append (DBusCredentials *credentials, + DBusString *string); + +static inline void +_dbus_clear_credentials (DBusCredentials **pointer_to_creds) +{ + _dbus_clear_pointer_impl (DBusCredentials, pointer_to_creds, + _dbus_credentials_unref); +} + +DBUS_END_DECLS + +#endif /* DBUS_CREDENTIALS_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-dataslot.c b/src/3rdparty/libdbus/dbus/dbus-dataslot.c new file mode 100644 index 00000000..776f9278 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-dataslot.c @@ -0,0 +1,466 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-dataslot.c storing data on objects + * + * Copyright (C) 2003 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-dataslot.h" +#include "dbus-threads-internal.h" +#include <dbus/dbus-test-tap.h> + +/** + * @defgroup DBusDataSlot Data slots + * @ingroup DBusInternals + * @brief Storing data by ID + * + * Types and functions related to storing data by an + * allocated ID. This is used for dbus_connection_set_data(), + * dbus_server_set_data(), etc. + * @{ + */ + +/** + * Initializes a data slot allocator object, used to assign + * integer IDs for data slots. + * + * @param allocator the allocator to initialize + */ +dbus_bool_t +_dbus_data_slot_allocator_init (DBusDataSlotAllocator *allocator, + DBusGlobalLock lock) +{ + allocator->allocated_slots = NULL; + allocator->n_allocated_slots = 0; + allocator->n_used_slots = 0; + allocator->lock = lock; + + return TRUE; +} + +/** + * Allocates an integer ID to be used for storing data + * in a #DBusDataSlotList. If the value at *slot_id_p is + * not -1, this function just increments the refcount for + * the existing slot ID. If the value is -1, a new slot ID + * is allocated and stored at *slot_id_p. + * + * @param allocator the allocator + * @param slot_id_p address to fill with the slot ID + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_data_slot_allocator_alloc (DBusDataSlotAllocator *allocator, + dbus_int32_t *slot_id_p) +{ + dbus_int32_t slot; + + if (!_dbus_lock (allocator->lock)) + return FALSE; + + if (*slot_id_p >= 0) + { + slot = *slot_id_p; + + _dbus_assert (slot < allocator->n_allocated_slots); + _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); + + allocator->allocated_slots[slot].refcount += 1; + + goto out; + } + + _dbus_assert (*slot_id_p < 0); + + if (allocator->n_used_slots < allocator->n_allocated_slots) + { + slot = 0; + while (slot < allocator->n_allocated_slots) + { + if (allocator->allocated_slots[slot].slot_id < 0) + { + allocator->allocated_slots[slot].slot_id = slot; + allocator->allocated_slots[slot].refcount = 1; + allocator->n_used_slots += 1; + break; + } + ++slot; + } + + _dbus_assert (slot < allocator->n_allocated_slots); + } + else + { + DBusAllocatedSlot *tmp; + + slot = -1; + tmp = dbus_realloc (allocator->allocated_slots, + sizeof (DBusAllocatedSlot) * (allocator->n_allocated_slots + 1)); + if (tmp == NULL) + goto out; + + allocator->allocated_slots = tmp; + slot = allocator->n_allocated_slots; + allocator->n_allocated_slots += 1; + allocator->n_used_slots += 1; + allocator->allocated_slots[slot].slot_id = slot; + allocator->allocated_slots[slot].refcount = 1; + } + + _dbus_assert (slot >= 0); + _dbus_assert (slot < allocator->n_allocated_slots); + _dbus_assert (*slot_id_p < 0); + _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); + _dbus_assert (allocator->allocated_slots[slot].refcount == 1); + + *slot_id_p = slot; + + _dbus_verbose ("Allocated slot %d on allocator %p total %d slots allocated %d used\n", + slot, allocator, allocator->n_allocated_slots, allocator->n_used_slots); + + out: + _dbus_unlock (allocator->lock); + return slot >= 0; +} + +/** + * Deallocates an ID previously allocated with + * _dbus_data_slot_allocator_alloc(). Existing data stored on + * existing #DBusDataSlotList objects with this ID will be freed when the + * data list is finalized, but may not be retrieved (and may only be + * replaced if someone else reallocates the slot). + * The slot value is reset to -1 if this is the last unref. + * + * @param allocator the allocator + * @param slot_id_p address where we store the slot + */ +void +_dbus_data_slot_allocator_free (DBusDataSlotAllocator *allocator, + dbus_int32_t *slot_id_p) +{ + if (!_dbus_lock (allocator->lock)) + _dbus_assert_not_reached ("we should have initialized global locks " + "before we allocated this slot"); + + _dbus_assert (*slot_id_p < allocator->n_allocated_slots); + _dbus_assert (allocator->allocated_slots[*slot_id_p].slot_id == *slot_id_p); + _dbus_assert (allocator->allocated_slots[*slot_id_p].refcount > 0); + + allocator->allocated_slots[*slot_id_p].refcount -= 1; + + if (allocator->allocated_slots[*slot_id_p].refcount > 0) + { + _dbus_unlock (allocator->lock); + return; + } + + /* refcount is 0, free the slot */ + _dbus_verbose ("Freeing slot %d on allocator %p total %d allocated %d used\n", + *slot_id_p, allocator, allocator->n_allocated_slots, allocator->n_used_slots); + + allocator->allocated_slots[*slot_id_p].slot_id = -1; + *slot_id_p = -1; + + allocator->n_used_slots -= 1; + + if (allocator->n_used_slots == 0) + { + dbus_free (allocator->allocated_slots); + allocator->allocated_slots = NULL; + allocator->n_allocated_slots = 0; + } + + _dbus_unlock (allocator->lock); +} + +/** + * Initializes a slot list. + * @param list the list to initialize. + */ +void +_dbus_data_slot_list_init (DBusDataSlotList *list) +{ + list->slots = NULL; + list->n_slots = 0; +} + +/** + * Stores a pointer in the data slot list, along with an optional + * function to be used for freeing the data when the data is set + * again, or when the slot list is finalized. The slot number must + * have been allocated with _dbus_data_slot_allocator_alloc() for the + * same allocator passed in here. The same allocator has to be used + * with the slot list every time. + * + * @param allocator the allocator to use + * @param list the data slot list + * @param slot the slot number + * @param data the data to store + * @param free_data_func finalizer function for the data + * @param old_free_func free function for any previously-existing data + * @param old_data previously-existing data, should be freed with old_free_func + * @returns #TRUE if there was enough memory to store the data + */ +dbus_bool_t +_dbus_data_slot_list_set (DBusDataSlotAllocator *allocator, + DBusDataSlotList *list, + int slot, + void *data, + DBusFreeFunction free_data_func, + DBusFreeFunction *old_free_func, + void **old_data) +{ +#ifndef DBUS_DISABLE_ASSERT + /* We need to take the allocator lock here, because the allocator could + * be e.g. realloc()ing allocated_slots. We avoid doing this if asserts + * are disabled, since then the asserts are empty. + */ + if (!_dbus_lock (allocator->lock)) + _dbus_assert_not_reached ("we should have initialized global locks " + "before we allocated this slot"); + + _dbus_assert (slot < allocator->n_allocated_slots); + _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); + _dbus_unlock (allocator->lock); +#endif + + if (slot >= list->n_slots) + { + DBusDataSlot *tmp; + int i; + + tmp = dbus_realloc (list->slots, + sizeof (DBusDataSlot) * (slot + 1)); + if (tmp == NULL) + return FALSE; + + list->slots = tmp; + i = list->n_slots; + list->n_slots = slot + 1; + while (i < list->n_slots) + { + list->slots[i].data = NULL; + list->slots[i].free_data_func = NULL; + ++i; + } + } + + _dbus_assert (slot < list->n_slots); + + *old_data = list->slots[slot].data; + *old_free_func = list->slots[slot].free_data_func; + + list->slots[slot].data = data; + list->slots[slot].free_data_func = free_data_func; + + return TRUE; +} + +/** + * Retrieves data previously set with _dbus_data_slot_list_set_data(). + * The slot must still be allocated (must not have been freed). + * + * @param allocator the allocator slot was allocated from + * @param list the data slot list + * @param slot the slot to get data from + * @returns the data, or #NULL if not found + */ +void* +_dbus_data_slot_list_get (DBusDataSlotAllocator *allocator, + DBusDataSlotList *list, + int slot) +{ +#ifndef DBUS_DISABLE_ASSERT + /* We need to take the allocator lock here, because the allocator could + * be e.g. realloc()ing allocated_slots. We avoid doing this if asserts + * are disabled, since then the asserts are empty. + */ + if (!_dbus_lock (allocator->lock)) + _dbus_assert_not_reached ("we should have initialized global locks " + "before we allocated this slot"); + + _dbus_assert (slot >= 0); + _dbus_assert (slot < allocator->n_allocated_slots); + _dbus_assert (allocator->allocated_slots[slot].slot_id == slot); + _dbus_unlock (allocator->lock); +#endif + + if (slot >= list->n_slots) + return NULL; + else + return list->slots[slot].data; +} + +/** + * Frees all data slots contained in the list, calling + * application-provided free functions if they exist. + * + * @param list the list to clear + */ +void +_dbus_data_slot_list_clear (DBusDataSlotList *list) +{ + int i; + + i = 0; + while (i < list->n_slots) + { + if (list->slots[i].free_data_func) + (* list->slots[i].free_data_func) (list->slots[i].data); + list->slots[i].data = NULL; + list->slots[i].free_data_func = NULL; + ++i; + } +} + +/** + * Frees the data slot list and all data slots contained + * in it, calling application-provided free functions + * if they exist. + * + * @param list the list to free + */ +void +_dbus_data_slot_list_free (DBusDataSlotList *list) +{ + _dbus_data_slot_list_clear (list); + + dbus_free (list->slots); + list->slots = NULL; + list->n_slots = 0; +} + +/** @} */ + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +#include "dbus-test.h" +#include <stdio.h> + +/* Test-only, does not need to be thread-safe */ +static int free_counter; + +static void +test_free_slot_data_func (void *data) +{ + int i = _DBUS_POINTER_TO_INT (data); + + _dbus_assert (free_counter == i); + ++free_counter; +} + +/** + * Test function for data slots + */ +dbus_bool_t +_dbus_data_slot_test (const char *test_data_dir _DBUS_GNUC_UNUSED) +{ + DBusDataSlotAllocator allocator; + DBusDataSlotList list; + int i; + DBusFreeFunction old_free_func; + void *old_data; + + if (!_dbus_data_slot_allocator_init (&allocator, _DBUS_LOCK_server_slots)) + _dbus_test_fatal ("no memory for allocator"); + + _dbus_data_slot_list_init (&list); + +#define N_SLOTS 100 + + i = 0; + while (i < N_SLOTS) + { + /* we don't really want apps to rely on this ordered + * allocation, but it simplifies things to rely on it + * here. + */ + dbus_int32_t tmp = -1; + + _dbus_data_slot_allocator_alloc (&allocator, &tmp); + + if (tmp != i) + _dbus_test_fatal ("did not allocate slots in numeric order"); + + ++i; + } + + i = 0; + while (i < N_SLOTS) + { + if (!_dbus_data_slot_list_set (&allocator, &list, + i, + _DBUS_INT_TO_POINTER (i), + test_free_slot_data_func, + &old_free_func, &old_data)) + _dbus_test_fatal ("no memory to set data"); + + _dbus_assert (old_free_func == NULL); + _dbus_assert (old_data == NULL); + + _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) == + _DBUS_INT_TO_POINTER (i)); + + ++i; + } + + free_counter = 0; + i = 0; + while (i < N_SLOTS) + { + if (!_dbus_data_slot_list_set (&allocator, &list, + i, + _DBUS_INT_TO_POINTER (i), + test_free_slot_data_func, + &old_free_func, &old_data)) + _dbus_test_fatal ("no memory to set data"); + + _dbus_assert (old_free_func == test_free_slot_data_func); + _dbus_assert (_DBUS_POINTER_TO_INT (old_data) == i); + + (* old_free_func) (old_data); + _dbus_assert (i == (free_counter - 1)); + + _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) == + _DBUS_INT_TO_POINTER (i)); + + ++i; + } + + free_counter = 0; + _dbus_data_slot_list_free (&list); + + _dbus_assert (N_SLOTS == free_counter); + + i = 0; + while (i < N_SLOTS) + { + dbus_int32_t tmp = i; + + _dbus_data_slot_allocator_free (&allocator, &tmp); + _dbus_assert (tmp == -1); + ++i; + } + + return TRUE; +} + +#endif /* DBUS_ENABLE_EMBEDDED_TESTS */ diff --git a/src/3rdparty/libdbus/dbus/dbus-dataslot.h b/src/3rdparty/libdbus/dbus/dbus-dataslot.h new file mode 100644 index 00000000..08322c1c --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-dataslot.h @@ -0,0 +1,100 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-dataslot.h storing data on objects + * + * Copyright (C) 2003 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_DATASLOT_H +#define DBUS_DATASLOT_H + +#include <dbus/dbus-internals.h> + +DBUS_BEGIN_DECLS + +typedef struct DBusDataSlotAllocator DBusDataSlotAllocator; +typedef struct DBusDataSlotList DBusDataSlotList; + +/** Opaque typedef for DBusDataSlot */ +typedef struct DBusDataSlot DBusDataSlot; +/** DBusDataSlot is used to store application data on the connection */ +struct DBusDataSlot +{ + void *data; /**< The application data */ + DBusFreeFunction free_data_func; /**< Free the application data */ +}; + +typedef struct DBusAllocatedSlot DBusAllocatedSlot; + +/** An allocated slot for storing data + */ +struct DBusAllocatedSlot +{ + dbus_int32_t slot_id; /**< ID of this slot */ + int refcount; /**< Number of uses of the slot */ +}; + +/** + * An allocator that tracks a set of slot IDs. + */ +struct DBusDataSlotAllocator +{ + DBusAllocatedSlot *allocated_slots; /**< Allocated slots */ + int n_allocated_slots; /**< number of slots malloc'd */ + int n_used_slots; /**< number of slots used */ + DBusGlobalLock lock; /**< index of thread lock */ +}; + +#define _DBUS_DATA_SLOT_ALLOCATOR_INIT(x) { NULL, 0, 0, x } + +/** + * Data structure that stores the actual user data set at a given + * slot. + */ +struct DBusDataSlotList +{ + DBusDataSlot *slots; /**< Data slots */ + int n_slots; /**< Slots we have storage for in data_slots */ +}; + +dbus_bool_t _dbus_data_slot_allocator_init (DBusDataSlotAllocator *allocator, + DBusGlobalLock lock); +dbus_bool_t _dbus_data_slot_allocator_alloc (DBusDataSlotAllocator *allocator, + int *slot_id_p); +void _dbus_data_slot_allocator_free (DBusDataSlotAllocator *allocator, + int *slot_id_p); +void _dbus_data_slot_list_init (DBusDataSlotList *list); +dbus_bool_t _dbus_data_slot_list_set (DBusDataSlotAllocator *allocator, + DBusDataSlotList *list, + int slot, + void *data, + DBusFreeFunction free_data_func, + DBusFreeFunction *old_free_func, + void **old_data); +void* _dbus_data_slot_list_get (DBusDataSlotAllocator *allocator, + DBusDataSlotList *list, + int slot); +void _dbus_data_slot_list_clear (DBusDataSlotList *list); +void _dbus_data_slot_list_free (DBusDataSlotList *list); + + +DBUS_END_DECLS + +#endif /* DBUS_DATASLOT_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-errors.c b/src/3rdparty/libdbus/dbus/dbus-errors.c new file mode 100644 index 00000000..24e54a82 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-errors.c @@ -0,0 +1,437 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-errors.c Error reporting + * + * Copyright (C) 2002, 2004 Red Hat Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-errors.h" +#include "dbus-internals.h" +#include "dbus-string.h" +#include "dbus-protocol.h" +#include <stdarg.h> +#include <string.h> + +/** + * @defgroup DBusErrorInternals Error reporting internals + * @ingroup DBusInternals + * @brief Error reporting internals + * @{ + */ + +/** + * @def DBUS_ERROR_INIT + * + * Expands to a suitable initializer for a DBusError on the stack. + * Declaring a DBusError with: + * + * @code + * DBusError error = DBUS_ERROR_INIT; + * + * do_things_with (&error); + * @endcode + * + * is a more concise form of: + * + * @code + * DBusError error; + * + * dbus_error_init (&error); + * do_things_with (&error); + * @endcode + */ + +/** + * Internals of DBusError + */ +typedef struct +{ + char *name; /**< error name */ + char *message; /**< error message */ + + unsigned int const_message : 1; /**< Message is not owned by DBusError */ + + unsigned int dummy2 : 1; /**< placeholder */ + unsigned int dummy3 : 1; /**< placeholder */ + unsigned int dummy4 : 1; /**< placeholder */ + unsigned int dummy5 : 1; /**< placeholder */ + + void *padding1; /**< placeholder */ + +} DBusRealError; + +_DBUS_STATIC_ASSERT (sizeof (DBusRealError) == sizeof (DBusError)); + +/** + * Returns a longer message describing an error name. + * If the error name is unknown, returns the name + * itself. + * + * @param error the error to describe + * @returns a constant string describing the error. + */ +static const char* +message_from_error (const char *error) +{ + if (strcmp (error, DBUS_ERROR_FAILED) == 0) + return "Unknown error"; + else if (strcmp (error, DBUS_ERROR_NO_MEMORY) == 0) + return "Not enough memory available"; + else if (strcmp (error, DBUS_ERROR_IO_ERROR) == 0) + return "Error reading or writing data"; + else if (strcmp (error, DBUS_ERROR_BAD_ADDRESS) == 0) + return "Could not parse address"; + else if (strcmp (error, DBUS_ERROR_NOT_SUPPORTED) == 0) + return "Feature not supported"; + else if (strcmp (error, DBUS_ERROR_LIMITS_EXCEEDED) == 0) + return "Resource limits exceeded"; + else if (strcmp (error, DBUS_ERROR_ACCESS_DENIED) == 0) + return "Permission denied"; + else if (strcmp (error, DBUS_ERROR_AUTH_FAILED) == 0) + return "Could not authenticate to server"; + else if (strcmp (error, DBUS_ERROR_NO_SERVER) == 0) + return "No server available at address"; + else if (strcmp (error, DBUS_ERROR_TIMEOUT) == 0) + return "Connection timed out"; + else if (strcmp (error, DBUS_ERROR_NO_NETWORK) == 0) + return "Network unavailable"; + else if (strcmp (error, DBUS_ERROR_ADDRESS_IN_USE) == 0) + return "Address already in use"; + else if (strcmp (error, DBUS_ERROR_DISCONNECTED) == 0) + return "Disconnected."; + else if (strcmp (error, DBUS_ERROR_INVALID_ARGS) == 0) + return "Invalid arguments."; + else if (strcmp (error, DBUS_ERROR_NO_REPLY) == 0) + return "Did not get a reply message."; + else if (strcmp (error, DBUS_ERROR_FILE_NOT_FOUND) == 0) + return "File doesn't exist."; + else if (strcmp (error, DBUS_ERROR_OBJECT_PATH_IN_USE) == 0) + return "Object path already in use"; + else + return error; +} + +/** @} */ /* End of internals */ + +/** + * @defgroup DBusErrors Error reporting + * @ingroup DBus + * @brief Error reporting + * + * Types and functions related to reporting errors. + * + * + * In essence D-Bus error reporting works as follows: + * + * @code + * DBusError error; + * dbus_error_init (&error); + * dbus_some_function (arg1, arg2, &error); + * if (dbus_error_is_set (&error)) + * { + * fprintf (stderr, "an error occurred: %s\n", error.message); + * dbus_error_free (&error); + * } + * @endcode + * + * By convention, all functions allow #NULL instead of a DBusError*, + * so callers who don't care about the error can ignore it. + * + * There are some rules. An error passed to a D-Bus function must + * always be unset; you can't pass in an error that's already set. If + * a function has a return code indicating whether an error occurred, + * and also a #DBusError parameter, then the error will always be set + * if and only if the return code indicates an error occurred. i.e. + * the return code and the error are never going to disagree. + * + * An error only needs to be freed if it's been set, not if + * it's merely been initialized. + * + * You can check the specific error that occurred using + * dbus_error_has_name(). + * + * Errors will not be set for programming errors, such as passing + * invalid arguments to the libdbus API. Instead, libdbus will print + * warnings, exit on a failed assertion, or even crash in those cases + * (in other words, incorrect use of the API results in undefined + * behavior, possibly accompanied by helpful debugging output if + * you're lucky). + * + * @{ + */ + +/** + * Initializes a DBusError structure. Does not allocate any memory; + * the error only needs to be freed if it is set at some point. + * + * @param error the DBusError. + */ +void +dbus_error_init (DBusError *error) +{ + DBusRealError *real; + + _DBUS_STATIC_ASSERT (sizeof (DBusError) == sizeof (DBusRealError)); + + _dbus_return_if_fail (error != NULL); + + real = (DBusRealError *)error; + + real->name = NULL; + real->message = NULL; + + real->const_message = TRUE; +} + +/** + * Frees an error that's been set (or just initialized), + * then reinitializes the error as in dbus_error_init(). + * + * @param error memory where the error is stored. + */ +void +dbus_error_free (DBusError *error) +{ + DBusRealError *real; + + _dbus_return_if_fail (error != NULL); + + real = (DBusRealError *)error; + + if (!real->const_message) + { + dbus_free (real->name); + dbus_free (real->message); + } + + dbus_error_init (error); +} + +/** + * Assigns an error name and message to a DBusError. Does nothing if + * error is #NULL. The message may be #NULL, which means a default + * message will be deduced from the name. The default message will be + * totally useless, though, so using a #NULL message is not recommended. + * + * Because this function does not copy the error name or message, you + * must ensure the name and message are global data that won't be + * freed. You probably want dbus_set_error() instead, in most cases. + * + * @param error the error or #NULL + * @param name the error name (not copied!!!) + * @param message the error message (not copied!!!) + */ +void +dbus_set_error_const (DBusError *error, + const char *name, + const char *message) +{ + DBusRealError *real; + + _dbus_return_if_error_is_set (error); + _dbus_return_if_fail (name != NULL); + + if (error == NULL) + return; + + _dbus_assert (error->name == NULL); + _dbus_assert (error->message == NULL); + + if (message == NULL) + message = message_from_error (name); + + real = (DBusRealError *)error; + + real->name = (char*) name; + real->message = (char *)message; + real->const_message = TRUE; +} + +/** + * Moves an error src into dest, freeing src and + * overwriting dest. Both src and dest must be initialized. + * src is reinitialized to an empty error. dest may not + * contain an existing error. If the destination is + * #NULL, just frees and reinits the source error. + * + * @param src the source error + * @param dest the destination error or #NULL + */ +void +dbus_move_error (DBusError *src, + DBusError *dest) +{ + _dbus_return_if_error_is_set (dest); + + if (dest) + { + dbus_error_free (dest); + *dest = *src; + dbus_error_init (src); + } + else + dbus_error_free (src); +} + +/** + * Checks whether the error is set and has the given + * name. + * @param error the error + * @param name the name + * @returns #TRUE if the given named error occurred + */ +dbus_bool_t +dbus_error_has_name (const DBusError *error, + const char *name) +{ + _dbus_return_val_if_fail (error != NULL, FALSE); + _dbus_return_val_if_fail (name != NULL, FALSE); + + _dbus_assert ((error->name != NULL && error->message != NULL) || + (error->name == NULL && error->message == NULL)); + + if (error->name != NULL) + { + DBusString str1, str2; + _dbus_string_init_const (&str1, error->name); + _dbus_string_init_const (&str2, name); + return _dbus_string_equal (&str1, &str2); + } + else + return FALSE; +} + +/** + * Checks whether an error occurred (the error is set). + * + * @param error the error object + * @returns #TRUE if an error occurred + */ +dbus_bool_t +dbus_error_is_set (const DBusError *error) +{ + _dbus_return_val_if_fail (error != NULL, FALSE); + _dbus_assert ((error->name != NULL && error->message != NULL) || + (error->name == NULL && error->message == NULL)); + return error->name != NULL; +} + +/** + * Assigns an error name and message to a DBusError. + * Does nothing if error is #NULL. + * + * The format may be #NULL, which means a (pretty much useless) + * default message will be deduced from the name. This is not a good + * idea, just go ahead and provide a useful error message. It won't + * hurt you. + * + * If no memory can be allocated for the error message, + * an out-of-memory error message will be set instead. + * + * @param error the error.or #NULL + * @param name the error name + * @param format printf-style format string. + */ +void +dbus_set_error (DBusError *error, + const char *name, + const char *format, + ...) +{ + va_list args; + + if (error == NULL) + return; + + /* it's a bug to pile up errors */ + _dbus_return_if_error_is_set (error); + _dbus_return_if_fail (name != NULL); + + va_start (args, format); + _dbus_set_error_valist (error, name, format, args); + va_end (args); +} + +void +_dbus_set_error_valist (DBusError *error, + const char *name, + const char *format, + va_list args) +{ + DBusRealError *real; + DBusString str; + + _dbus_assert (name != NULL); + + if (error == NULL) + return; + + _dbus_assert (error->name == NULL); + _dbus_assert (error->message == NULL); + + if (!_dbus_string_init (&str)) + goto nomem; + + if (format == NULL) + { + if (!_dbus_string_append (&str, + message_from_error (name))) + { + _dbus_string_free (&str); + goto nomem; + } + } + else + { + if (!_dbus_string_append_printf_valist (&str, format, args)) + { + _dbus_string_free (&str); + goto nomem; + } + } + + real = (DBusRealError *)error; + + if (!_dbus_string_steal_data (&str, &real->message)) + { + _dbus_string_free (&str); + goto nomem; + } + _dbus_string_free (&str); + + real->name = _dbus_strdup (name); + if (real->name == NULL) + { + dbus_free (real->message); + real->message = NULL; + goto nomem; + } + real->const_message = FALSE; + + return; + + nomem: + _DBUS_SET_OOM (error); +} + +/** @} */ /* End public API */ diff --git a/src/3rdparty/libdbus/dbus/dbus-errors.h b/src/3rdparty/libdbus/dbus/dbus-errors.h new file mode 100644 index 00000000..003f583b --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-errors.h @@ -0,0 +1,92 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-errors.h Error reporting + * + * Copyright (C) 2002 Red Hat Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_ERROR_H +#define DBUS_ERROR_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-protocol.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusErrors + * @{ + */ + +/** Mostly-opaque type representing an error that occurred */ +typedef struct DBusError DBusError; + +/** + * Object representing an exception. + */ +struct DBusError +{ + const char *name; /**< public error name field */ + const char *message; /**< public error message field */ + + unsigned int dummy1 : 1; /**< placeholder */ + unsigned int dummy2 : 1; /**< placeholder */ + unsigned int dummy3 : 1; /**< placeholder */ + unsigned int dummy4 : 1; /**< placeholder */ + unsigned int dummy5 : 1; /**< placeholder */ + + void *padding1; /**< placeholder */ +}; + +#define DBUS_ERROR_INIT { NULL, NULL, TRUE, 0, 0, 0, 0, NULL } + +DBUS_EXPORT +void dbus_error_init (DBusError *error); +DBUS_EXPORT +void dbus_error_free (DBusError *error); +DBUS_EXPORT +void dbus_set_error (DBusError *error, + const char *name, + const char *message, + ...) _DBUS_GNUC_PRINTF (3, 4); +DBUS_EXPORT +void dbus_set_error_const (DBusError *error, + const char *name, + const char *message); +DBUS_EXPORT +void dbus_move_error (DBusError *src, + DBusError *dest); +DBUS_EXPORT +dbus_bool_t dbus_error_has_name (const DBusError *error, + const char *name); +DBUS_EXPORT +dbus_bool_t dbus_error_is_set (const DBusError *error); + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_ERROR_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-file-unix.c b/src/3rdparty/libdbus/dbus/dbus-file-unix.c new file mode 100644 index 00000000..d5fa029e --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-file-unix.c @@ -0,0 +1,459 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-file-unix.c unix related file implementation (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003, 2006 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#include "dbus-protocol.h" +#include "dbus-errors.h" +#include "dbus-file.h" +#include "dbus-internals.h" +#include "dbus-sysdeps.h" +#include "dbus-sysdeps-unix.h" + +#ifdef HAVE_LINUX_MAGIC_H +#include <linux/magic.h> +#endif +#include <sys/stat.h> +#ifdef HAVE_SYS_VFS_H +#include <sys/vfs.h> +#endif +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +/** + * Appends the contents of the given file to the string, + * returning error code. At the moment, won't open a file + * more than a megabyte in size. + * + * @param str the string to append to + * @param filename filename to load + * @param error place to set an error + * @returns #FALSE if error was set + */ +dbus_bool_t +_dbus_file_get_contents (DBusString *str, + const DBusString *filename, + DBusError *error) +{ + int fd; + struct stat sb; +#ifdef HAVE_FSTATFS + struct statfs sfs; +#endif + int orig_len; + int total; + int file_size; + const char *filename_c; + dbus_bool_t is_procfs = FALSE; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + /* O_BINARY useful on Cygwin */ + fd = open (filename_c, O_RDONLY | O_BINARY); + if (fd < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to open \"%s\": %s", + filename_c, + _dbus_strerror (errno)); + return FALSE; + } + + _dbus_verbose ("file fd %d opened\n", fd); + + if (fstat (fd, &sb) < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to stat \"%s\": %s", + filename_c, + _dbus_strerror (errno)); + + _dbus_verbose ("fstat() failed: %s", + _dbus_strerror (errno)); + + _dbus_close (fd, NULL); + + return FALSE; + } + + if (sb.st_size > _DBUS_ONE_MEGABYTE) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "File size %lu of \"%s\" is too large.", + (unsigned long) sb.st_size, filename_c); + _dbus_close (fd, NULL); + return FALSE; + } + + /* procfs has different semantics - most files are 0 size, + * we can do only one read, and at most we can read 4M. + */ +#ifdef HAVE_FSTATFS + if (sb.st_size == 0) + { + if (fstatfs(fd, &sfs) < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to stat \"%s\": %s", + filename_c, + _dbus_strerror (errno)); + + _dbus_verbose ("fstatvfs() failed: %s", + _dbus_strerror (errno)); + + _dbus_close (fd, NULL); + + return FALSE; + } + if (sfs.f_type == PROC_SUPER_MAGIC) + is_procfs = TRUE; + } +#endif + + if (is_procfs) + file_size = _DBUS_ONE_MEGABYTE; + else + file_size = sb.st_size; + + total = 0; + orig_len = _dbus_string_get_length (str); + if (file_size > 0 && S_ISREG (sb.st_mode)) + { + int bytes_read; + + do + { + bytes_read = _dbus_read (fd, str, + file_size - total); + if (bytes_read <= 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Error reading \"%s\": %s", + filename_c, + _dbus_strerror (errno)); + + _dbus_verbose ("read() failed: %s", + _dbus_strerror (errno)); + + _dbus_close (fd, NULL); + _dbus_string_set_length (str, orig_len); + return FALSE; + } + else + total += bytes_read; + } + while (total < file_size && !is_procfs); + + _dbus_close (fd, NULL); + return TRUE; + } + else if (file_size != 0) + { + _dbus_verbose ("Can only open regular files at the moment.\n"); + dbus_set_error (error, DBUS_ERROR_FAILED, + "\"%s\" is not a regular file", + filename_c); + _dbus_close (fd, NULL); + return FALSE; + } + else + { + _dbus_close (fd, NULL); + return TRUE; + } +} + +/** + * Writes a string out to a file. If the file exists, + * it will be atomically overwritten by the new data. + * + * @param str the string to write out + * @param filename the file to save string to + * @param world_readable If set, ensure the file is world readable + * @param error error to be filled in on failure + * @returns #FALSE on failure + */ +dbus_bool_t +_dbus_string_save_to_file (const DBusString *str, + const DBusString *filename, + dbus_bool_t world_readable, + DBusError *error) +{ + int fd; + int bytes_to_write; + const char *filename_c; + DBusString tmp_filename; + const char *tmp_filename_c; + int total; + dbus_bool_t need_unlink; + dbus_bool_t retval; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + fd = -1; + retval = FALSE; + need_unlink = FALSE; + + if (!_dbus_string_init (&tmp_filename)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return FALSE; + } + + if (!_dbus_string_copy (filename, 0, &tmp_filename, 0)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&tmp_filename); + return FALSE; + } + + if (!_dbus_string_append (&tmp_filename, ".")) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&tmp_filename); + return FALSE; + } + +#define N_TMP_FILENAME_RANDOM_BYTES 8 + if (!_dbus_generate_random_ascii (&tmp_filename, N_TMP_FILENAME_RANDOM_BYTES, + error)) + { + _dbus_string_free (&tmp_filename); + return FALSE; + } + + filename_c = _dbus_string_get_const_data (filename); + tmp_filename_c = _dbus_string_get_const_data (&tmp_filename); + + fd = open (tmp_filename_c, O_WRONLY | O_BINARY | O_EXCL | O_CREAT, + world_readable ? 0644 : 0600); + if (fd < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not create %s: %s", tmp_filename_c, + _dbus_strerror (errno)); + goto out; + } + if (world_readable) + { + /* Ensure the file is world readable even in the presence of + * possibly restrictive umasks; + * see http://lists.freedesktop.org/archives/dbus/2010-September/013367.html + */ + if (fchmod (fd, 0644) < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not chmod %s: %s", tmp_filename_c, + _dbus_strerror (errno)); + goto out; + } + } + + _dbus_verbose ("tmp file fd %d opened\n", fd); + + need_unlink = TRUE; + + total = 0; + bytes_to_write = _dbus_string_get_length (str); + + while (total < bytes_to_write) + { + int bytes_written; + + bytes_written = _dbus_write (fd, str, total, + bytes_to_write - total); + + if (bytes_written <= 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not write to %s: %s", tmp_filename_c, + _dbus_strerror (errno)); + + goto out; + } + + total += bytes_written; + } + + if (fsync(fd)) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not synchronize file %s: %s", + tmp_filename_c, _dbus_strerror (errno)); + + goto out; + } + + if (!_dbus_close (fd, NULL)) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not close file %s: %s", + tmp_filename_c, _dbus_strerror (errno)); + + goto out; + } + + fd = -1; + + if (rename (tmp_filename_c, filename_c) < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not rename %s to %s: %s", + tmp_filename_c, filename_c, + _dbus_strerror (errno)); + + goto out; + } + + need_unlink = FALSE; + + retval = TRUE; + + out: + /* close first, then unlink, to prevent ".nfs34234235" garbage + * files + */ + + if (fd >= 0) + _dbus_close (fd, NULL); + + if (need_unlink && unlink (tmp_filename_c) < 0) + _dbus_verbose ("Failed to unlink temp file %s: %s\n", + tmp_filename_c, _dbus_strerror (errno)); + + _dbus_string_free (&tmp_filename); + + _DBUS_ASSERT_ERROR_XOR_BOOL (error, retval); + return retval; +} + +/** Makes the file readable by every user in the system. + * + * @param filename the filename + * @param error error location + * @returns #TRUE if the file's permissions could be changed. + */ +dbus_bool_t +_dbus_make_file_world_readable(const DBusString *filename, + DBusError *error) +{ + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + if (chmod (filename_c, 0644) == -1) + { + dbus_set_error (error, + DBUS_ERROR_FAILED, + "Could not change permissions of file %s: %s\n", + filename_c, + _dbus_strerror (errno)); + return FALSE; + } + return TRUE; +} + +/** Creates the given file, failing if the file already exists. + * + * @param filename the filename + * @param error error location + * @returns #TRUE if we created the file and it didn't exist + */ +dbus_bool_t +_dbus_create_file_exclusively (const DBusString *filename, + DBusError *error) +{ + int fd; + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + fd = open (filename_c, O_WRONLY | O_BINARY | O_EXCL | O_CREAT, + 0600); + if (fd < 0) + { + dbus_set_error (error, + DBUS_ERROR_FAILED, + "Could not create file %s: %s\n", + filename_c, + _dbus_strerror (errno)); + return FALSE; + } + + _dbus_verbose ("exclusive file fd %d opened\n", fd); + + if (!_dbus_close (fd, NULL)) + { + dbus_set_error (error, + DBUS_ERROR_FAILED, + "Could not close file %s: %s\n", + filename_c, + _dbus_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +/** + * Deletes the given file. + * + * @param filename the filename + * @param error error location + * + * @returns #TRUE if unlink() succeeded + */ +dbus_bool_t +_dbus_delete_file (const DBusString *filename, + DBusError *error) +{ + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + if (unlink (filename_c) < 0) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to delete file %s: %s\n", + filename_c, _dbus_strerror (errno)); + return FALSE; + } + else + return TRUE; +} diff --git a/src/3rdparty/libdbus/dbus/dbus-file-win.c b/src/3rdparty/libdbus/dbus/dbus-file-win.c new file mode 100644 index 00000000..a17e0869 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-file-win.c @@ -0,0 +1,408 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-file-win.c windows related file implementation (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003, 2006 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-protocol.h" +#include "dbus-string.h" +#include "dbus-internals.h" +#include "dbus-sysdeps-win.h" +#include "dbus-pipe.h" + +#include <windows.h> + + +/** + * Thin wrapper around the read() system call that appends + * the data it reads to the DBusString buffer. It appends + * up to the given count. + * + * @param hnd the HANDLE to read from + * @param buffer the buffer to append data to + * @param count the amount of data to read + * @param error place to set an error + * @returns the number of bytes read or -1 + */ +static int +_dbus_file_read (HANDLE hnd, + DBusString *buffer, + int count, + DBusError *error) +{ + BOOL result; + DWORD bytes_read; + int start; + char *data; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_assert (count >= 0); + + start = _dbus_string_get_length (buffer); + + if (!_dbus_string_lengthen (buffer, count)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return -1; + } + + data = _dbus_string_get_data_len (buffer, start, count); + + result = ReadFile (hnd, data, count, &bytes_read, NULL); + if (result == 0) + { + char *emsg = _dbus_win_error_string (GetLastError ()); + dbus_set_error (error, _dbus_win_error_from_last_error (), + "Failed to read from %p: %s", hnd, emsg); + _dbus_win_free_error_string (emsg); + return -1; + } + + if (bytes_read) + { + /* put length back (doesn't actually realloc) */ + _dbus_string_set_length (buffer, start + bytes_read); + +#if 0 + if (bytes_read > 0) + _dbus_verbose_bytes_of_string (buffer, start, bytes_read); +#endif + } + + return bytes_read; +} + + +/** + * Appends the contents of the given file to the string, + * returning error code. At the moment, won't open a file + * more than a megabyte in size. + * + * @param str the string to append to + * @param filename filename to load + * @param error place to set an error + * @returns #FALSE if error was set + */ +dbus_bool_t +_dbus_file_get_contents (DBusString *str, + const DBusString *filename, + DBusError *error) +{ + HANDLE hnd; + DWORD fsize; + DWORD fsize_hi; + int orig_len; + unsigned int total; + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + hnd = CreateFileA (filename_c, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hnd == INVALID_HANDLE_VALUE) + { + char *emsg = _dbus_win_error_string (GetLastError ()); + dbus_set_error (error, _dbus_win_error_from_last_error (), + "Failed to open \"%s\": %s", filename_c, emsg); + _dbus_win_free_error_string (emsg); + return FALSE; + } + + _dbus_verbose ("file %s hnd %p opened\n", filename_c, hnd); + + fsize = GetFileSize (hnd, &fsize_hi); + if (fsize == 0xFFFFFFFF && GetLastError() != NO_ERROR) + { + char *emsg = _dbus_win_error_string (GetLastError ()); + dbus_set_error (error, _dbus_win_error_from_last_error (), + "Failed to get file size for \"%s\": %s", + filename_c, emsg); + _dbus_win_free_error_string (emsg); + + _dbus_verbose ("GetFileSize() failed: %s", emsg); + + CloseHandle (hnd); + + return FALSE; + } + + if (fsize_hi != 0 || fsize > _DBUS_ONE_MEGABYTE) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "File size %lu/%lu of \"%s\" is too large.", + (unsigned long) fsize_hi, + (unsigned long) fsize, filename_c); + CloseHandle (hnd); + return FALSE; + } + + total = 0; + orig_len = _dbus_string_get_length (str); + if (fsize > 0) + { + int bytes_read; + + while (total < fsize) + { + bytes_read = _dbus_file_read (hnd, str, fsize - total, error); + if (bytes_read <= 0) + { + if (bytes_read == 0) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Premature EOF reading \"%s\"", + filename_c); + } + else + _DBUS_ASSERT_ERROR_IS_SET (error); + + CloseHandle (hnd); + _dbus_string_set_length (str, orig_len); + return FALSE; + } + else + total += bytes_read; + } + + CloseHandle (hnd); + return TRUE; + } + else + { + CloseHandle (hnd); + return TRUE; + } +} + + +/** + * Writes a string out to a file. If the file exists, + * it will be atomically overwritten by the new data. + * + * @param str the string to write out + * @param filename the file to save string to + * @param world_readable if true, ensure file is world readable + * @param error error to be filled in on failure + * @returns #FALSE on failure + */ +dbus_bool_t +_dbus_string_save_to_file (const DBusString *str, + const DBusString *filename, + dbus_bool_t world_readable, + DBusError *error) +{ + HANDLE hnd; + int bytes_to_write; + const char *filename_c; + DBusString tmp_filename; + const char *tmp_filename_c; + int total; + const char *str_c; + dbus_bool_t need_unlink; + dbus_bool_t retval; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + hnd = INVALID_HANDLE_VALUE; + retval = FALSE; + need_unlink = FALSE; + + if (!_dbus_string_init (&tmp_filename)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return FALSE; + } + + if (!_dbus_string_copy (filename, 0, &tmp_filename, 0)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&tmp_filename); + return FALSE; + } + + if (!_dbus_string_append (&tmp_filename, ".")) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&tmp_filename); + return FALSE; + } + +#define N_TMP_FILENAME_RANDOM_BYTES 8 + if (!_dbus_generate_random_ascii (&tmp_filename, N_TMP_FILENAME_RANDOM_BYTES, + error)) + { + _dbus_string_free (&tmp_filename); + return FALSE; + } + + filename_c = _dbus_string_get_const_data (filename); + tmp_filename_c = _dbus_string_get_const_data (&tmp_filename); + + /* TODO - support world-readable in an atomic fashion */ + hnd = CreateFileA (tmp_filename_c, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, + INVALID_HANDLE_VALUE); + if (hnd == INVALID_HANDLE_VALUE) + { + char *emsg = _dbus_win_error_string (GetLastError ()); + dbus_set_error (error, _dbus_win_error_from_last_error (), + "Could not create \"%s\": %s", filename_c, emsg); + _dbus_win_free_error_string (emsg); + goto out; + } + if (world_readable) + { + if (! _dbus_make_file_world_readable (&tmp_filename, error)) + goto out; + } + + _dbus_verbose ("tmp file %s hnd %p opened\n", tmp_filename_c, hnd); + + need_unlink = TRUE; + + total = 0; + bytes_to_write = _dbus_string_get_length (str); + str_c = _dbus_string_get_const_data (str); + + while (total < bytes_to_write) + { + DWORD bytes_written; + BOOL res; + + res = WriteFile (hnd, str_c + total, bytes_to_write - total, + &bytes_written, NULL); + + if (res == 0 || bytes_written <= 0) + { + char *emsg = _dbus_win_error_string (GetLastError ()); + dbus_set_error (error, _dbus_win_error_from_last_error (), + "Could not write to %s: %s", tmp_filename_c, emsg); + _dbus_win_free_error_string (emsg); + goto out; + } + + total += bytes_written; + } + + if (CloseHandle (hnd) == 0) + { + char *emsg = _dbus_win_error_string (GetLastError ()); + dbus_set_error (error, _dbus_win_error_from_last_error (), + "Could not close file %s: %s", tmp_filename_c, emsg); + _dbus_win_free_error_string (emsg); + goto out; + } + + hnd = INVALID_HANDLE_VALUE; + + /* Unlike rename(), MoveFileEx() can replace existing files */ + if (!MoveFileExA (tmp_filename_c, filename_c, MOVEFILE_REPLACE_EXISTING)) + { + char *emsg = _dbus_win_error_string (GetLastError ()); + dbus_set_error (error, _dbus_win_error_from_last_error (), + "Could not rename %s to %s: %s", + tmp_filename_c, filename_c, emsg); + _dbus_win_free_error_string (emsg); + + goto out; + } + + need_unlink = FALSE; + + retval = TRUE; + + out: + /* close first, then unlink */ + + if (hnd != INVALID_HANDLE_VALUE) + CloseHandle (hnd); + + if (need_unlink && DeleteFileA (tmp_filename_c) == 0) + { + char *emsg = _dbus_win_error_string (GetLastError ()); + _dbus_verbose ("Failed to unlink temp file %s: %s", tmp_filename_c, + emsg); + _dbus_win_free_error_string (emsg); + } + + _dbus_string_free (&tmp_filename); + + if (!retval) + _DBUS_ASSERT_ERROR_IS_SET (error); + + return retval; +} + + +/** Creates the given file, failing if the file already exists. + * + * @param filename the filename + * @param error error location + * @returns #TRUE if we created the file and it didn't exist + */ +dbus_bool_t +_dbus_create_file_exclusively (const DBusString *filename, + DBusError *error) +{ + HANDLE hnd; + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + hnd = CreateFileA (filename_c, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, + INVALID_HANDLE_VALUE); + if (hnd == INVALID_HANDLE_VALUE) + { + char *emsg = _dbus_win_error_string (GetLastError ()); + dbus_set_error (error, _dbus_win_error_from_last_error (), + "Could not create file %s: %s", + filename_c, emsg); + _dbus_win_free_error_string (emsg); + return FALSE; + } + + _dbus_verbose ("exclusive file %s hnd %p opened\n", filename_c, hnd); + + if (CloseHandle (hnd) == 0) + { + char *emsg = _dbus_win_error_string (GetLastError ()); + dbus_set_error (error, _dbus_win_error_from_last_error (), + "Could not close file %s: %s", + filename_c, emsg); + _dbus_win_free_error_string (emsg); + + return FALSE; + } + + return TRUE; +} diff --git a/src/3rdparty/libdbus/dbus/dbus-file.c b/src/3rdparty/libdbus/dbus/dbus-file.c new file mode 100644 index 00000000..5b122189 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-file.c @@ -0,0 +1,29 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sysdeps-unix.c Wrappers around UNIX system/libc features (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003, 2006 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#include "dbus-file.h" diff --git a/src/3rdparty/libdbus/dbus/dbus-file.h b/src/3rdparty/libdbus/dbus/dbus-file.h new file mode 100644 index 00000000..90fc5295 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-file.h @@ -0,0 +1,67 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-file.h dbus file related stuff (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_FILE_H +#define DBUS_FILE_H + +//#include <dbus/dbus-types.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-errors.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusFile + * @{ + */ + +/** + * File interface + */ +dbus_bool_t _dbus_file_exists (const char *file); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_file_get_contents (DBusString *str, + const DBusString *filename, + DBusError *error); +dbus_bool_t _dbus_string_save_to_file (const DBusString *str, + const DBusString *filename, + dbus_bool_t world_readable, + DBusError *error); + +dbus_bool_t _dbus_make_file_world_readable (const DBusString *filename, + DBusError *error); + +dbus_bool_t _dbus_create_file_exclusively (const DBusString *filename, + DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_delete_file (const DBusString *filename, + DBusError *error); + +/** @} */ + +DBUS_END_DECLS + +#endif diff --git a/src/3rdparty/libdbus/dbus/dbus-hash.c b/src/3rdparty/libdbus/dbus/dbus-hash.c new file mode 100644 index 00000000..2635a154 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-hash.c @@ -0,0 +1,1595 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-hash.c Generic hash table utility (internal to D-Bus implementation) + * + * Copyright 1991-1993 The Regents of the University of California. + * Copyright 1994 Sun Microsystems, Inc. + * Copyright 2002-2005 Red Hat, Inc. + * Copyright 2003 Joe Shaw + * Copyright 2006 Sjoerd Simons + * Copyright 2010 Fridrich Štrba + * Copyright 2016 Ralf Habacker + * Copyright 2017 Endless Mobile, Inc. + * SPDX-License-Identifier: (AFL-2.1 OR GPL-2.0-or-later) AND TCL + * + * Hash table implementation based on generic/tclHash.c from the Tcl + * source code. The original Tcl license applies to portions of the + * code from tclHash.c; the Tcl license follows this standad D-Bus + * license information. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +/* + * The following copyright applies to code from the Tcl distribution. + * + * Copyright (c) 1991-1993 The Regents of the University of California. + * Copyright (c) 1994 Sun Microsystems, Inc. + * + * This software is copyrighted by the Regents of the University of + * California, Sun Microsystems, Inc., Scriptics Corporation, and + * other parties. The following terms apply to all files associated + * with the software unless explicitly disclaimed in individual files. + * + * The authors hereby grant permission to use, copy, modify, + * distribute, and license this software and its documentation for any + * purpose, provided that existing copyright notices are retained in + * all copies and that this notice is included verbatim in any + * distributions. No written agreement, license, or royalty fee is + * required for any of the authorized uses. Modifications to this + * software may be copyrighted by their authors and need not follow + * the licensing terms described here, provided that the new terms are + * clearly indicated on the first page of each file where they apply. + * + * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY + * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL + * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, + * OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND + * NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, + * AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE + * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * GOVERNMENT USE: If you are acquiring this software on behalf of the + * U.S. government, the Government shall have only "Restricted Rights" + * in the software and related documentation as defined in the Federal + * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you + * are acquiring the software on behalf of the Department of Defense, + * the software shall be classified as "Commercial Computer Software" + * and the Government shall have only "Restricted Rights" as defined + * in Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the + * foregoing, the authors grant the U.S. Government and others acting + * in its behalf permission to use and distribute the software in + * accordance with the terms specified in this license. + */ + +#include <config.h> +#include "dbus-hash.h" +#include "dbus-internals.h" +#include "dbus-mempool.h" +#include <dbus/dbus-test-tap.h> + +/** + * @defgroup DBusHashTable Hash table + * @ingroup DBusInternals + * @brief DBusHashTable data structure + * + * Types and functions related to DBusHashTable. + */ + +/** + * @defgroup DBusHashTableInternals Hash table implementation details + * @ingroup DBusInternals + * @brief DBusHashTable implementation details + * + * The guts of DBusHashTable. + * + * @{ + */ + +/** + * When there are this many entries per bucket, on average, rebuild + * the hash table to make it larger. + */ +#define REBUILD_MULTIPLIER 3 + +/** + * Takes a preliminary integer hash value and produces an index into a + * hash tables bucket list. The idea is to make it so that + * preliminary values that are arbitrarily similar will end up in + * different buckets. The hash function was taken from a + * random-number generator. (This is used to hash integers.) + * + * The down_shift drops off the high bits of the hash index, and + * decreases as we increase the number of hash buckets (to keep more + * range in the hash index). The mask also strips high bits and strips + * fewer high bits as the number of hash buckets increases. + * I don't understand two things: why is the initial downshift 28 + * to keep 4 bits when the initial mask is 011 to keep 2 bits, + * and why do we have both a mask and a downshift? + * + */ +#define RANDOM_INDEX(table, i) \ + (((((intptr_t) (i))*1103515245) >> (table)->down_shift) & (table)->mask) + +/** + * Initial number of buckets in hash table (hash table statically + * allocates its buckets for this size and below). + * The initial mask has to be synced to this. + */ +#define DBUS_SMALL_HASH_TABLE 4 + +/** + * Typedef for DBusHashEntry + */ +typedef struct DBusHashEntry DBusHashEntry; + +/** + * @brief Internal representation of a hash entry. + * + * A single entry (key-value pair) in the hash table. + * Internal to hash table implementation. + */ +struct DBusHashEntry +{ + DBusHashEntry *next; /**< Pointer to next entry in this + * hash bucket, or #NULL for end of + * chain. + */ + void *key; /**< Hash key */ + void *value; /**< Hash value */ +}; + +/** + * Function used to find and optionally create a hash entry. + */ +typedef DBusHashEntry* (* DBusFindEntryFunction) (DBusHashTable *table, + void *key, + dbus_bool_t create_if_not_found, + DBusHashEntry ***bucket, + DBusPreallocatedHash *preallocated); + +/** + * @brief Internals of DBusHashTable. + * + * Hash table internals. Hash tables are opaque objects, they must be + * used via accessor functions. + */ +struct DBusHashTable { + int refcount; /**< Reference count */ + + DBusHashEntry **buckets; /**< Pointer to bucket array. Each + * element points to first entry in + * bucket's hash chain, or #NULL. + */ + DBusHashEntry *static_buckets[DBUS_SMALL_HASH_TABLE]; + /**< Bucket array used for small tables + * (to avoid mallocs and frees). + */ + int n_buckets; /**< Total number of buckets allocated + * at **buckets. + */ + int n_entries; /**< Total number of entries present + * in table. + */ + int hi_rebuild_size; /**< Enlarge table when n_entries gets + * to be this large. + */ + int lo_rebuild_size; /**< Shrink table when n_entries gets + * below this. + */ + int down_shift; /**< Shift count used in hashing + * function. Designed to use high- + * order bits of randomized keys. + */ + int mask; /**< Mask value used in hashing + * function. + */ + DBusHashType key_type; /**< Type of keys used in this table */ + + + DBusFindEntryFunction find_function; /**< Function for finding entries */ + + DBusFreeFunction free_key_function; /**< Function to free keys */ + DBusFreeFunction free_value_function; /**< Function to free values */ + + DBusMemPool *entry_pool; /**< Memory pool for hash entries */ +}; + +/** + * @brief Internals of DBusHashIter. + */ +typedef struct +{ + DBusHashTable *table; /**< Pointer to table containing entry. */ + DBusHashEntry **bucket; /**< Pointer to bucket that points to + * first entry in this entry's chain: + * used for deleting the entry. + */ + DBusHashEntry *entry; /**< Current hash entry */ + DBusHashEntry *next_entry; /**< Next entry to be iterated onto in current bucket */ + int next_bucket; /**< index of next bucket */ + int n_entries_on_init; /**< used to detect table resize since initialization */ +} DBusRealHashIter; + +_DBUS_STATIC_ASSERT (sizeof (DBusRealHashIter) == sizeof (DBusHashIter)); + +static DBusHashEntry* find_direct_function (DBusHashTable *table, + void *key, + dbus_bool_t create_if_not_found, + DBusHashEntry ***bucket, + DBusPreallocatedHash *preallocated); +static DBusHashEntry* find_string_function (DBusHashTable *table, + void *key, + dbus_bool_t create_if_not_found, + DBusHashEntry ***bucket, + DBusPreallocatedHash *preallocated); +static unsigned int string_hash (const char *str); +static dbus_bool_t rebuild_table (DBusHashTable *table); +static DBusHashEntry* alloc_entry (DBusHashTable *table); +static void remove_entry (DBusHashTable *table, + DBusHashEntry **bucket, + DBusHashEntry *entry); +static void free_entry (DBusHashTable *table, + DBusHashEntry *entry); +static void free_entry_data (DBusHashTable *table, + DBusHashEntry *entry); + + +/** @} */ + +/** + * @addtogroup DBusHashTable + * @{ + */ + +/** + * @typedef DBusHashIter + * + * Public opaque hash table iterator object. + */ + +/** + * @typedef DBusHashTable + * + * Public opaque hash table object. + */ + +/** + * @typedef DBusHashType + * + * Indicates the type of a key in the hash table. + */ + +/** + * Constructs a new hash table. Should be freed with + * _dbus_hash_table_unref(). If memory cannot be + * allocated for the hash table, returns #NULL. + * + * @param type the type of hash key to use. + * @param key_free_function function to free hash keys. + * @param value_free_function function to free hash values. + * @returns a new DBusHashTable or #NULL if no memory. + */ +DBusHashTable* +_dbus_hash_table_new (DBusHashType type, + DBusFreeFunction key_free_function, + DBusFreeFunction value_free_function) +{ + DBusHashTable *table; + DBusMemPool *entry_pool; + + table = dbus_new0 (DBusHashTable, 1); + if (table == NULL) + return NULL; + + entry_pool = _dbus_mem_pool_new (sizeof (DBusHashEntry), TRUE); + if (entry_pool == NULL) + { + dbus_free (table); + return NULL; + } + + table->refcount = 1; + table->entry_pool = entry_pool; + + _dbus_assert (DBUS_SMALL_HASH_TABLE == _DBUS_N_ELEMENTS (table->static_buckets)); + + table->buckets = table->static_buckets; + table->n_buckets = DBUS_SMALL_HASH_TABLE; + table->n_entries = 0; + table->hi_rebuild_size = DBUS_SMALL_HASH_TABLE * REBUILD_MULTIPLIER; + table->lo_rebuild_size = 0; + table->down_shift = 28; + table->mask = 3; + table->key_type = type; + + _dbus_assert (table->mask < table->n_buckets); + + switch (table->key_type) + { + case DBUS_HASH_INT: + case DBUS_HASH_UINTPTR: + table->find_function = find_direct_function; + break; + case DBUS_HASH_STRING: + table->find_function = find_string_function; + break; + default: + _dbus_assert_not_reached ("Unknown hash table type"); + break; + } + + table->free_key_function = key_free_function; + table->free_value_function = value_free_function; + + return table; +} + + +/** + * Increments the reference count for a hash table. + * + * @param table the hash table to add a reference to. + * @returns the hash table. + */ +DBusHashTable * +_dbus_hash_table_ref (DBusHashTable *table) +{ + table->refcount += 1; + + return table; +} + +/** + * Decrements the reference count for a hash table, + * freeing the hash table if the count reaches zero. + * + * @param table the hash table to remove a reference from. + */ +void +_dbus_hash_table_unref (DBusHashTable *table) +{ + table->refcount -= 1; + + if (table->refcount == 0) + { +#if 0 + DBusHashEntry *entry; + DBusHashEntry *next; + int i; + + /* Free the entries in the table. */ + for (i = 0; i < table->n_buckets; i++) + { + entry = table->buckets[i]; + while (entry != NULL) + { + next = entry->next; + + free_entry (table, entry); + + entry = next; + } + } +#else + DBusHashEntry *entry; + int i; + + /* Free the entries in the table. */ + for (i = 0; i < table->n_buckets; i++) + { + entry = table->buckets[i]; + while (entry != NULL) + { + free_entry_data (table, entry); + + entry = entry->next; + } + } + /* We can do this very quickly with memory pools ;-) */ + _dbus_mem_pool_free (table->entry_pool); +#endif + + /* Free the bucket array, if it was dynamically allocated. */ + if (table->buckets != table->static_buckets) + dbus_free (table->buckets); + + dbus_free (table); + } +} + +/** + * Removed all entries from a hash table. + * + * @param table the hash table to remove all entries from. + */ +void +_dbus_hash_table_remove_all (DBusHashTable *table) +{ + DBusHashIter iter; + _dbus_hash_iter_init (table, &iter); + while (_dbus_hash_iter_next (&iter)) + { + _dbus_hash_iter_remove_entry(&iter); + } +} + +static DBusHashEntry* +alloc_entry (DBusHashTable *table) +{ + DBusHashEntry *entry; + + entry = _dbus_mem_pool_alloc (table->entry_pool); + + return entry; +} + +static void +free_entry_data (DBusHashTable *table, + DBusHashEntry *entry) +{ + if (table->free_key_function) + (* table->free_key_function) (entry->key); + if (table->free_value_function) + (* table->free_value_function) (entry->value); +} + +static void +free_entry (DBusHashTable *table, + DBusHashEntry *entry) +{ + free_entry_data (table, entry); + _dbus_mem_pool_dealloc (table->entry_pool, entry); +} + +static void +remove_entry (DBusHashTable *table, + DBusHashEntry **bucket, + DBusHashEntry *entry) +{ + _dbus_assert (table != NULL); + _dbus_assert (bucket != NULL); + _dbus_assert (*bucket != NULL); + _dbus_assert (entry != NULL); + + if (*bucket == entry) + *bucket = entry->next; + else + { + DBusHashEntry *prev; + prev = *bucket; + + while (prev->next != entry) + prev = prev->next; + + _dbus_assert (prev != NULL); + + prev->next = entry->next; + } + + table->n_entries -= 1; + free_entry (table, entry); +} + +/** + * Initializes a hash table iterator. To iterate over all entries in a + * hash table, use the following code (the printf assumes a hash + * from strings to strings obviously): + * + * @code + * DBusHashIter iter; + * + * _dbus_hash_iter_init (table, &iter); + * while (_dbus_hash_iter_next (&iter)) + * { + * printf ("The first key is %s and value is %s\n", + * _dbus_hash_iter_get_string_key (&iter), + * _dbus_hash_iter_get_value (&iter)); + * } + * + * + * @endcode + * + * The iterator is initialized pointing "one before" the first hash + * entry. The first call to _dbus_hash_iter_next() moves it onto + * the first valid entry or returns #FALSE if the hash table is + * empty. Subsequent calls move to the next valid entry or return + * #FALSE if there are no more entries. + * + * Note that it is guaranteed to be safe to remove a hash entry during + * iteration, but it is not safe to add a hash entry. + * + * @param table the hash table to iterate over. + * @param iter the iterator to initialize. + */ +void +_dbus_hash_iter_init (DBusHashTable *table, + DBusHashIter *iter) +{ + DBusRealHashIter *real; + + _DBUS_STATIC_ASSERT (sizeof (DBusHashIter) == sizeof (DBusRealHashIter)); + + real = (DBusRealHashIter*) iter; + + real->table = table; + real->bucket = NULL; + real->entry = NULL; + real->next_entry = NULL; + real->next_bucket = 0; + real->n_entries_on_init = table->n_entries; +} + +/** + * Move the hash iterator forward one step, to the next hash entry. + * The documentation for _dbus_hash_iter_init() explains in more + * detail. + * + * @param iter the iterator to move forward. + * @returns #FALSE if there are no more entries to move to. + */ +dbus_bool_t +_dbus_hash_iter_next (DBusHashIter *iter) +{ + DBusRealHashIter *real; + + _DBUS_STATIC_ASSERT (sizeof (DBusHashIter) == sizeof (DBusRealHashIter)); + + real = (DBusRealHashIter*) iter; + + /* if this assertion failed someone probably added hash entries + * during iteration, which is bad. + */ + _dbus_assert (real->n_entries_on_init >= real->table->n_entries); + + /* Remember that real->entry may have been deleted */ + + while (real->next_entry == NULL) + { + if (real->next_bucket >= real->table->n_buckets) + { + /* invalidate iter and return false */ + real->entry = NULL; + real->table = NULL; + real->bucket = NULL; + return FALSE; + } + + real->bucket = &(real->table->buckets[real->next_bucket]); + real->next_entry = *(real->bucket); + real->next_bucket += 1; + } + + _dbus_assert (real->next_entry != NULL); + _dbus_assert (real->bucket != NULL); + + real->entry = real->next_entry; + real->next_entry = real->entry->next; + + return TRUE; +} + +/** + * Removes the current entry from the hash table. + * If a key_free_function or value_free_function + * was provided to _dbus_hash_table_new(), + * frees the key and/or value for this entry. + * + * @param iter the hash table iterator. + */ +void +_dbus_hash_iter_remove_entry (DBusHashIter *iter) +{ + DBusRealHashIter *real; + + real = (DBusRealHashIter*) iter; + + _dbus_assert (real->table != NULL); + _dbus_assert (real->entry != NULL); + _dbus_assert (real->bucket != NULL); + + remove_entry (real->table, real->bucket, real->entry); + + real->entry = NULL; /* make it crash if you try to use this entry */ +} + +/** + * Gets the value of the current entry. + * + * @param iter the hash table iterator. + */ +void* +_dbus_hash_iter_get_value (DBusHashIter *iter) +{ + DBusRealHashIter *real; + + real = (DBusRealHashIter*) iter; + + _dbus_assert (real->table != NULL); + _dbus_assert (real->entry != NULL); + + return real->entry->value; +} + +/** + * Sets the value of the current entry. + * If the hash table has a value_free_function + * it will be used to free the previous value. + * The hash table will own the passed-in value + * (it will not be copied). + * + * @param iter the hash table iterator. + * @param value the new value. + */ +void +_dbus_hash_iter_set_value (DBusHashIter *iter, + void *value) +{ + DBusRealHashIter *real; + + real = (DBusRealHashIter*) iter; + + _dbus_assert (real->table != NULL); + _dbus_assert (real->entry != NULL); + + if (real->table->free_value_function && value != real->entry->value) + (* real->table->free_value_function) (real->entry->value); + + real->entry->value = value; +} + +/** + * Gets the key for the current entry. + * Only works for hash tables of type #DBUS_HASH_INT. + * + * @param iter the hash table iterator. + */ +int +_dbus_hash_iter_get_int_key (DBusHashIter *iter) +{ + DBusRealHashIter *real; + + real = (DBusRealHashIter*) iter; + + _dbus_assert (real->table != NULL); + _dbus_assert (real->entry != NULL); + + return _DBUS_POINTER_TO_INT (real->entry->key); +} + +/** + * Gets the key for the current entry. + * Only works for hash tables of type #DBUS_HASH_UINTPTR. + * + * @param iter the hash table iterator. + */ +uintptr_t +_dbus_hash_iter_get_uintptr_key (DBusHashIter *iter) +{ + DBusRealHashIter *real; + + real = (DBusRealHashIter*) iter; + + _dbus_assert (real->table != NULL); + _dbus_assert (real->entry != NULL); + + return (uintptr_t) real->entry->key; +} + +/** + * Gets the key for the current entry. + * Only works for hash tables of type #DBUS_HASH_STRING + * @param iter the hash table iterator. + */ +const char* +_dbus_hash_iter_get_string_key (DBusHashIter *iter) +{ + DBusRealHashIter *real; + + real = (DBusRealHashIter*) iter; + + _dbus_assert (real->table != NULL); + _dbus_assert (real->entry != NULL); + + return real->entry->key; +} + +/** + * A low-level but efficient interface for manipulating the hash + * table. It's efficient because you can get, set, and optionally + * create the hash entry while only running the hash function one + * time. + * + * Note that while calling _dbus_hash_iter_next() on the iterator + * filled in by this function may work, it's completely + * undefined which entries are after this iter and which + * are before it. So it would be silly to iterate using this + * iterator. + * + * If the hash entry is created, its value will be initialized + * to all bits zero. + * + * #FALSE may be returned due to memory allocation failure, or + * because create_if_not_found was #FALSE and the entry + * did not exist. + * + * If create_if_not_found is #TRUE, the hash + * table takes ownership of the key that's passed in (either using it to create + * the entry, or freeing it immediately). + * + * For a hash table of type #DBUS_HASH_INT, cast the int + * key to the key parameter using #_DBUS_INT_TO_POINTER(). + * + * @param table the hash table. + * @param key the hash key. + * @param create_if_not_found if #TRUE, create the entry if it didn't exist. + * @param iter the iterator to initialize. + * @returns #TRUE if the hash entry now exists (and the iterator is thus valid). + */ +dbus_bool_t +_dbus_hash_iter_lookup (DBusHashTable *table, + void *key, + dbus_bool_t create_if_not_found, + DBusHashIter *iter) +{ + DBusRealHashIter *real; + DBusHashEntry *entry = NULL; + DBusHashEntry **bucket = NULL; + + _DBUS_STATIC_ASSERT (sizeof (DBusHashIter) == sizeof (DBusRealHashIter)); + + real = (DBusRealHashIter*) iter; + + entry = (* table->find_function) (table, key, create_if_not_found, &bucket, NULL); + + /* entry == NULL means not found, and either !create_if_not_found or OOM */ + if (entry == NULL) + return FALSE; + + _dbus_assert (bucket != NULL); + _dbus_assert (table->n_buckets >= 1); + _dbus_assert (bucket >= table->buckets); + _dbus_assert (bucket <= &table->buckets[table->n_buckets - 1]); + + if (create_if_not_found) + { + if (table->free_key_function && entry->key != key) + (* table->free_key_function) (entry->key); + + entry->key = key; + } + + real->table = table; + real->bucket = bucket; + real->entry = entry; + real->next_entry = entry->next; + real->next_bucket = (bucket - table->buckets) + 1; + real->n_entries_on_init = table->n_entries; + + _dbus_assert (real->next_bucket >= 0); + _dbus_assert (real->next_bucket <= table->n_buckets); + _dbus_assert (&(table->buckets[real->next_bucket-1]) == real->bucket); + + return TRUE; +} + +static void +add_allocated_entry (DBusHashTable *table, + DBusHashEntry *entry, + unsigned int idx, + void *key, + DBusHashEntry ***bucket) +{ + DBusHashEntry **b; + + entry->key = key; + + b = &(table->buckets[idx]); + entry->next = *b; + *b = entry; + + if (bucket) + *bucket = b; + + table->n_entries += 1; + + /* note we ONLY rebuild when ADDING - because you can iterate over a + * table and remove entries safely. + */ + if (table->n_entries >= table->hi_rebuild_size || + table->n_entries < table->lo_rebuild_size) + { + if (!rebuild_table (table)) + return; + + if (bucket) + { + /* Recalculate hash for the new table size */ + switch (table->key_type) + { + case DBUS_HASH_STRING: + idx = string_hash (entry->key) & table->mask; + break; + + case DBUS_HASH_INT: + case DBUS_HASH_UINTPTR: + idx = RANDOM_INDEX (table, entry->key); + break; + + default: + idx = 0; + _dbus_assert_not_reached ("Unknown hash table type"); + break; + } + + *bucket = &(table->buckets[idx]); + } + } +} + +static DBusHashEntry* +add_entry (DBusHashTable *table, + unsigned int idx, + void *key, + DBusHashEntry ***bucket, + DBusPreallocatedHash *preallocated) +{ + DBusHashEntry *entry; + + if (preallocated == NULL) + { + entry = alloc_entry (table); + if (entry == NULL) + { + if (bucket) + *bucket = NULL; + return NULL; + } + } + else + { + entry = (DBusHashEntry*) preallocated; + } + + add_allocated_entry (table, entry, idx, key, bucket); + _dbus_assert (bucket == NULL || *bucket != NULL); + + return entry; +} + +/* This is g_str_hash from GLib which was + * extensively discussed/tested/profiled + */ +static unsigned int +string_hash (const char *str) +{ + const char *p = str; + unsigned int h = *p; + + if (h) + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + *p; + + return h; +} + +/** Key comparison function */ +typedef int (* KeyCompareFunc) (const void *key_a, const void *key_b); + +static DBusHashEntry* +find_generic_function (DBusHashTable *table, + void *key, + unsigned int idx, + KeyCompareFunc compare_func, + dbus_bool_t create_if_not_found, + DBusHashEntry ***bucket, + DBusPreallocatedHash *preallocated) +{ + DBusHashEntry *entry; + + if (bucket) + *bucket = NULL; + + /* Search all of the entries in this bucket. */ + entry = table->buckets[idx]; + while (entry != NULL) + { + if ((compare_func == NULL && key == entry->key) || + (compare_func != NULL && (* compare_func) (key, entry->key) == 0)) + { + if (bucket) + *bucket = &(table->buckets[idx]); + + if (preallocated) + _dbus_hash_table_free_preallocated_entry (table, preallocated); + + return entry; + } + + entry = entry->next; + } + + if (create_if_not_found) + { + entry = add_entry (table, idx, key, bucket, preallocated); + + if (entry == NULL) /* OOM */ + return NULL; + + _dbus_assert (bucket == NULL || *bucket != NULL); + } + else if (preallocated) + { + _dbus_hash_table_free_preallocated_entry (table, preallocated); + } + + return entry; +} + +static DBusHashEntry* +find_string_function (DBusHashTable *table, + void *key, + dbus_bool_t create_if_not_found, + DBusHashEntry ***bucket, + DBusPreallocatedHash *preallocated) +{ + unsigned int idx; + + idx = string_hash (key) & table->mask; + + return find_generic_function (table, key, idx, + (KeyCompareFunc) strcmp, create_if_not_found, bucket, + preallocated); +} + +static DBusHashEntry* +find_direct_function (DBusHashTable *table, + void *key, + dbus_bool_t create_if_not_found, + DBusHashEntry ***bucket, + DBusPreallocatedHash *preallocated) +{ + unsigned int idx; + + idx = RANDOM_INDEX (table, key) & table->mask; + + + return find_generic_function (table, key, idx, + NULL, create_if_not_found, bucket, + preallocated); +} + +/* Return FALSE if nothing happened. */ +static dbus_bool_t +rebuild_table (DBusHashTable *table) +{ + int old_size; + int new_buckets; + DBusHashEntry **old_buckets; + DBusHashEntry **old_chain; + DBusHashEntry *entry; + dbus_bool_t growing; + + /* + * Allocate and initialize the new bucket array, and set up + * hashing constants for new array size. + */ + + growing = table->n_entries >= table->hi_rebuild_size; + + old_size = table->n_buckets; + old_buckets = table->buckets; + + if (growing) + { + /* overflow paranoia */ + if (table->n_buckets < _DBUS_INT_MAX / 4 && + table->down_shift >= 2) + new_buckets = table->n_buckets * 4; + else + return FALSE; /* can't grow any more */ + } + else + { + new_buckets = table->n_buckets / 4; + if (new_buckets < DBUS_SMALL_HASH_TABLE) + return FALSE; /* don't bother shrinking this far */ + } + + table->buckets = dbus_new0 (DBusHashEntry*, new_buckets); + if (table->buckets == NULL) + { + /* out of memory, yay - just don't reallocate, the table will + * still work, albeit more slowly. + */ + table->buckets = old_buckets; + return FALSE; + } + + table->n_buckets = new_buckets; + + if (growing) + { + table->lo_rebuild_size = table->hi_rebuild_size; + table->hi_rebuild_size *= 4; + + table->down_shift -= 2; /* keep 2 more high bits */ + table->mask = (table->mask << 2) + 3; /* keep 2 more high bits */ + } + else + { + table->hi_rebuild_size = table->lo_rebuild_size; + table->lo_rebuild_size /= 4; + + table->down_shift += 2; /* keep 2 fewer high bits */ + table->mask = table->mask >> 2; /* keep 2 fewer high bits */ + } + +#if 0 + printf ("%s table to lo = %d hi = %d downshift = %d mask = 0x%x\n", + growing ? "GROW" : "SHRINK", + table->lo_rebuild_size, + table->hi_rebuild_size, + table->down_shift, + table->mask); +#endif + + _dbus_assert (table->lo_rebuild_size >= 0); + _dbus_assert (table->hi_rebuild_size > table->lo_rebuild_size); + _dbus_assert (table->down_shift >= 0); + _dbus_assert (table->mask != 0); + /* the mask is essentially the max index */ + _dbus_assert (table->mask < table->n_buckets); + + /* + * Rehash all of the existing entries into the new bucket array. + */ + + for (old_chain = old_buckets; old_size > 0; old_size--, old_chain++) + { + for (entry = *old_chain; entry != NULL; entry = *old_chain) + { + unsigned int idx; + DBusHashEntry **bucket; + + *old_chain = entry->next; + switch (table->key_type) + { + case DBUS_HASH_STRING: + idx = string_hash (entry->key) & table->mask; + break; + case DBUS_HASH_INT: + case DBUS_HASH_UINTPTR: + idx = RANDOM_INDEX (table, entry->key); + break; + default: + idx = 0; + _dbus_assert_not_reached ("Unknown hash table type"); + break; + } + + bucket = &(table->buckets[idx]); + entry->next = *bucket; + *bucket = entry; + } + } + + /* Free the old bucket array, if it was dynamically allocated. */ + + if (old_buckets != table->static_buckets) + dbus_free (old_buckets); + + return TRUE; +} + +/** + * Looks up the value for a given string in a hash table + * of type #DBUS_HASH_STRING. Returns %NULL if the value + * is not present. (A not-present entry is indistinguishable + * from an entry with a value of %NULL.) + * @param table the hash table. + * @param key the string to look up. + * @returns the value of the hash entry. + */ +void* +_dbus_hash_table_lookup_string (DBusHashTable *table, + const char *key) +{ + DBusHashEntry *entry; + + _dbus_assert (table->key_type == DBUS_HASH_STRING); + + entry = (* table->find_function) (table, (char*) key, FALSE, NULL, NULL); + + if (entry) + return entry->value; + else + return NULL; +} + +/** + * Looks up the value for a given integer in a hash table + * of type #DBUS_HASH_INT. Returns %NULL if the value + * is not present. (A not-present entry is indistinguishable + * from an entry with a value of %NULL.) + * @param table the hash table. + * @param key the integer to look up. + * @returns the value of the hash entry. + */ +void* +_dbus_hash_table_lookup_int (DBusHashTable *table, + int key) +{ + DBusHashEntry *entry; + + _dbus_assert (table->key_type == DBUS_HASH_INT); + + entry = (* table->find_function) (table, _DBUS_INT_TO_POINTER (key), FALSE, NULL, NULL); + + if (entry) + return entry->value; + else + return NULL; +} + +/** + * Looks up the value for a given integer in a hash table + * of type #DBUS_HASH_UINTPTR. Returns %NULL if the value + * is not present. (A not-present entry is indistinguishable + * from an entry with a value of %NULL.) + * @param table the hash table. + * @param key the integer to look up. + * @returns the value of the hash entry. + */ +void* +_dbus_hash_table_lookup_uintptr (DBusHashTable *table, + uintptr_t key) +{ + DBusHashEntry *entry; + + _dbus_assert (table->key_type == DBUS_HASH_UINTPTR); + + entry = (* table->find_function) (table, (void*) key, FALSE, NULL, NULL); + + if (entry) + return entry->value; + else + return NULL; +} + +/** + * Removes the hash entry for the given key. If no hash entry + * for the key exists, does nothing. + * + * @param table the hash table. + * @param key the hash key. + * @returns #TRUE if the entry existed + */ +dbus_bool_t +_dbus_hash_table_remove_string (DBusHashTable *table, + const char *key) +{ + DBusHashEntry *entry; + DBusHashEntry **bucket; + + _dbus_assert (table->key_type == DBUS_HASH_STRING); + + entry = (* table->find_function) (table, (char*) key, FALSE, &bucket, NULL); + + if (entry) + { + remove_entry (table, bucket, entry); + return TRUE; + } + else + return FALSE; +} + +/** + * Removes the hash entry for the given key. If no hash entry + * for the key exists, does nothing. + * + * @param table the hash table. + * @param key the hash key. + * @returns #TRUE if the entry existed + */ +dbus_bool_t +_dbus_hash_table_remove_int (DBusHashTable *table, + int key) +{ + DBusHashEntry *entry; + DBusHashEntry **bucket; + + _dbus_assert (table->key_type == DBUS_HASH_INT); + + entry = (* table->find_function) (table, _DBUS_INT_TO_POINTER (key), FALSE, &bucket, NULL); + + if (entry) + { + remove_entry (table, bucket, entry); + return TRUE; + } + else + return FALSE; +} + +/** + * Removes the hash entry for the given key. If no hash entry + * for the key exists, does nothing. + * + * @param table the hash table. + * @param key the hash key. + * @returns #TRUE if the entry existed + */ +dbus_bool_t +_dbus_hash_table_remove_uintptr (DBusHashTable *table, + uintptr_t key) +{ + DBusHashEntry *entry; + DBusHashEntry **bucket; + + _dbus_assert (table->key_type == DBUS_HASH_UINTPTR); + + entry = (* table->find_function) (table, (void*) key, FALSE, &bucket, NULL); + + if (entry) + { + remove_entry (table, bucket, entry); + return TRUE; + } + else + return FALSE; +} + +/** + * Creates a hash entry with the given key and value. + * The key and value are not copied; they are stored + * in the hash table by reference. If an entry with the + * given key already exists, the previous key and value + * are overwritten (and freed if the hash table has + * a key_free_function and/or value_free_function). + * + * Returns #FALSE if memory for the new hash entry + * can't be allocated. + * + * @param table the hash table. + * @param key the hash entry key. + * @param value the hash entry value. + */ +dbus_bool_t +_dbus_hash_table_insert_string (DBusHashTable *table, + char *key, + void *value) +{ + DBusPreallocatedHash *preallocated; + + _dbus_assert (table->key_type == DBUS_HASH_STRING); + + preallocated = _dbus_hash_table_preallocate_entry (table); + if (preallocated == NULL) + return FALSE; + + _dbus_hash_table_insert_string_preallocated (table, preallocated, + key, value); + + return TRUE; +} + +/** + * Creates a hash entry with the given key and value. + * The key and value are not copied; they are stored + * in the hash table by reference. If an entry with the + * given key already exists, the previous key and value + * are overwritten (and freed if the hash table has + * a key_free_function and/or value_free_function). + * + * Returns #FALSE if memory for the new hash entry + * can't be allocated. + * + * @param table the hash table. + * @param key the hash entry key. + * @param value the hash entry value. + */ +dbus_bool_t +_dbus_hash_table_insert_int (DBusHashTable *table, + int key, + void *value) +{ + DBusHashEntry *entry; + + _dbus_assert (table->key_type == DBUS_HASH_INT); + + entry = (* table->find_function) (table, _DBUS_INT_TO_POINTER (key), TRUE, NULL, NULL); + + if (entry == NULL) + return FALSE; /* no memory */ + + if (table->free_key_function && entry->key != _DBUS_INT_TO_POINTER (key)) + (* table->free_key_function) (entry->key); + + if (table->free_value_function && entry->value != value) + (* table->free_value_function) (entry->value); + + entry->key = _DBUS_INT_TO_POINTER (key); + entry->value = value; + + return TRUE; +} + +/** + * Creates a hash entry with the given key and value. + * The key and value are not copied; they are stored + * in the hash table by reference. If an entry with the + * given key already exists, the previous key and value + * are overwritten (and freed if the hash table has + * a key_free_function and/or value_free_function). + * + * Returns #FALSE if memory for the new hash entry + * can't be allocated. + * + * @param table the hash table. + * @param key the hash entry key. + * @param value the hash entry value. + */ +dbus_bool_t +_dbus_hash_table_insert_uintptr (DBusHashTable *table, + uintptr_t key, + void *value) +{ + DBusHashEntry *entry; + + _dbus_assert (table->key_type == DBUS_HASH_UINTPTR); + + entry = (* table->find_function) (table, (void*) key, TRUE, NULL, NULL); + + if (entry == NULL) + return FALSE; /* no memory */ + + if (table->free_key_function && entry->key != (void*) key) + (* table->free_key_function) (entry->key); + + if (table->free_value_function && entry->value != value) + (* table->free_value_function) (entry->value); + + entry->key = (void*) key; + entry->value = value; + + return TRUE; +} + +/** + * Preallocate an opaque data blob that allows us to insert into the + * hash table at a later time without allocating any memory. + * + * @param table the hash table + * @returns the preallocated data, or #NULL if no memory + */ +DBusPreallocatedHash* +_dbus_hash_table_preallocate_entry (DBusHashTable *table) +{ + DBusHashEntry *entry; + + entry = alloc_entry (table); + + return (DBusPreallocatedHash*) entry; +} + +/** + * Frees an opaque DBusPreallocatedHash that was *not* used + * in order to insert into the hash table. + * + * @param table the hash table + * @param preallocated the preallocated data + */ +void +_dbus_hash_table_free_preallocated_entry (DBusHashTable *table, + DBusPreallocatedHash *preallocated) +{ + DBusHashEntry *entry; + + _dbus_assert (preallocated != NULL); + + entry = (DBusHashEntry*) preallocated; + + /* Don't use free_entry(), since this entry has no key/data */ + _dbus_mem_pool_dealloc (table->entry_pool, entry); +} + +/** + * Inserts a string-keyed entry into the hash table, using a + * preallocated data block from + * _dbus_hash_table_preallocate_entry(). This function cannot fail due + * to lack of memory. The DBusPreallocatedHash object is consumed and + * should not be reused or freed. Otherwise this function works + * just like _dbus_hash_table_insert_string(). + * + * @param table the hash table + * @param preallocated the preallocated data + * @param key the hash key + * @param value the value + */ +void +_dbus_hash_table_insert_string_preallocated (DBusHashTable *table, + DBusPreallocatedHash *preallocated, + char *key, + void *value) +{ + DBusHashEntry *entry; + + _dbus_assert (table->key_type == DBUS_HASH_STRING); + _dbus_assert (preallocated != NULL); + + entry = (* table->find_function) (table, key, TRUE, NULL, preallocated); + + _dbus_assert (entry != NULL); + + if (table->free_key_function && entry->key != key) + (* table->free_key_function) (entry->key); + + if (table->free_value_function && entry->value != value) + (* table->free_value_function) (entry->value); + + entry->key = key; + entry->value = value; +} + +/** + * Gets the number of hash entries in a hash table. + * + * @param table the hash table. + * @returns the number of entries in the table. + */ +int +_dbus_hash_table_get_n_entries (DBusHashTable *table) +{ + return table->n_entries; +} + +/** + * Imports a string array into a hash table + * The hash table needs to be initialized with string keys, + * and dbus_free() as both key and value free-function. + * + * @param table the hash table + * @param array the string array to import + * @param delimiter the delimiter to separate key and value + * @return #TRUE on success. + * @return #FALSE if not enough memory. + */ + +dbus_bool_t +_dbus_hash_table_from_array (DBusHashTable *table, char **array, char delimiter) +{ + DBusString key; + DBusString value; + int i; + dbus_bool_t retval = FALSE; + + _dbus_assert (table != NULL); + _dbus_assert (array != NULL); + + if (!_dbus_string_init (&key)) + { + return FALSE; + } + + if (!_dbus_string_init (&value)) + { + _dbus_string_free (&key); + return FALSE; + } + + for (i = 0; array[i] != NULL; i++) + { + if (!_dbus_string_append (&key, array[i])) + break; + + if (_dbus_string_split_on_byte (&key, delimiter, &value)) + { + char *hash_key, *hash_value; + + if (!_dbus_string_steal_data (&key, &hash_key)) + break; + + if (!_dbus_string_steal_data (&value, &hash_value)) + break; + + if (!_dbus_hash_table_insert_string (table, + hash_key, hash_value)) + break; + } + _dbus_string_set_length (&key, 0); + _dbus_string_set_length (&value, 0); + } + + if (array[i] != NULL) + goto out; + + retval = TRUE; +out: + + _dbus_string_free (&key); + _dbus_string_free (&value); + + return retval; +} + +/** + * Creates a string array from a hash table + * + * @param table the hash table + * @param delimiter the delimiter to join key and value + * @return pointer to created string array (free with dbus_free_string_array) + * @return #FALSE if not enough memory. + */ +char ** +_dbus_hash_table_to_array (DBusHashTable *table, char delimiter) +{ + int i, length; + DBusString entry; + DBusHashIter iter; + char **array; + + _dbus_assert (table != NULL); + + length = _dbus_hash_table_get_n_entries (table); + + array = dbus_new0 (char *, length + 1); + + if (array == NULL) + return NULL; + + i = 0; + _dbus_hash_iter_init (table, &iter); + + if (!_dbus_string_init (&entry)) + { + dbus_free_string_array (array); + return NULL; + } + + while (_dbus_hash_iter_next (&iter)) + { + const char *key, *value; + + key = (const char *) _dbus_hash_iter_get_string_key (&iter); + value = (const char *) _dbus_hash_iter_get_value (&iter); + + if (!_dbus_string_append_printf (&entry, "%s%c%s", key, delimiter, value)) + break; + + if (!_dbus_string_steal_data (&entry, array + i)) + break; + i++; + } + + _dbus_string_free (&entry); + + if (i != length) + { + dbus_free_string_array (array); + array = NULL; + } + + return array; +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-hash.h b/src/3rdparty/libdbus/dbus/dbus-hash.h new file mode 100644 index 00000000..290fb1a2 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-hash.h @@ -0,0 +1,226 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-hash.h Generic hash table utility (internal to D-Bus implementation) + * + * Copyright (C) 2002 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_HASH_H +#define DBUS_HASH_H + +#include <stdint.h> + +#ifdef HAVE_INTTYPES_H +#include <inttypes.h> +#endif + +#include <dbus/dbus-memory.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-sysdeps.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusHashTable + * @{ + */ + +/** Hash iterator object. The iterator is on the stack, but its real + * fields are hidden privately. + */ +struct DBusHashIter +{ + void *dummy1; /**< Do not use. */ + void *dummy2; /**< Do not use. */ + void *dummy3; /**< Do not use. */ + void *dummy4; /**< Do not use. */ + int dummy5; /**< Do not use. */ + int dummy6; /**< Do not use. */ +}; + +typedef struct DBusHashTable DBusHashTable; +typedef struct DBusHashIter DBusHashIter; + +/* Allowing an arbitrary function as with GLib + * would be nicer for a public API, but for + * an internal API this saves typing, we can add + * more whenever we feel like it. + */ +typedef enum +{ + DBUS_HASH_STRING, /**< Hash keys are strings. */ + DBUS_HASH_INT, /**< Hash keys are integers. */ + DBUS_HASH_UINTPTR /**< Hash keys are integer capable to hold a pointer. */ +} DBusHashType; + +DBUS_PRIVATE_EXPORT +DBusHashTable* _dbus_hash_table_new (DBusHashType type, + DBusFreeFunction key_free_function, + DBusFreeFunction value_free_function); +DBUS_PRIVATE_EXPORT +DBusHashTable* _dbus_hash_table_ref (DBusHashTable *table); +DBUS_PRIVATE_EXPORT +void _dbus_hash_table_unref (DBusHashTable *table); +void _dbus_hash_table_remove_all (DBusHashTable *table); +DBUS_PRIVATE_EXPORT +void _dbus_hash_iter_init (DBusHashTable *table, + DBusHashIter *iter); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_hash_iter_next (DBusHashIter *iter); +DBUS_PRIVATE_EXPORT +void _dbus_hash_iter_remove_entry (DBusHashIter *iter); +DBUS_PRIVATE_EXPORT +void* _dbus_hash_iter_get_value (DBusHashIter *iter); +DBUS_PRIVATE_EXPORT +void _dbus_hash_iter_set_value (DBusHashIter *iter, + void *value); +DBUS_PRIVATE_EXPORT +int _dbus_hash_iter_get_int_key (DBusHashIter *iter); +DBUS_PRIVATE_EXPORT +const char* _dbus_hash_iter_get_string_key (DBusHashIter *iter); +DBUS_PRIVATE_EXPORT +uintptr_t _dbus_hash_iter_get_uintptr_key (DBusHashIter *iter); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_hash_iter_lookup (DBusHashTable *table, + void *key, + dbus_bool_t create_if_not_found, + DBusHashIter *iter); +DBUS_PRIVATE_EXPORT +void* _dbus_hash_table_lookup_string (DBusHashTable *table, + const char *key); +DBUS_PRIVATE_EXPORT +void* _dbus_hash_table_lookup_int (DBusHashTable *table, + int key); +DBUS_PRIVATE_EXPORT +void* _dbus_hash_table_lookup_uintptr (DBusHashTable *table, + uintptr_t key); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_hash_table_remove_string (DBusHashTable *table, + const char *key); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_hash_table_remove_int (DBusHashTable *table, + int key); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_hash_table_remove_uintptr (DBusHashTable *table, + uintptr_t key); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_hash_table_insert_string (DBusHashTable *table, + char *key, + void *value); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_hash_table_insert_int (DBusHashTable *table, + int key, + void *value); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_hash_table_insert_uintptr (DBusHashTable *table, + uintptr_t key, + void *value); +DBUS_PRIVATE_EXPORT +int _dbus_hash_table_get_n_entries (DBusHashTable *table); + +DBUS_PRIVATE_EXPORT +char ** _dbus_hash_table_to_array (DBusHashTable *table, + char delimiter); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_hash_table_from_array (DBusHashTable *table, + char **array, + char delimiter); + +/* Preallocation */ + +/** A preallocated hash entry */ +typedef struct DBusPreallocatedHash DBusPreallocatedHash; + +DBUS_PRIVATE_EXPORT +DBusPreallocatedHash *_dbus_hash_table_preallocate_entry (DBusHashTable *table); +DBUS_PRIVATE_EXPORT +void _dbus_hash_table_free_preallocated_entry (DBusHashTable *table, + DBusPreallocatedHash *preallocated); +DBUS_PRIVATE_EXPORT +void _dbus_hash_table_insert_string_preallocated (DBusHashTable *table, + DBusPreallocatedHash *preallocated, + char *key, + void *value); + +#ifdef DBUS_WIN +# define DBUS_HASH_POLLABLE DBUS_HASH_UINTPTR +#else +# define DBUS_HASH_POLLABLE DBUS_HASH_INT +#endif + +static inline DBusPollable +_dbus_hash_iter_get_pollable_key (DBusHashIter *iter) +{ +#ifdef DBUS_WIN + DBusSocket s; + + s.sock = _dbus_hash_iter_get_uintptr_key (iter); + return s; +#else + return _dbus_hash_iter_get_int_key (iter); +#endif +} + +static inline void * +_dbus_hash_table_lookup_pollable (DBusHashTable *table, + DBusPollable key) +{ +#ifdef DBUS_WIN + return _dbus_hash_table_lookup_uintptr (table, key.sock); +#else + return _dbus_hash_table_lookup_int (table, key); +#endif +} + +static inline dbus_bool_t +_dbus_hash_table_remove_pollable (DBusHashTable *table, + DBusPollable key) +{ +#ifdef DBUS_WIN + return _dbus_hash_table_remove_uintptr (table, key.sock); +#else + return _dbus_hash_table_remove_int (table, key); +#endif +} + +static inline dbus_bool_t +_dbus_hash_table_insert_pollable (DBusHashTable *table, + DBusPollable key, + void *value) +{ +#ifdef DBUS_WIN + return _dbus_hash_table_insert_uintptr (table, key.sock, value); +#else + return _dbus_hash_table_insert_int (table, key, value); +#endif +} + +static inline void +_dbus_clear_hash_table (DBusHashTable **table_p) +{ + _dbus_clear_pointer_impl (DBusHashTable, table_p, _dbus_hash_table_unref); +} + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_HASH_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-init-win.cpp b/src/3rdparty/libdbus/dbus/dbus-init-win.cpp new file mode 100644 index 00000000..56311d8a --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-init-win.cpp @@ -0,0 +1,54 @@ +/* + * dbus-init-win.cpp - once-per-process initialization + * + * Copyright © 2013 Intel Corporation + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +extern "C" +{ +#include "dbus-init-win.h" +} + +class DBusInternalInit + { + public: + DBusInternalInit () + { + _dbus_threads_windows_init_global (); + } + + void must_not_be_omitted () + { + } + }; + +static DBusInternalInit init; + +extern "C" void +_dbus_threads_windows_ensure_ctor_linked () +{ + /* Do nothing significant, just ensure that the global initializer gets + * linked in. */ + init.must_not_be_omitted (); +} diff --git a/src/3rdparty/libdbus/dbus/dbus-init-win.h b/src/3rdparty/libdbus/dbus/dbus-init-win.h new file mode 100644 index 00000000..8a9de8f7 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-init-win.h @@ -0,0 +1,17 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright © 2013 Intel Corporation + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Do not include other private headers in this one, particularly + * dbus-sysdeps.h: it gets included into C++ code which is not + * compatible with our use of <stdatomic.h>. + */ + +#ifndef DBUS_INIT_WIN_H +#define DBUS_INIT_WIN_H + +void _dbus_threads_windows_init_global (void); +void _dbus_threads_windows_ensure_ctor_linked (void); + +#endif diff --git a/src/3rdparty/libdbus/dbus/dbus-internals.c b/src/3rdparty/libdbus/dbus/dbus-internals.c new file mode 100644 index 00000000..6a23ff5b --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-internals.c @@ -0,0 +1,1194 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-internals.c random utility stuff (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-protocol.h" +#include "dbus-marshal-basic.h" +#include "dbus-test.h" +#include "dbus-test-tap.h" +#include "dbus-valgrind-internal.h" +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> +#ifdef DBUS_USE_OUTPUT_DEBUG_STRING +#include <windows.h> +#include <mbstring.h> +#endif + +/** + * @defgroup DBusInternals D-Bus secret internal implementation details + * @brief Documentation useful when developing or debugging D-Bus itself. + * + */ + +/** + * @defgroup DBusInternalsUtils Utilities and portability + * @ingroup DBusInternals + * @brief Utility functions (_dbus_assert(), _dbus_warn(), etc.) + * @{ + */ + +/** + * @def _dbus_assert + * + * Aborts with an error message if the condition is false. + * + * @param condition condition which must be true. + */ + +/** + * @def _dbus_assert_not_reached + * + * Aborts with an error message if called. + * The given explanation will be printed. + * + * @param explanation explanation of what happened if the code was reached. + */ + +/** + * @def _DBUS_N_ELEMENTS + * + * Computes the number of elements in a fixed-size array using + * sizeof(). + * + * @param array the array to count elements in. + */ + +/** + * @def _DBUS_POINTER_TO_INT + * + * Safely casts a void* to an integer; should only be used on void* + * that actually contain integers, for example one created with + * _DBUS_INT_TO_POINTER. Only guaranteed to preserve 32 bits. + * (i.e. it's used to store 32-bit ints in pointers, but + * can't be used to store 64-bit pointers in ints.) + * + * @param pointer pointer to extract an integer from. + */ +/** + * @def _DBUS_INT_TO_POINTER + * + * Safely stuffs an integer into a pointer, to be extracted later with + * _DBUS_POINTER_TO_INT. Only guaranteed to preserve 32 bits. + * + * @param integer the integer to stuff into a pointer. + */ +/** + * @def _DBUS_ZERO + * + * Sets all bits in an object to zero. + * + * @param object the object to be zeroed. + */ +/** + * @def _DBUS_INT16_MIN + * + * Minimum value of type "int16" + */ +/** + * @def _DBUS_INT16_MAX + * + * Maximum value of type "int16" + */ +/** + * @def _DBUS_UINT16_MAX + * + * Maximum value of type "uint16" + */ + +/** + * @def _DBUS_INT32_MIN + * + * Minimum value of type "int32" + */ +/** + * @def _DBUS_INT32_MAX + * + * Maximum value of type "int32" + */ +/** + * @def _DBUS_UINT32_MAX + * + * Maximum value of type "uint32" + */ + +/** + * @def _DBUS_INT_MIN + * + * Minimum value of type "int" + */ +/** + * @def _DBUS_INT_MAX + * + * Maximum value of type "int" + */ +/** + * @def _DBUS_UINT_MAX + * + * Maximum value of type "uint" + */ + +/** + * @typedef DBusForeachFunction + * + * Used to iterate over each item in a collection, such as + * a DBusList. + */ + +/** + * @def _DBUS_LOCK_NAME + * + * Expands to name of a global lock variable. + */ + +/** + * @def _DBUS_LOCK + * + * Locks a global lock, initializing it first if necessary. + * + * @returns #FALSE if not enough memory + */ + +/** + * @def _DBUS_UNLOCK + * + * Unlocks a global lock + */ + +/* The build system should have checked for DBUS_SIZEOF_VOID_P */ +_DBUS_STATIC_ASSERT (sizeof (void *) == DBUS_SIZEOF_VOID_P); + +/* dbus currently assumes that function pointers are essentially + * interchangeable with data pointers. There's nothing special about + * DBusShutdownFunction, it's just an arbitrary function pointer type. + * If this assertion fails on your platform, some porting will be required. */ +_DBUS_STATIC_ASSERT (sizeof (void *) == sizeof (DBusShutdownFunction)); +_DBUS_STATIC_ASSERT (_DBUS_ALIGNOF (void *) == _DBUS_ALIGNOF (DBusShutdownFunction)); + +/* This is meant to be true by definition. */ +_DBUS_STATIC_ASSERT (sizeof (void *) == sizeof (intptr_t)); +_DBUS_STATIC_ASSERT (sizeof (void *) == sizeof (uintptr_t)); + +/* + * Some frequent assumptions that we should *avoid* making include these, + * all of which are false on CHERI (which has 128-bit tagged pointers, + * but a 64-bit address space and therefore 64-bit sizes): + * + * sizeof (void *) <= sizeof (size_t) + * sizeof (void *) <= 8 + * _DBUS_ALIGNOF (void *) <= 8 + * + * We should also avoid making these assumptions, although we don't currently + * know a concrete example of platforms where they're false: + * + * sizeof (ptrdiff_t) == sizeof (size_t) + */ + +/** + * Fixed "out of memory" error message, just to avoid + * making up a different string every time and wasting + * space. + */ +const char *_dbus_no_memory_message = "Not enough memory"; + +/* Not necessarily thread-safe, but if writes don't propagate between + * threads, the worst that will happen is that we duplicate work in more than + * one thread. */ +static dbus_bool_t warn_initted = FALSE; + +/* Not necessarily thread-safe, but if writes don't propagate between + * threads, the worst that will happen is that warnings get their default + * fatal/non-fatal nature. */ +static dbus_bool_t fatal_warnings = FALSE; +static dbus_bool_t fatal_warnings_on_check_failed = TRUE; + +static int check_failed_count = 0; + +int _dbus_get_check_failed_count (void) +{ + return check_failed_count; +} + +static void +init_warnings(void) +{ + if (!warn_initted) + { + const char *s; + s = _dbus_getenv ("DBUS_FATAL_WARNINGS"); + if (s && *s) + { + if (*s == '0') + { + fatal_warnings = FALSE; + fatal_warnings_on_check_failed = FALSE; + } + else if (*s == '1') + { + fatal_warnings = TRUE; + fatal_warnings_on_check_failed = TRUE; + } + else + { + fprintf(stderr, "DBUS_FATAL_WARNINGS should be set to 0 or 1 if set, not '%s'", + s); + } + } + + check_failed_count = 0; + + warn_initted = TRUE; + } +} + +/** + * Prints a warning message to stderr. Can optionally be made to exit + * fatally by setting DBUS_FATAL_WARNINGS, but this is rarely + * used. This function should be considered pretty much equivalent to + * fprintf(stderr). _dbus_warn_check_failed() on the other hand is + * suitable for use when a programming mistake has been made. + * + * @param format printf-style format string. + */ +void +_dbus_warn (const char *format, + ...) +{ + DBusSystemLogSeverity severity = DBUS_SYSTEM_LOG_WARNING; + va_list args; + + if (!warn_initted) + init_warnings (); + + if (fatal_warnings) + severity = DBUS_SYSTEM_LOG_ERROR; + + va_start (args, format); + _dbus_logv (severity, format, args); + va_end (args); + + if (fatal_warnings) + { + fflush (stderr); + _dbus_abort (); + } +} + +/** + * Prints a "critical" warning to stderr when an assertion fails; + * differs from _dbus_warn primarily in that it + * defaults to fatal. This should be used only when a programming + * error has been detected. (NOT for unavoidable errors that an app + * might handle - those should be returned as DBusError.) Calling this + * means "there is a bug" + */ +void +_dbus_warn_check_failed(const char *format, + ...) +{ + DBusSystemLogSeverity severity = DBUS_SYSTEM_LOG_WARNING; + va_list args; + + if (!warn_initted) + init_warnings (); + + if (fatal_warnings_on_check_failed) + severity = DBUS_SYSTEM_LOG_ERROR; + + va_start (args, format); + _dbus_logv (severity, format, args); + va_end (args); + + if (fatal_warnings_on_check_failed) + { + fflush (stderr); + _dbus_abort (); + } + else + check_failed_count++; +} + +#ifdef DBUS_ENABLE_VERBOSE_MODE + +static dbus_bool_t verbose_initted = FALSE; +static dbus_bool_t verbose = TRUE; + +#ifdef DBUS_USE_OUTPUT_DEBUG_STRING +static char module_name[1024]; +#endif + +static inline void +_dbus_verbose_init (void) +{ + if (!verbose_initted) + { + const char *p = _dbus_getenv ("DBUS_VERBOSE"); + verbose = p != NULL && *p == '1'; + verbose_initted = TRUE; +#ifdef DBUS_USE_OUTPUT_DEBUG_STRING + { + char *last_period, *last_slash; + GetModuleFileName(0,module_name,sizeof(module_name)-1); + last_period = _mbsrchr(module_name,'.'); + if (last_period) + *last_period ='\0'; + last_slash = _mbsrchr(module_name,'\\'); + if (last_slash) + strcpy(module_name,last_slash+1); + strcat(module_name,": "); + } +#endif + } +} + +/** + remove source root from file path + the source root is determined by +*/ +static char *_dbus_file_path_extract_elements_from_tail(const char *file,int level) +{ + int prefix = 0; + char *p = (char *)file + strlen(file); + int i = 0; + + for (;p >= file;p--) + { + if (DBUS_IS_DIR_SEPARATOR(*p)) + { + if (++i >= level) + { + prefix = p-file+1; + break; + } + } + } + + return (char *)file+prefix; +} + +/** + * Implementation of dbus_is_verbose() macro if built with verbose logging + * enabled. + * @returns whether verbose logging is active. + */ +dbus_bool_t +_dbus_is_verbose_real (void) +{ + _dbus_verbose_init (); + return verbose; +} + +void _dbus_set_verbose (dbus_bool_t state) +{ + verbose = state; +} + +dbus_bool_t _dbus_get_verbose (void) +{ + return verbose; +} + +/** + * Low-level function for displaying a string + * for the predefined output channel, which + * can be the Windows debug output port or stderr. + * + * This function must be used instead of + * dbus_verbose(), if a dynamic memory request + * cannot be used to avoid recursive call loops. + * + * @param s string to display + */ +void +_dbus_verbose_raw (const char *s) +{ + if (!_dbus_is_verbose_real ()) + return; +#ifdef DBUS_USE_OUTPUT_DEBUG_STRING + OutputDebugStringA (s); +#else + fputs (s, stderr); + fflush (stderr); +#endif +} + +/** + * Prints a warning message to stderr + * if the user has enabled verbose mode. + * This is the real function implementation, + * use _dbus_verbose() macro in code. + * + * @param format printf-style format string. + */ +void +_dbus_verbose_real ( +#ifdef DBUS_CPP_SUPPORTS_VARIABLE_MACRO_ARGUMENTS + const char *file, + const int line, + const char *function, +#endif + const char *format, + ...) +{ + va_list args; + static dbus_bool_t need_pid = TRUE; + int len; + dbus_int64_t sec; + long usec; + + /* things are written a bit oddly here so that + * in the non-verbose case we just have the one + * conditional and return immediately. + */ + if (!_dbus_is_verbose_real()) + return; + +#ifndef DBUS_USE_OUTPUT_DEBUG_STRING + /* Print out pid before the line */ + if (need_pid) + { + _dbus_print_thread (); + } + _dbus_get_real_time (&sec, &usec); + fprintf (stderr, "%" DBUS_INT64_MODIFIER "d.%06ld ", sec, usec); +#endif + + /* Only print pid again if the next line is a new line */ + len = strlen (format); + if (format[len-1] == '\n') + need_pid = TRUE; + else + need_pid = FALSE; + + va_start (args, format); + +#ifdef DBUS_USE_OUTPUT_DEBUG_STRING + { + DBusString out = _DBUS_STRING_INIT_INVALID; + const char *message = NULL; + + if (!_dbus_string_init (&out)) + goto out; + + if (!_dbus_string_append (&out, module_name)) + goto out; + +#ifdef DBUS_CPP_SUPPORTS_VARIABLE_MACRO_ARGUMENTS + if (!_dbus_string_append_printf (&out, "[%s(%d):%s] ", _dbus_file_path_extract_elements_from_tail (file, 2), line, function)) + goto out; +#endif + if (!_dbus_string_append_printf_valist (&out, format, args)) + goto out; + message = _dbus_string_get_const_data (&out); +out: + if (message == NULL) + { + OutputDebugStringA ("Out of memory while formatting verbose message: '''"); + OutputDebugStringA (format); + OutputDebugStringA ("'''"); + } + else + { + OutputDebugStringA (message); + } + _dbus_string_free (&out); + } +#else +#ifdef DBUS_CPP_SUPPORTS_VARIABLE_MACRO_ARGUMENTS + fprintf (stderr, "[%s(%d):%s] ",_dbus_file_path_extract_elements_from_tail(file,2),line,function); +#endif + + vfprintf (stderr, format, args); + fflush (stderr); +#endif + + va_end (args); +} + +/** + * Reinitializes the verbose logging code, used + * as a hack in dbus-spawn-unix.c so that a child + * process re-reads its pid + * + */ +void +_dbus_verbose_reset_real (void) +{ + verbose_initted = FALSE; +} + +void +_dbus_trace_ref (const char *obj_name, + void *obj, + int old_refcount, + int new_refcount, + const char *why, + const char *env_var, + int *enabled) +{ + _dbus_assert (obj_name != NULL); + _dbus_assert (obj != NULL); + _dbus_assert (old_refcount >= -1); + _dbus_assert (new_refcount >= -1); + + if (old_refcount == -1) + { + _dbus_assert (new_refcount == -1); + } + else + { + _dbus_assert (new_refcount >= 0); + _dbus_assert (old_refcount >= 0); + _dbus_assert (old_refcount > 0 || new_refcount > 0); + } + + _dbus_assert (why != NULL); + _dbus_assert (env_var != NULL); + _dbus_assert (enabled != NULL); + + if (*enabled < 0) + { + const char *s = _dbus_getenv (env_var); + + *enabled = FALSE; + + if (s && *s) + { + if (*s == '0') + *enabled = FALSE; + else if (*s == '1') + *enabled = TRUE; + else + _dbus_warn ("%s should be 0 or 1 if set, not '%s'", env_var, s); + } + } + + if (*enabled) + { + if (old_refcount == -1) + { + VALGRIND_PRINTF_BACKTRACE ("%s %p ref stolen (%s)", + obj_name, obj, why); + _dbus_verbose ("%s %p ref stolen (%s)\n", + obj_name, obj, why); + } + else + { + VALGRIND_PRINTF_BACKTRACE ("%s %p %d -> %d refs (%s)", + obj_name, obj, + old_refcount, new_refcount, why); + _dbus_verbose ("%s %p %d -> %d refs (%s)\n", + obj_name, obj, old_refcount, new_refcount, why); + } + } +} + +#endif /* DBUS_ENABLE_VERBOSE_MODE */ + +/** + * Duplicates a string. Result must be freed with + * dbus_free(). Returns #NULL if memory allocation fails. + * If the string to be duplicated is #NULL, returns #NULL. + * + * @param str string to duplicate. + * @returns newly-allocated copy. + */ +char* +_dbus_strdup (const char *str) +{ + size_t len; + char *copy; + + if (str == NULL) + return NULL; + + len = strlen (str); + + copy = dbus_malloc (len + 1); + if (copy == NULL) + return NULL; + + memcpy (copy, str, len + 1); + + return copy; +} + +/** + * Duplicates a block of memory. Returns + * #NULL on failure. + * + * @param mem memory to copy + * @param n_bytes number of bytes to copy + * @returns the copy + */ +void* +_dbus_memdup (const void *mem, + size_t n_bytes) +{ + void *copy; + + copy = dbus_malloc (n_bytes); + if (copy == NULL) + return NULL; + + memcpy (copy, mem, n_bytes); + + return copy; +} + +/** + * Duplicates a string array. Result may be freed with + * dbus_free_string_array(). Returns #NULL if memory allocation fails. + * If the array to be duplicated is #NULL, returns #NULL. + * + * @param array array to duplicate. + * @returns newly-allocated copy. + */ +char** +_dbus_dup_string_array (const char **array) +{ + int len; + int i; + char **copy; + + if (array == NULL) + return NULL; + + for (len = 0; array[len] != NULL; ++len) + ; + + copy = dbus_new0 (char*, len + 1); + if (copy == NULL) + return NULL; + + i = 0; + while (i < len) + { + copy[i] = _dbus_strdup (array[i]); + if (copy[i] == NULL) + { + dbus_free_string_array (copy); + return NULL; + } + + ++i; + } + + return copy; +} + +/** + * Checks whether a string array contains the given string. + * + * @param array array to search. + * @param str string to look for + * @returns #TRUE if array contains string + */ +dbus_bool_t +_dbus_string_array_contains (const char **array, + const char *str) +{ + int i; + + i = 0; + while (array[i] != NULL) + { + if (strcmp (array[i], str) == 0) + return TRUE; + ++i; + } + + return FALSE; +} + +/** + * Returns the size of a string array + * + * @param array array to search. + * @returns size of array + */ +size_t +_dbus_string_array_length (const char **array) +{ + size_t i; + for (i = 0; array[i]; i++) {} + return i; +} + + +/** + * Generates a new UUID. If you change how this is done, + * there's some text about it in the spec that should also change. + * + * @param uuid the uuid to initialize + * @param error location to store reason for failure + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_generate_uuid (DBusGUID *uuid, + DBusError *error) +{ + DBusError rand_error; + dbus_int64_t now; + + dbus_error_init (&rand_error); + + /* don't use monotonic time because the UUID may be saved to disk, e.g. + * it may persist across reboots + */ + _dbus_get_real_time (&now, NULL); + + uuid->as_uint32s[DBUS_UUID_LENGTH_WORDS - 1] = DBUS_UINT32_TO_BE (now); + + if (!_dbus_generate_random_bytes_buffer (uuid->as_bytes, + DBUS_UUID_LENGTH_BYTES - 4, + &rand_error)) + { + dbus_set_error (error, rand_error.name, + "Failed to generate UUID: %s", rand_error.message); + dbus_error_free (&rand_error); + return FALSE; + } + + return TRUE; +} + +/** + * Hex-encode a UUID. + * + * @param uuid the uuid + * @param encoded string to append hex uuid to + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_uuid_encode (const DBusGUID *uuid, + DBusString *encoded) +{ + DBusString binary; + _dbus_string_init_const_len (&binary, uuid->as_bytes, DBUS_UUID_LENGTH_BYTES); + return _dbus_string_hex_encode (&binary, 0, encoded, _dbus_string_get_length (encoded)); +} + +static dbus_bool_t +_dbus_read_uuid_file_without_creating (const DBusString *filename, + DBusGUID *uuid, + DBusError *error) +{ + DBusString contents; + DBusString decoded; + int end; + + if (!_dbus_string_init (&contents)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_init (&decoded)) + { + _dbus_string_free (&contents); + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_file_get_contents (&contents, filename, error)) + goto error; + + _dbus_string_chop_white (&contents); + + if (_dbus_string_get_length (&contents) != DBUS_UUID_LENGTH_HEX) + { + dbus_set_error (error, DBUS_ERROR_INVALID_FILE_CONTENT, + "UUID file '%s' should contain a hex string of length %d, not length %d, with no other text", + _dbus_string_get_const_data (filename), + DBUS_UUID_LENGTH_HEX, + _dbus_string_get_length (&contents)); + goto error; + } + + if (!_dbus_string_hex_decode (&contents, 0, &end, &decoded, 0)) + { + _DBUS_SET_OOM (error); + goto error; + } + + if (end == 0) + { + dbus_set_error (error, DBUS_ERROR_INVALID_FILE_CONTENT, + "UUID file '%s' contains invalid hex data", + _dbus_string_get_const_data (filename)); + goto error; + } + + if (_dbus_string_get_length (&decoded) != DBUS_UUID_LENGTH_BYTES) + { + dbus_set_error (error, DBUS_ERROR_INVALID_FILE_CONTENT, + "UUID file '%s' contains %d bytes of hex-encoded data instead of %d", + _dbus_string_get_const_data (filename), + _dbus_string_get_length (&decoded), + DBUS_UUID_LENGTH_BYTES); + goto error; + } + + _dbus_string_copy_to_buffer (&decoded, uuid->as_bytes, DBUS_UUID_LENGTH_BYTES); + + _dbus_string_free (&decoded); + _dbus_string_free (&contents); + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + return TRUE; + + error: + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_string_free (&contents); + _dbus_string_free (&decoded); + return FALSE; +} + +/** + * Write the give UUID to a file. + * + * @param filename the file to write + * @param uuid the UUID to save + * @param error used to raise an error + * @returns #FALSE on error + */ +dbus_bool_t +_dbus_write_uuid_file (const DBusString *filename, + const DBusGUID *uuid, + DBusError *error) +{ + DBusString encoded; + + if (!_dbus_string_init (&encoded)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_uuid_encode (uuid, &encoded)) + { + _DBUS_SET_OOM (error); + goto error; + } + + if (!_dbus_string_append_byte (&encoded, '\n')) + { + _DBUS_SET_OOM (error); + goto error; + } + + if (!_dbus_string_save_to_file (&encoded, filename, TRUE, error)) + goto error; + + _dbus_string_free (&encoded); + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return TRUE; + + error: + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_string_free (&encoded); + return FALSE; +} + +/** + * Reads (and optionally writes) a uuid to a file. Initializes the uuid + * unless an error is returned. + * + * @param filename the name of the file + * @param uuid uuid to be initialized with the loaded uuid + * @param create_if_not_found #TRUE to create a new uuid and save it if the file doesn't exist + * @param error the error return + * @returns #FALSE if the error is set + */ +dbus_bool_t +_dbus_read_uuid_file (const DBusString *filename, + DBusGUID *uuid, + dbus_bool_t create_if_not_found, + DBusError *error) +{ + DBusError read_error = DBUS_ERROR_INIT; + + if (_dbus_read_uuid_file_without_creating (filename, uuid, &read_error)) + return TRUE; + + if (!create_if_not_found) + { + dbus_move_error (&read_error, error); + return FALSE; + } + + /* If the file exists and contains junk, we want to keep that error + * message instead of overwriting it with a "file exists" error + * message when we try to write + */ + if (dbus_error_has_name (&read_error, DBUS_ERROR_INVALID_FILE_CONTENT)) + { + dbus_move_error (&read_error, error); + return FALSE; + } + else + { + dbus_error_free (&read_error); + + if (!_dbus_generate_uuid (uuid, error)) + return FALSE; + + return _dbus_write_uuid_file (filename, uuid, error); + } +} + +/* Protected by _DBUS_LOCK (machine_uuid) */ +static int machine_uuid_initialized_generation = 0; +static DBusGUID machine_uuid; + +/** + * Gets the hex-encoded UUID of the machine this function is + * executed on. This UUID is guaranteed to be the same for a given + * machine at least until it next reboots, though it also + * makes some effort to be the same forever, it may change if the + * machine is reconfigured or its hardware is modified. + * + * @param uuid_str string to append hex-encoded machine uuid to + * @param error location to store reason for failure + * @returns #TRUE if successful + */ +dbus_bool_t +_dbus_get_local_machine_uuid_encoded (DBusString *uuid_str, + DBusError *error) +{ + dbus_bool_t ok = TRUE; + + if (!_DBUS_LOCK (machine_uuid)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (machine_uuid_initialized_generation != _dbus_current_generation) + { + if (!_dbus_read_local_machine_uuid (&machine_uuid, FALSE, error)) + ok = FALSE; + } + + if (ok) + { + if (!_dbus_uuid_encode (&machine_uuid, uuid_str)) + { + ok = FALSE; + _DBUS_SET_OOM (error); + } + } + + _DBUS_UNLOCK (machine_uuid); + + return ok; +} + +#ifndef DBUS_DISABLE_CHECKS +void +_dbus_warn_return_if_fail (const char *function, + const char *assertion, + const char *file, + int line) +{ + _dbus_warn_check_failed ( + "arguments to %s() were incorrect, assertion \"%s\" failed in file %s line %d.\n" + "This is normally a bug in some application using the D-Bus library.\n", + function, assertion, file, line); +} +#endif + +#ifndef DBUS_DISABLE_ASSERT +/** + * Internals of _dbus_assert(); it's a function + * rather than a macro with the inline code so + * that the assertion failure blocks don't show up + * in test suite coverage, and to shrink code size. + * + * @param condition TRUE if assertion succeeded + * @param condition_text condition as a string + * @param file file the assertion is in + * @param line line the assertion is in + * @param func function the assertion is in + */ +void +_dbus_real_assert (dbus_bool_t condition, + const char *condition_text, + const char *file, + int line, + const char *func) +{ + if (_DBUS_UNLIKELY (!condition)) + { + _dbus_warn ("assertion failed \"%s\" file \"%s\" line %d function %s", + condition_text, file, line, func); + _dbus_abort (); + } +} + +/** + * Internals of _dbus_assert_not_reached(); it's a function + * rather than a macro with the inline code so + * that the assertion failure blocks don't show up + * in test suite coverage, and to shrink code size. + * + * @param explanation what was reached that shouldn't have been + * @param file file the assertion is in + * @param line line the assertion is in + */ +void +_dbus_real_assert_not_reached (const char *explanation, + const char *file, + int line) +{ + _dbus_warn ("File \"%s\" line %d should not have been reached: %s", + file, line, explanation); + _dbus_abort (); +} +#endif /* DBUS_DISABLE_ASSERT */ + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +static dbus_bool_t +run_failing_each_malloc (int n_mallocs, + const char *description, + DBusTestMemoryFunction func, + void *data) +{ + n_mallocs += 10; /* fudge factor to ensure reallocs etc. are covered */ + + while (n_mallocs >= 0) + { + _dbus_set_fail_alloc_counter (n_mallocs); + + _dbus_test_diag ("%s: will fail malloc %d and %d that follow", + description, n_mallocs, + _dbus_get_fail_alloc_failures () - 1); + + if (!(* func) (data, FALSE)) + return FALSE; + + n_mallocs -= 1; + } + + _dbus_set_fail_alloc_counter (_DBUS_INT_MAX); + + return TRUE; +} + +/** + * Tests how well the given function responds to out-of-memory + * situations. Calls the function repeatedly, failing a different + * call to malloc() each time. If the function ever returns #FALSE, + * the test fails. The function should return #TRUE whenever something + * valid (such as returning an error, or succeeding) occurs, and #FALSE + * if it gets confused in some way. + * + * @param description description of the test used in verbose output + * @param func function to call + * @param data data to pass to function + * @returns #TRUE if the function never returns FALSE + */ +dbus_bool_t +_dbus_test_oom_handling (const char *description, + DBusTestMemoryFunction func, + void *data) +{ + int approx_mallocs; + const char *setting; + int max_failures_to_try; + int i; + + /* Run once to see about how many mallocs are involved */ + + _dbus_set_fail_alloc_counter (_DBUS_INT_MAX); + + _dbus_test_diag ("Running \"%s\" once to count mallocs", description); + + if (!(* func) (data, TRUE)) + return FALSE; + + approx_mallocs = _DBUS_INT_MAX - _dbus_get_fail_alloc_counter (); + + _dbus_test_diag ("\"%s\" has about %d mallocs in total", + description, approx_mallocs); + + setting = _dbus_getenv ("DBUS_TEST_MALLOC_FAILURES"); + + if (setting != NULL) + { + DBusString str; + long v; + _dbus_string_init_const (&str, setting); + v = 4; + if (!_dbus_string_parse_int (&str, 0, &v, NULL)) + _dbus_warn ("couldn't parse '%s' as integer\n", setting); + max_failures_to_try = v; + } + else + { + max_failures_to_try = 4; + } + + if (RUNNING_ON_VALGRIND && _dbus_getenv ("DBUS_TEST_SLOW") == NULL) + { + /* The full OOM testing is slow, valgrind is slow, so the + * combination is just horrible. Only do this if the user + * asked for extra-slow tests. */ + max_failures_to_try = 0; + } + + if (max_failures_to_try < 1) + { + _dbus_test_diag ("not testing OOM handling"); + return TRUE; + } + + _dbus_test_diag ("testing \"%s\" with up to %d consecutive malloc failures", + description, max_failures_to_try); + + i = setting ? max_failures_to_try - 1 : 1; + while (i < max_failures_to_try) + { + _dbus_test_diag ("testing \"%s\" with %d consecutive malloc failures", + description, i + 1); + + _dbus_set_fail_alloc_failures (i); + if (!run_failing_each_malloc (approx_mallocs, description, func, data)) + return FALSE; + ++i; + } + + _dbus_verbose ("\"%s\" coped OK with malloc failures\n", description); + + return TRUE; +} +#endif /* DBUS_ENABLE_EMBEDDED_TESTS */ + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-internals.h b/src/3rdparty/libdbus/dbus/dbus-internals.h new file mode 100644 index 00000000..6266bb0d --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-internals.h @@ -0,0 +1,528 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-internals.h random utility stuff (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifdef DBUS_INSIDE_DBUS_H +#error "You can't include dbus-internals.h in the public header dbus.h" +#endif + +#ifndef DBUS_INTERNALS_H +#define DBUS_INTERNALS_H + +#include <dbus/dbus-memory.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-errors.h> +#include <dbus/dbus-sysdeps.h> +#include <dbus/dbus-macros-internal.h> +#include <dbus/dbus-threads-internal.h> + +DBUS_BEGIN_DECLS + +DBUS_PRIVATE_EXPORT +void _dbus_warn (const char *format, + ...) _DBUS_GNUC_PRINTF (1, 2); + +DBUS_PRIVATE_EXPORT +void _dbus_warn_check_failed (const char *format, + ...) _DBUS_GNUC_PRINTF (1, 2); +DBUS_PRIVATE_EXPORT +void _dbus_warn_return_if_fail (const char *function, + const char *assertion, + const char *file, + int line); + +DBUS_EMBEDDED_TESTS_EXPORT +int _dbus_get_check_failed_count (void); + +#if defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#define _DBUS_FUNCTION_NAME __func__ +#elif defined(__GNUC__) || defined(_MSC_VER) +#define _DBUS_FUNCTION_NAME __FUNCTION__ +#else +#define _DBUS_FUNCTION_NAME "unknown function" +#endif + +/* + * (code from GLib) + * + * The _DBUS_LIKELY and _DBUS_UNLIKELY macros let the programmer give hints to + * the compiler about the expected result of an expression. Some compilers + * can use this information for optimizations. + * + * The _DBUS_BOOLEAN_EXPR macro is intended to trigger a gcc warning when + * putting assignments in the macro arg + */ +#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__) +#define _DBUS_BOOLEAN_EXPR(expr) \ + __extension__ ({ \ + int _dbus_boolean_var_; \ + if (expr) \ + _dbus_boolean_var_ = 1; \ + else \ + _dbus_boolean_var_ = 0; \ + _dbus_boolean_var_; \ +}) +#define _DBUS_LIKELY(expr) (__builtin_expect (_DBUS_BOOLEAN_EXPR(expr), 1)) +#define _DBUS_UNLIKELY(expr) (__builtin_expect (_DBUS_BOOLEAN_EXPR(expr), 0)) +#else +#define _DBUS_LIKELY(expr) (expr) +#define _DBUS_UNLIKELY(expr) (expr) +#endif + +#ifdef DBUS_ENABLE_VERBOSE_MODE + +/* + at least gnu cc and msvc compiler are known to + have support for variable macro argument lists + add other compilers is required +*/ +#if defined(__GNUC__) || defined(_MSC_VER) +#define DBUS_CPP_SUPPORTS_VARIABLE_MACRO_ARGUMENTS +#endif + +#ifdef DBUS_CPP_SUPPORTS_VARIABLE_MACRO_ARGUMENTS +DBUS_PRIVATE_EXPORT +void _dbus_verbose_real (const char *file, const int line, const char *function, + const char *format,...) _DBUS_GNUC_PRINTF (4, 5); +# define _dbus_verbose(fmt,...) _dbus_verbose_real( __FILE__,__LINE__,_DBUS_FUNCTION_NAME,fmt, ## __VA_ARGS__) +#else +DBUS_PRIVATE_EXPORT +void _dbus_verbose_real (const char *format, + ...) _DBUS_GNUC_PRINTF (1, 2); +# define _dbus_verbose _dbus_verbose_real +#endif +DBUS_PRIVATE_EXPORT +void _dbus_verbose_reset_real (void); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_is_verbose_real (void); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_get_verbose (void); +DBUS_PRIVATE_EXPORT +void _dbus_set_verbose (dbus_bool_t state); +void _dbus_verbose_raw (const char *s); + +# define _dbus_verbose_reset _dbus_verbose_reset_real +# define _dbus_is_verbose _dbus_is_verbose_real +#else +# define _dbus_verbose(...) do { } while (0) +# define _dbus_verbose_reset() do { } while (0) +# define _dbus_is_verbose() FALSE +#endif /* !DBUS_ENABLE_VERBOSE_MODE */ + +DBUS_PRIVATE_EXPORT +void _dbus_trace_ref (const char *obj_name, + void *obj, + int old_refcount, + int new_refcount, + const char *why, + const char *env_var, + int *enabled); + +DBUS_PRIVATE_EXPORT +const char* _dbus_strerror (int error_number); + +#ifdef DBUS_DISABLE_ASSERT +#define _dbus_assert(condition) do { } while (0) +#else +DBUS_PRIVATE_EXPORT +void _dbus_real_assert (dbus_bool_t condition, + const char *condition_text, + const char *file, + int line, + const char *func); +#define _dbus_assert(condition) \ + _dbus_real_assert ((condition) != 0, #condition, __FILE__, __LINE__, _DBUS_FUNCTION_NAME) +#endif /* !DBUS_DISABLE_ASSERT */ + +#ifdef DBUS_DISABLE_ASSERT +#define _dbus_assert_not_reached(explanation) do { } while (0) +#else +DBUS_PRIVATE_EXPORT +void _dbus_real_assert_not_reached (const char *explanation, + const char *file, + int line) _DBUS_GNUC_NORETURN; +#define _dbus_assert_not_reached(explanation) \ + _dbus_real_assert_not_reached (explanation, __FILE__, __LINE__) +#endif /* !DBUS_DISABLE_ASSERT */ + +#ifdef DBUS_DISABLE_CHECKS +#define _dbus_return_if_fail(condition) +#define _dbus_return_val_if_fail(condition, val) +#else + +#define _dbus_return_if_fail(condition) do { \ + _dbus_assert ((*(const char*)_DBUS_FUNCTION_NAME) != '_'); \ + if (!(condition)) { \ + _dbus_warn_return_if_fail (_DBUS_FUNCTION_NAME, #condition, __FILE__, __LINE__); \ + return; \ + } } while (0) + +#define _dbus_return_val_if_fail(condition, val) do { \ + _dbus_assert ((*(const char*)_DBUS_FUNCTION_NAME) != '_'); \ + if (!(condition)) { \ + _dbus_warn_return_if_fail (_DBUS_FUNCTION_NAME, #condition, __FILE__, __LINE__); \ + return (val); \ + } } while (0) + +#endif /* !DBUS_DISABLE_ASSERT */ + +#define _DBUS_N_ELEMENTS(array) ((int) (sizeof ((array)) / sizeof ((array)[0]))) + +#define _DBUS_POINTER_TO_INT(pointer) ((intptr_t)(pointer)) +#define _DBUS_INT_TO_POINTER(integer) ((void*)((intptr_t)(integer))) + +#define _DBUS_ZERO(object) (memset (&(object), '\0', sizeof ((object)))) + +#ifdef offsetof +#define _DBUS_STRUCT_OFFSET(struct_type, member) \ + (offsetof (struct_type, member)) +#else +#define _DBUS_STRUCT_OFFSET(struct_type, member) \ + ((intptr_t) ((unsigned char*) &((struct_type*) 0)->member)) +#endif + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__cplusplus) +#define _DBUS_ALIGNOF(type) _Alignof(type) +#else +#define _DBUS_ALIGNOF(type) \ + (_DBUS_STRUCT_OFFSET (struct { char _1; type _2; }, _2)) +#endif + +#if defined(DBUS_DISABLE_CHECKS) || defined(DBUS_DISABLE_ASSERT) +/* this is an assert and not an error, but in the typical --disable-checks case (you're trying + * to really minimize code size), disabling these assertions makes sense. + */ +#define _DBUS_ASSERT_ERROR_IS_SET(error) do { } while (0) +#define _DBUS_ASSERT_ERROR_IS_CLEAR(error) do { } while (0) +#define _DBUS_ASSERT_ERROR_XOR_BOOL(error, retval) do { } while (0) +#else +static inline void +_dbus_assert_error_is_set (const DBusError *error, + const char *file, + int line, + const char *func) +{ + _dbus_real_assert (error == NULL || dbus_error_is_set (error), + "error is set", file, line, func); +} + +static inline void +_dbus_assert_error_is_clear (const DBusError *error, + const char *file, + int line, + const char *func) +{ + _dbus_real_assert (error == NULL || !dbus_error_is_set (error), + "error is clear", file, line, func); +} + +static inline void +_dbus_assert_error_xor_bool (const DBusError *error, + dbus_bool_t retval, + const char *file, + int line, + const char *func) +{ + _dbus_real_assert (error == NULL || dbus_error_is_set (error) == !retval, + "error is consistent with boolean result", file, line, func); +} + +/** + * Assert that error is set, unless it is NULL in which case we cannot + * tell whether it would have been set. + */ +#define _DBUS_ASSERT_ERROR_IS_SET(error) _dbus_assert_error_is_set (error, __FILE__, __LINE__, _DBUS_FUNCTION_NAME) + +/** + * Assert that error is not set, unless it is NULL in which case we cannot + * tell whether it would have been set. + */ +#define _DBUS_ASSERT_ERROR_IS_CLEAR(error) _dbus_assert_error_is_clear (error, __FILE__, __LINE__, _DBUS_FUNCTION_NAME) + +/** + * Assert that error is consistent with retval: if error is not NULL, + * it must be set if and only if retval is false. + * + * retval can be a boolean expression like "result != NULL". + */ +#define _DBUS_ASSERT_ERROR_XOR_BOOL(error, retval) _dbus_assert_error_xor_bool (error, retval, __FILE__, __LINE__, _DBUS_FUNCTION_NAME) +#endif + +#define _dbus_return_if_error_is_set(error) _dbus_return_if_fail ((error) == NULL || !dbus_error_is_set ((error))) +#define _dbus_return_val_if_error_is_set(error, val) _dbus_return_val_if_fail ((error) == NULL || !dbus_error_is_set ((error)), (val)) + +/* This alignment thing is from ORBit2 */ +/* Align a value upward to a boundary, expressed as a number of bytes. + * E.g. align to an 8-byte boundary with argument of 8. + */ + +/* + * (this + boundary - 1) + * & + * ~(boundary - 1) + */ + +#define _DBUS_ALIGN_VALUE(this, boundary) \ + ((((uintptr_t) (this)) + (((size_t) (boundary)) - 1)) & \ + (~(((size_t) (boundary)) - 1))) + +#define _DBUS_ALIGN_ADDRESS(this, boundary) \ + ((void*)_DBUS_ALIGN_VALUE(this, boundary)) + +#define _DBUS_IS_ALIGNED(this, boundary) \ + ((((size_t) (uintptr_t) (this)) & ((size_t) (boundary) - 1)) == 0) + +/** + * Aligning a pointer to _DBUS_ALIGNOF(dbus_max_align_t) guarantees that all + * scalar types can be loaded/stored from/to such an address without incurring + * an alignment fault (or a slow misaligned access). + * This is based on C11 max_align_t, but falls back to DBusBasicValue for + * older C standards. + */ +// #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// typedef max_align_t dbus_max_align_t; +// #else +typedef DBusBasicValue dbus_max_align_t; +// #endif + +DBUS_PRIVATE_EXPORT +char* _dbus_strdup (const char *str); +void* _dbus_memdup (const void *mem, + size_t n_bytes); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_array_contains (const char **array, + const char *str); +DBUS_PRIVATE_EXPORT +size_t _dbus_string_array_length (const char **array); +char** _dbus_dup_string_array (const char **array); + +#define _DBUS_INT16_MIN ((dbus_int16_t) 0x8000) +#define _DBUS_INT16_MAX ((dbus_int16_t) 0x7fff) +#define _DBUS_UINT16_MAX ((dbus_uint16_t)0xffff) +#define _DBUS_INT32_MIN ((dbus_int32_t) 0x80000000) +#define _DBUS_INT32_MAX ((dbus_int32_t) 0x7fffffff) +#define _DBUS_UINT32_MAX ((dbus_uint32_t)0xffffffff) +/* using 32-bit here is sort of bogus */ +#define _DBUS_INT_MIN _DBUS_INT32_MIN +#define _DBUS_INT_MAX _DBUS_INT32_MAX +#define _DBUS_UINT_MAX _DBUS_UINT32_MAX +#define _DBUS_INT64_MAX DBUS_INT64_CONSTANT (0x7fffffffffffffff) +#define _DBUS_UINT64_MAX DBUS_UINT64_CONSTANT (0xffffffffffffffff) +#define _DBUS_ONE_KILOBYTE 1024 +#define _DBUS_ONE_MEGABYTE 1024 * _DBUS_ONE_KILOBYTE +#define _DBUS_ONE_HOUR_IN_MILLISECONDS (1000 * 60 * 60) +#define _DBUS_USEC_PER_SECOND (1000000) + +#undef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#undef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +#undef ABS +#define ABS(a) (((a) < 0) ? -(a) : (a)) + +#define _DBUS_ISASCII(c) ((c) != '\0' && (((c) & ~0x7f) == 0)) + +typedef void (* DBusForeachFunction) (void *element, + void *data); + +void _dbus_verbose_bytes (const unsigned char *data, + int len, + int offset); +DBUS_PRIVATE_EXPORT +void _dbus_verbose_bytes_of_string (const DBusString *str, + int start, + int len); + +DBUS_PRIVATE_EXPORT +extern const char *_dbus_no_memory_message; +#define _DBUS_SET_OOM(error) dbus_set_error_const ((error), DBUS_ERROR_NO_MEMORY, _dbus_no_memory_message) +DBUS_PRIVATE_EXPORT +void _dbus_set_error_valist (DBusError *error, + const char *name, + const char *format, + va_list args) _DBUS_GNUC_PRINTF (3, 0); + +typedef dbus_bool_t (* DBusTestMemoryFunction) (void *data, + dbus_bool_t have_memory); + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +/* Memory debugging */ +void _dbus_set_fail_alloc_counter (int until_next_fail); +int _dbus_get_fail_alloc_counter (void); +void _dbus_set_fail_alloc_failures (int failures_per_failure); +int _dbus_get_fail_alloc_failures (void); +dbus_bool_t _dbus_decrement_fail_alloc_counter (void); +dbus_bool_t _dbus_disable_mem_pools (void); +DBUS_PRIVATE_EXPORT +int _dbus_get_malloc_blocks_outstanding (void); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_test_oom_handling (const char *description, + DBusTestMemoryFunction func, + void *data); +#else +#define _dbus_set_fail_alloc_counter(n) +#define _dbus_get_fail_alloc_counter _DBUS_INT_MAX + +/* These are constant expressions so that blocks + * they protect should be optimized away + */ +#define _dbus_decrement_fail_alloc_counter() (FALSE) +#define _dbus_disable_mem_pools() (FALSE) +#define _dbus_get_malloc_blocks_outstanding() (0) + +#define _dbus_test_oom_handling(description, func, data) ((*func) (data, TRUE)) +#endif /* !DBUS_ENABLE_EMBEDDED_TESTS */ + +typedef void (* DBusShutdownFunction) (void *data); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_register_shutdown_func (DBusShutdownFunction function, + void *data); +dbus_bool_t _dbus_register_shutdown_func_unlocked (DBusShutdownFunction function, + void *data); + +extern int _dbus_current_generation; + +/* The weird case convention is to avoid having to change all the callers, + * which would be quite a mega-patch. */ +typedef enum +{ + /* index 0-4 */ + _DBUS_LOCK_list, + _DBUS_LOCK_connection_slots, + _DBUS_LOCK_pending_call_slots, + _DBUS_LOCK_server_slots, + _DBUS_LOCK_message_slots, + /* index 5-9 */ + _DBUS_LOCK_bus, + _DBUS_LOCK_bus_datas, + _DBUS_LOCK_shutdown_funcs, + _DBUS_LOCK_system_users, + _DBUS_LOCK_message_cache, + /* index 10-12 */ + _DBUS_LOCK_shared_connections, + _DBUS_LOCK_machine_uuid, + _DBUS_LOCK_sysdeps, + + _DBUS_N_GLOBAL_LOCKS +} DBusGlobalLock; + +_DBUS_WARN_UNUSED_RESULT +dbus_bool_t _dbus_lock (DBusGlobalLock lock); +void _dbus_unlock (DBusGlobalLock lock); + +#define _DBUS_LOCK_NAME(name) _DBUS_LOCK_##name +#define _DBUS_LOCK(name) _dbus_lock (_DBUS_LOCK_##name) +#define _DBUS_UNLOCK(name) _dbus_unlock (_DBUS_LOCK_##name) + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_address_append_escaped (DBusString *escaped, + const DBusString *unescaped); + +void _dbus_set_bad_address (DBusError *error, + const char *address_problem_type, + const char *address_problem_field, + const char *address_problem_other); + +#define DBUS_UUID_LENGTH_BYTES 16 +#define DBUS_UUID_LENGTH_WORDS (DBUS_UUID_LENGTH_BYTES / 4) +#define DBUS_UUID_LENGTH_HEX (DBUS_UUID_LENGTH_BYTES * 2) + +/** + * A globally unique ID ; we have one for each DBusServer, and also one for each + * machine with libdbus installed on it. + */ +union DBusGUID +{ + dbus_uint32_t as_uint32s[DBUS_UUID_LENGTH_WORDS]; /**< guid as four uint32 values */ + char as_bytes[DBUS_UUID_LENGTH_BYTES]; /**< guid as 16 single-byte values */ +}; + +DBUS_PRIVATE_EXPORT _DBUS_WARN_UNUSED_RESULT +dbus_bool_t _dbus_generate_uuid (DBusGUID *uuid, + DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_uuid_encode (const DBusGUID *uuid, + DBusString *encoded); +dbus_bool_t _dbus_read_uuid_file (const DBusString *filename, + DBusGUID *uuid, + dbus_bool_t create_if_not_found, + DBusError *error); + +dbus_bool_t _dbus_write_uuid_file (const DBusString *filename, + const DBusGUID *uuid, + DBusError *error); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_get_local_machine_uuid_encoded (DBusString *uuid_str, + DBusError *error); + +#define _DBUS_PASTE2(a, b) a ## b +#define _DBUS_PASTE(a, b) _DBUS_PASTE2 (a, b) +#define _DBUS_STATIC_ASSERT(expr) \ + typedef struct { char _assertion[(expr) ? 1 : -1]; } \ + _DBUS_PASTE (_DBUS_STATIC_ASSERT_, __LINE__) _DBUS_GNUC_UNUSED + +#define _DBUS_STRINGIFY(x) #x +#define _DBUS_FILE_LINE __FILE__ ":" _DBUS_STRINGIFY(__LINE__) + +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +/* MSVC defines __SANITIZE_ADDRESS__, but does not provide the special + * builtins associated with it. */ +#if ((defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)) && \ + !defined(_MSC_VER)) +# include <sanitizer/lsan_interface.h> +/* Defined if we are building with AddressSanitizer */ +# define _DBUS_ADDRESS_SANITIZER +/* Ignore memory allocations until the next _DBUS_END_IGNORE_LEAKS when + * checking for memory leaks */ +# define _DBUS_BEGIN_IGNORE_LEAKS __lsan_disable () +/* End the scope of a previous _DBUS_BEGIN_IGNORE_LEAKS */ +# define _DBUS_END_IGNORE_LEAKS __lsan_enable () +#else +# undef _DBUS_ADDRESS_SANITIZER +# define _DBUS_BEGIN_IGNORE_LEAKS do { } while (0) +# define _DBUS_END_IGNORE_LEAKS do { } while (0) +#endif + +/** @def DBUS_IS_DIR_SEPARATOR(c) + * macro for checking if character c is a path separator + */ +#ifdef DBUS_WIN +#define DBUS_IS_DIR_SEPARATOR(c) (c == '\\' || c == '/') +#define DBUS_DIR_SEPARATOR '\\' +#define DBUS_DIR_SEPARATOR_S "\\" +#else +#define DBUS_IS_DIR_SEPARATOR(c) (c == '/') +#define DBUS_DIR_SEPARATOR '/' +#define DBUS_DIR_SEPARATOR_S "/" +#endif + +DBUS_END_DECLS + +#endif /* DBUS_INTERNALS_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-keyring.c b/src/3rdparty/libdbus/dbus/dbus-keyring.c new file mode 100644 index 00000000..f55b21a8 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-keyring.c @@ -0,0 +1,1155 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-keyring.c Store secret cookies in your homedir + * + * Copyright (C) 2003, 2004 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-keyring.h" +#include "dbus-protocol.h" +#include <dbus/dbus-string.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-sysdeps.h> +#include <dbus/dbus-test-tap.h> + +/** + * @defgroup DBusKeyring keyring class + * @ingroup DBusInternals + * @brief DBusKeyring data structure + * + * Types and functions related to DBusKeyring. DBusKeyring is intended + * to manage cookies used to authenticate clients to servers. This is + * essentially the "verify that client can read the user's homedir" + * authentication mechanism. Both client and server must have access + * to the homedir. + * + * The secret keys are not kept in locked memory, and are written to a + * file in the user's homedir. However they are transient (only used + * by a single server instance for a fixed period of time, then + * discarded). Also, the keys are not sent over the wire. + * + * @todo there's a memory leak on some codepath in here, I saw it once + * when running make check - probably some specific initial cookies + * present in the cookie file, then depending on what we do with them. + */ + +/** + * @defgroup DBusKeyringInternals DBusKeyring implementation details + * @ingroup DBusInternals + * @brief DBusKeyring implementation details + * + * The guts of DBusKeyring. + * + * @{ + */ + +/** The maximum age of a key before we create a new key to use in + * challenges. This isn't super-reliably enforced, since system + * clocks can change or be wrong, but we make a best effort to only + * use keys for a short time. + */ +#define NEW_KEY_TIMEOUT_SECONDS (60*5) +/** + * The time after which we drop a key from the secrets file. + * The EXPIRE_KEYS_TIMEOUT_SECONDS - NEW_KEY_TIMEOUT_SECONDS is the minimum + * time window a client has to complete authentication. + */ +#define EXPIRE_KEYS_TIMEOUT_SECONDS (NEW_KEY_TIMEOUT_SECONDS + (60*2)) +/** + * The maximum amount of time a key can be in the future. + */ +#define MAX_TIME_TRAVEL_SECONDS (60*5) + +/** + * Maximum number of keys in the keyring before + * we just ignore the rest + */ +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +#define MAX_KEYS_IN_FILE 10 +#else +#define MAX_KEYS_IN_FILE 256 +#endif + +/** + * A single key from the cookie file + */ +typedef struct +{ + dbus_int32_t id; /**< identifier used to refer to the key */ + + dbus_int64_t creation_time; /**< when the key was generated, in seconds since 1970-01-01 */ + + DBusString secret; /**< the actual key */ + +} DBusKey; + +/** + * @brief Internals of DBusKeyring. + * + * DBusKeyring internals. DBusKeyring is an opaque object, it must be + * used via accessor functions. + */ +struct DBusKeyring +{ + int refcount; /**< Reference count */ + DBusString directory; /**< Directory the below two items are inside */ + DBusString filename; /**< Keyring filename */ + DBusString filename_lock; /**< Name of lockfile */ + DBusKey *keys; /**< Keys loaded from the file */ + int n_keys; /**< Number of keys */ + DBusCredentials *credentials; /**< Credentials containing user the keyring is for */ +}; + +static DBusKeyring* +_dbus_keyring_new (void) +{ + DBusKeyring *keyring; + + keyring = dbus_new0 (DBusKeyring, 1); + if (keyring == NULL) + goto out_0; + + if (!_dbus_string_init (&keyring->directory)) + goto out_1; + + if (!_dbus_string_init (&keyring->filename)) + goto out_2; + + if (!_dbus_string_init (&keyring->filename_lock)) + goto out_3; + + keyring->refcount = 1; + keyring->keys = NULL; + keyring->n_keys = 0; + + return keyring; + + out_3: + _dbus_string_free (&keyring->filename); + out_2: + _dbus_string_free (&keyring->directory); + out_1: + dbus_free (keyring); + out_0: + return NULL; +} + +static void +free_keys (DBusKey *keys, + int n_keys) +{ + int i; + + /* should be safe for args NULL, 0 */ + + i = 0; + while (i < n_keys) + { + _dbus_string_free (&keys[i].secret); + ++i; + } + + dbus_free (keys); +} + +/* Our locking scheme is highly unreliable. However, there is + * unfortunately no reliable locking scheme in user home directories; + * between bugs in Linux NFS, people using Tru64 or other total crap + * NFS, AFS, random-file-system-of-the-week, and so forth, fcntl() in + * homedirs simply generates tons of bug reports. This has been + * learned through hard experience with GConf, unfortunately. + * + * This bad hack might work better for the kind of lock we have here, + * which we don't expect to hold for any length of time. Crashing + * while we hold it should be unlikely, and timing out such that we + * delete a stale lock should also be unlikely except when the + * filesystem is running really slowly. Stuff might break in corner + * cases but as long as it's not a security-level breakage it should + * be OK. + */ + +/** Maximum number of timeouts waiting for lock before we decide it's stale */ +#define MAX_LOCK_TIMEOUTS 32 +/** Length of each timeout while waiting for a lock */ +#define LOCK_TIMEOUT_MILLISECONDS 250 + +static dbus_bool_t +_dbus_keyring_lock (DBusKeyring *keyring) +{ + int n_timeouts; + + n_timeouts = 0; + while (n_timeouts < MAX_LOCK_TIMEOUTS) + { + DBusError error = DBUS_ERROR_INIT; + + if (_dbus_create_file_exclusively (&keyring->filename_lock, + &error)) + break; + + _dbus_verbose ("Did not get lock file, sleeping %d milliseconds (%s)\n", + LOCK_TIMEOUT_MILLISECONDS, error.message); + dbus_error_free (&error); + + _dbus_sleep_milliseconds (LOCK_TIMEOUT_MILLISECONDS); + + ++n_timeouts; + } + + if (n_timeouts == MAX_LOCK_TIMEOUTS) + { + DBusError error = DBUS_ERROR_INIT; + + _dbus_verbose ("Lock file timed out %d times, assuming stale\n", + n_timeouts); + + if (!_dbus_delete_file (&keyring->filename_lock, &error)) + { + _dbus_verbose ("Couldn't delete old lock file: %s\n", + error.message); + dbus_error_free (&error); + return FALSE; + } + + if (!_dbus_create_file_exclusively (&keyring->filename_lock, + &error)) + { + _dbus_verbose ("Couldn't create lock file after deleting stale one: %s\n", + error.message); + dbus_error_free (&error); + return FALSE; + } + } + + return TRUE; +} + +static void +_dbus_keyring_unlock (DBusKeyring *keyring) +{ + DBusError error = DBUS_ERROR_INIT; + + if (!_dbus_delete_file (&keyring->filename_lock, &error)) + { + _dbus_warn ("Failed to delete lock file: %s", + error.message); + dbus_error_free (&error); + } +} + +static DBusKey* +find_key_by_id (DBusKey *keys, + int n_keys, + int id) +{ + int i; + + i = 0; + while (i < n_keys) + { + if (keys[i].id == id) + return &keys[i]; + + ++i; + } + + return NULL; +} + +static dbus_bool_t +add_new_key (DBusKey **keys_p, + int *n_keys_p, + DBusError *error) +{ + DBusKey *new; + DBusString bytes; + int id; + dbus_int64_t timestamp; + const unsigned char *s; + dbus_bool_t retval; + DBusKey *keys; + int n_keys; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&bytes)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return FALSE; + } + + keys = *keys_p; + n_keys = *n_keys_p; + retval = FALSE; + + /* Generate an integer ID and then the actual key. */ + retry: + + if (!_dbus_generate_random_bytes (&bytes, 4, error)) + goto out; + + s = (const unsigned char*) _dbus_string_get_const_data (&bytes); + + id = s[0] | (s[1] << 8) | (s[2] << 16) | ((s[3] & 0x7f) << 24); + _dbus_assert (id >= 0); + + if (find_key_by_id (keys, n_keys, id) != NULL) + { + _dbus_string_set_length (&bytes, 0); + _dbus_verbose ("Key ID %d already existed, trying another one\n", + id); + goto retry; + } + + _dbus_verbose ("Creating key with ID %d\n", id); + +#define KEY_LENGTH_BYTES 24 + _dbus_string_set_length (&bytes, 0); + if (!_dbus_generate_random_bytes (&bytes, KEY_LENGTH_BYTES, error)) + { + goto out; + } + + new = dbus_realloc (keys, sizeof (DBusKey) * (n_keys + 1)); + if (new == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto out; + } + + keys = new; + *keys_p = keys; /* otherwise *keys_p ends up invalid */ + n_keys += 1; + + if (!_dbus_string_init (&keys[n_keys-1].secret)) + { + n_keys -= 1; /* we don't want to free the one we didn't init */ + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto out; + } + + _dbus_get_real_time (×tamp, NULL); + + keys[n_keys-1].id = id; + keys[n_keys-1].creation_time = timestamp; + if (!_dbus_string_move (&bytes, 0, + &keys[n_keys-1].secret, + 0)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&keys[n_keys-1].secret); + n_keys -= 1; + goto out; + } + + retval = TRUE; + + out: + *n_keys_p = n_keys; + + _dbus_string_free (&bytes); + return retval; +} + +/** + * Reloads the keyring file, optionally adds one new key to the file, + * removes all expired keys from the file iff a key was added, then + * resaves the file. Stores the keys from the file in keyring->keys. + * Note that the file is only resaved (written to) if a key is added, + * this means that only servers ever write to the file and need to + * lock it, which avoids a lot of lock contention at login time and + * such. + * + * @param keyring the keyring + * @param add_new #TRUE to add a new key to the file, expire keys, and resave + * @param error return location for errors + * @returns #FALSE on failure + */ +static dbus_bool_t +_dbus_keyring_reload (DBusKeyring *keyring, + dbus_bool_t add_new, + DBusError *error) +{ + DBusString contents; + DBusString line; + dbus_bool_t retval; + dbus_bool_t have_lock; + DBusKey *keys; + int n_keys; + int i; + dbus_int64_t now; + DBusError tmp_error; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_check_dir_is_private_to_user (&keyring->directory, error)) + return FALSE; + + if (!_dbus_string_init (&contents)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return FALSE; + } + + if (!_dbus_string_init (&line)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&contents); + return FALSE; + } + + keys = NULL; + n_keys = 0; + retval = FALSE; + have_lock = FALSE; + + _dbus_get_real_time (&now, NULL); + + if (add_new) + { + if (!_dbus_keyring_lock (keyring)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Could not lock keyring file to add to it"); + goto out; + } + + have_lock = TRUE; + } + + dbus_error_init (&tmp_error); + if (!_dbus_file_get_contents (&contents, + &keyring->filename, + &tmp_error)) + { + _dbus_verbose ("Failed to load keyring file: %s\n", + tmp_error.message); + /* continue with empty keyring file, so we recreate it */ + dbus_error_free (&tmp_error); + } + + if (!_dbus_string_validate_ascii (&contents, 0, + _dbus_string_get_length (&contents))) + { + _dbus_warn ("Secret keyring file contains non-ASCII! Ignoring existing contents"); + _dbus_string_set_length (&contents, 0); + } + + /* FIXME this is badly inefficient for large keyring files + * (not that large keyring files exist outside of test suites) + */ + while (_dbus_string_pop_line (&contents, &line)) + { + int next; + long val; + int id; + dbus_int64_t timestamp; + int len; + int end; + DBusKey *new; + + /* Don't load more than the max. */ + if (n_keys >= (add_new ? MAX_KEYS_IN_FILE - 1 : MAX_KEYS_IN_FILE)) + break; + + next = 0; + if (!_dbus_string_parse_int (&line, 0, &val, &next)) + { + _dbus_verbose ("could not parse secret key ID at start of line\n"); + continue; + } + + if (val > _DBUS_INT32_MAX || val < 0) + { + _dbus_verbose ("invalid secret key ID at start of line\n"); + continue; + } + + id = val; + + _dbus_string_skip_blank (&line, next, &next); + + if (!_dbus_string_parse_int64 (&line, next, ×tamp, &next)) + { + _dbus_verbose ("could not parse secret key timestamp\n"); + continue; + } + + if (timestamp < 0 || + (now + MAX_TIME_TRAVEL_SECONDS) < timestamp || + (now - EXPIRE_KEYS_TIMEOUT_SECONDS) > timestamp) + { + _dbus_verbose ("dropping/ignoring %" DBUS_INT64_MODIFIER "d-seconds old key with timestamp %" DBUS_INT64_MODIFIER "d as current time is %" DBUS_INT64_MODIFIER "d\n", + now - timestamp, timestamp, now); + continue; + } + + _dbus_string_skip_blank (&line, next, &next); + + len = _dbus_string_get_length (&line); + + if ((len - next) == 0) + { + _dbus_verbose ("no secret key after ID and timestamp\n"); + continue; + } + + /* We have all three parts */ + new = dbus_realloc (keys, sizeof (DBusKey) * (n_keys + 1)); + if (new == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto out; + } + + keys = new; + n_keys += 1; + + if (!_dbus_string_init (&keys[n_keys-1].secret)) + { + n_keys -= 1; /* we don't want to free the one we didn't init */ + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto out; + } + + keys[n_keys-1].id = id; + keys[n_keys-1].creation_time = timestamp; + if (!_dbus_string_hex_decode (&line, next, &end, + &keys[n_keys-1].secret, 0)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto out; + } + + if (_dbus_string_get_length (&line) != end) + { + _dbus_verbose ("invalid hex encoding in keyring file\n"); + _dbus_string_free (&keys[n_keys - 1].secret); + n_keys -= 1; + continue; + } + } + + _dbus_verbose ("Successfully loaded %d existing keys\n", + n_keys); + + if (add_new) + { + if (!add_new_key (&keys, &n_keys, error)) + { + _dbus_verbose ("Failed to generate new key: %s\n", + error ? error->message : "(unknown)"); + goto out; + } + + _dbus_string_set_length (&contents, 0); + + i = 0; + while (i < n_keys) + { + if (!_dbus_string_append_int (&contents, + keys[i].id)) + goto nomem; + + if (!_dbus_string_append_byte (&contents, ' ')) + goto nomem; + + if (!_dbus_string_append_printf (&contents, "%" DBUS_INT64_MODIFIER "d", + keys[i].creation_time)) + goto nomem; + + if (!_dbus_string_append_byte (&contents, ' ')) + goto nomem; + + if (!_dbus_string_hex_encode (&keys[i].secret, 0, + &contents, + _dbus_string_get_length (&contents))) + goto nomem; + + if (!_dbus_string_append_byte (&contents, '\n')) + goto nomem; + + ++i; + continue; + + nomem: + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto out; + } + + if (!_dbus_string_save_to_file (&contents, &keyring->filename, + FALSE, error)) + goto out; + } + + if (keyring->keys) + free_keys (keyring->keys, keyring->n_keys); + keyring->keys = keys; + keyring->n_keys = n_keys; + keys = NULL; + n_keys = 0; + + retval = TRUE; + + out: + if (have_lock) + _dbus_keyring_unlock (keyring); + + if (! ((retval == TRUE && (error == NULL || error->name == NULL)) || + (retval == FALSE && (error == NULL || error->name != NULL)))) + { + if (error && error->name) + _dbus_verbose ("error is %s: %s\n", error->name, error->message); + _dbus_warn ("returning %d but error pointer %p name %s", + retval, error, error->name ? error->name : "(none)"); + _dbus_assert_not_reached ("didn't handle errors properly"); + } + + if (keys != NULL) + { + i = 0; + while (i < n_keys) + { + _dbus_string_zero (&keys[i].secret); + _dbus_string_free (&keys[i].secret); + ++i; + } + + dbus_free (keys); + } + + _dbus_string_free (&contents); + _dbus_string_free (&line); + + return retval; +} + +/** @} */ /* end of internals */ + +/** + * @addtogroup DBusKeyring + * + * @{ + */ + +/** + * Increments reference count of the keyring + * + * @param keyring the keyring + * @returns the keyring + */ +DBusKeyring * +_dbus_keyring_ref (DBusKeyring *keyring) +{ + keyring->refcount += 1; + + return keyring; +} + +/** + * Decrements refcount and finalizes if it reaches + * zero. + * + * @param keyring the keyring + */ +void +_dbus_keyring_unref (DBusKeyring *keyring) +{ + keyring->refcount -= 1; + + if (keyring->refcount == 0) + { + if (keyring->credentials) + _dbus_credentials_unref (keyring->credentials); + + _dbus_string_free (&keyring->filename); + _dbus_string_free (&keyring->filename_lock); + _dbus_string_free (&keyring->directory); + free_keys (keyring->keys, keyring->n_keys); + dbus_free (keyring); + } +} + +/** + * Creates a new keyring that lives in the ~/.dbus-keyrings directory + * of the user represented by @p credentials. If the @p credentials are + * #NULL or empty, uses those of the current process. + * + * @param credentials a set of credentials representing a user or #NULL + * @param context which keyring to get + * @param error return location for errors + * @returns the keyring or #NULL on error + */ +DBusKeyring* +_dbus_keyring_new_for_credentials (DBusCredentials *credentials, + const DBusString *context, + DBusError *error) +{ + DBusString ringdir; + DBusKeyring *keyring; + dbus_bool_t error_set; + DBusError tmp_error; + DBusCredentials *our_credentials; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (_dbus_check_setuid ()) + { + dbus_set_error_const (error, DBUS_ERROR_NOT_SUPPORTED, + "Unable to create DBus keyring when setuid"); + return NULL; + } + + keyring = NULL; + error_set = FALSE; + our_credentials = NULL; + + if (!_dbus_string_init (&ringdir)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + if (credentials != NULL) + { + our_credentials = _dbus_credentials_copy (credentials); + } + else + { + our_credentials = _dbus_credentials_new_from_current_process (); + } + + if (our_credentials == NULL) + goto failed; + + if (_dbus_credentials_are_anonymous (our_credentials)) + { + if (!_dbus_credentials_add_from_current_process (our_credentials)) + goto failed; + } + + if (!_dbus_append_keyring_directory_for_credentials (&ringdir, + our_credentials)) + goto failed; + + keyring = _dbus_keyring_new (); + if (keyring == NULL) + goto failed; + + _dbus_assert (keyring->credentials == NULL); + keyring->credentials = our_credentials; + our_credentials = NULL; /* so we don't unref it again later */ + + /* should have been validated already, but paranoia check here */ + if (!_dbus_keyring_validate_context (context)) + { + error_set = TRUE; + dbus_set_error_const (error, + DBUS_ERROR_FAILED, + "Invalid context in keyring creation"); + goto failed; + } + + /* Save keyring dir in the keyring object */ + if (!_dbus_string_copy (&ringdir, 0, + &keyring->directory, 0)) + goto failed; + + /* Create keyring->filename based on keyring dir and context */ + if (!_dbus_string_copy (&keyring->directory, 0, + &keyring->filename, 0)) + goto failed; + + if (!_dbus_concat_dir_and_file (&keyring->filename, + context)) + goto failed; + + /* Create lockfile name */ + if (!_dbus_string_copy (&keyring->filename, 0, + &keyring->filename_lock, 0)) + goto failed; + + if (!_dbus_string_append (&keyring->filename_lock, ".lock")) + goto failed; + + /* Reload keyring */ + dbus_error_init (&tmp_error); + if (!_dbus_keyring_reload (keyring, FALSE, &tmp_error)) + { + _dbus_verbose ("didn't load an existing keyring: %s\n", + tmp_error.message); + dbus_error_free (&tmp_error); + } + + /* We don't fail fatally if we can't create the directory, + * but the keyring will probably always be empty + * unless someone else manages to create it + */ + dbus_error_init (&tmp_error); + if (!_dbus_ensure_directory (&keyring->directory, + &tmp_error)) + { + _dbus_verbose ("Creating keyring directory: %s\n", + tmp_error.message); + dbus_error_free (&tmp_error); + } + + _dbus_string_free (&ringdir); + + return keyring; + + failed: + if (!error_set) + dbus_set_error_const (error, + DBUS_ERROR_NO_MEMORY, + NULL); + if (our_credentials) + _dbus_credentials_unref (our_credentials); + if (keyring) + _dbus_keyring_unref (keyring); + _dbus_string_free (&ringdir); + return NULL; + +} + +/** + * Checks whether the context is a valid context. + * Contexts that might cause confusion when used + * in filenames are not allowed (contexts can't + * start with a dot or contain dir separators). + * + * @todo this is the most inefficient implementation + * imaginable. + * + * @param context the context + * @returns #TRUE if valid + */ +dbus_bool_t +_dbus_keyring_validate_context (const DBusString *context) +{ + if (_dbus_string_get_length (context) == 0) + { + _dbus_verbose ("context is zero-length\n"); + return FALSE; + } + + if (!_dbus_string_validate_ascii (context, 0, + _dbus_string_get_length (context))) + { + _dbus_verbose ("context not valid ascii\n"); + return FALSE; + } + + /* no directory separators */ + if (_dbus_string_find (context, 0, "/", NULL)) + { + _dbus_verbose ("context contains a slash\n"); + return FALSE; + } + + if (_dbus_string_find (context, 0, "\\", NULL)) + { + _dbus_verbose ("context contains a backslash\n"); + return FALSE; + } + + /* prevent attempts to use dotfiles or ".." or ".lock" + * all of which might allow some kind of attack + */ + if (_dbus_string_find (context, 0, ".", NULL)) + { + _dbus_verbose ("context contains a dot\n"); + return FALSE; + } + + /* no spaces/tabs, those are used for separators in the protocol */ + if (_dbus_string_find_blank (context, 0, NULL)) + { + _dbus_verbose ("context contains a blank\n"); + return FALSE; + } + + if (_dbus_string_find (context, 0, "\n", NULL)) + { + _dbus_verbose ("context contains a newline\n"); + return FALSE; + } + + if (_dbus_string_find (context, 0, "\r", NULL)) + { + _dbus_verbose ("context contains a carriage return\n"); + return FALSE; + } + + return TRUE; +} + +static DBusKey* +find_recent_key (DBusKeyring *keyring) +{ + int i; + dbus_int64_t tv_sec; + long tv_usec; + + _dbus_get_real_time (&tv_sec, &tv_usec); + + i = 0; + while (i < keyring->n_keys) + { + DBusKey *key = &keyring->keys[i]; + + _dbus_verbose ("Key %d is %" DBUS_INT64_MODIFIER "d seconds old\n", + i, tv_sec - key->creation_time); + + if ((tv_sec - NEW_KEY_TIMEOUT_SECONDS) < key->creation_time) + return key; + + ++i; + } + + return NULL; +} + +/** + * Gets a recent key to use for authentication. + * If no recent key exists, creates one. Returns + * the key ID. If a key can't be written to the keyring + * file so no recent key can be created, returns -1. + * All valid keys are > 0. + * + * @param keyring the keyring + * @param error error on failure + * @returns key ID to use for auth, or -1 on failure + */ +int +_dbus_keyring_get_best_key (DBusKeyring *keyring, + DBusError *error) +{ + DBusKey *key; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + key = find_recent_key (keyring); + if (key) + return key->id; + + /* All our keys are too old, or we've never loaded the + * keyring. Create a new one. + */ + if (!_dbus_keyring_reload (keyring, TRUE, + error)) + return -1; + + key = find_recent_key (keyring); + if (key) + return key->id; + else + { + dbus_set_error_const (error, + DBUS_ERROR_FAILED, + "No recent-enough key found in keyring, and unable to create a new key"); + return -1; + } +} + +/** + * Checks whether the keyring is for the same user as the given credentials. + * + * @param keyring the keyring + * @param credentials the credentials to check + * + * @returns #TRUE if the keyring belongs to the given user + */ +dbus_bool_t +_dbus_keyring_is_for_credentials (DBusKeyring *keyring, + DBusCredentials *credentials) +{ + return _dbus_credentials_same_user (keyring->credentials, + credentials); +} + +/** + * Gets the hex-encoded secret key for the given ID. + * Returns #FALSE if not enough memory. Returns #TRUE + * but empty key on any other error such as unknown + * key ID. + * + * @param keyring the keyring + * @param key_id the key ID + * @param hex_key string to append hex-encoded key to + * @returns #TRUE if we had enough memory + */ +dbus_bool_t +_dbus_keyring_get_hex_key (DBusKeyring *keyring, + int key_id, + DBusString *hex_key) +{ + DBusKey *key; + + key = find_key_by_id (keyring->keys, + keyring->n_keys, + key_id); + if (key == NULL) + return TRUE; /* had enough memory, so TRUE */ + + return _dbus_string_hex_encode (&key->secret, 0, + hex_key, + _dbus_string_get_length (hex_key)); +} + +/** @} */ /* end of exposed API */ + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +#include "dbus-test.h" +#include <stdio.h> + +dbus_bool_t +_dbus_keyring_test (const char *test_data_dir _DBUS_GNUC_UNUSED) +{ + DBusString context; + DBusKeyring *ring1; + DBusKeyring *ring2; + int id; + DBusError error; + int i; + + ring1 = NULL; + ring2 = NULL; + + /* Context validation */ + + _dbus_string_init_const (&context, "foo"); + _dbus_assert (_dbus_keyring_validate_context (&context)); + _dbus_string_init_const (&context, "org_freedesktop_blah"); + _dbus_assert (_dbus_keyring_validate_context (&context)); + + _dbus_string_init_const (&context, ""); + _dbus_assert (!_dbus_keyring_validate_context (&context)); + _dbus_string_init_const (&context, ".foo"); + _dbus_assert (!_dbus_keyring_validate_context (&context)); + _dbus_string_init_const (&context, "bar.foo"); + _dbus_assert (!_dbus_keyring_validate_context (&context)); + _dbus_string_init_const (&context, "bar/foo"); + _dbus_assert (!_dbus_keyring_validate_context (&context)); + _dbus_string_init_const (&context, "bar\\foo"); + _dbus_assert (!_dbus_keyring_validate_context (&context)); + _dbus_string_init_const (&context, "foo\xfa\xf0"); + _dbus_assert (!_dbus_keyring_validate_context (&context)); + _dbus_string_init_const (&context, "foo\x80"); + _dbus_assert (!_dbus_keyring_validate_context (&context)); + _dbus_string_init_const (&context, "foo\x7f"); + _dbus_assert (_dbus_keyring_validate_context (&context)); + _dbus_string_init_const (&context, "foo bar"); + _dbus_assert (!_dbus_keyring_validate_context (&context)); + + if (!_dbus_string_init (&context)) + _dbus_test_fatal ("no memory"); + if (!_dbus_string_append_byte (&context, '\0')) + _dbus_test_fatal ("no memory"); + _dbus_assert (!_dbus_keyring_validate_context (&context)); + _dbus_string_free (&context); + + /* Now verify that if we create a key in keyring 1, + * it is properly loaded in keyring 2 + */ + + _dbus_string_init_const (&context, "org_freedesktop_dbus_testsuite"); + dbus_error_init (&error); + ring1 = _dbus_keyring_new_for_credentials (NULL, &context, + &error); + _dbus_assert (ring1 != NULL); + _dbus_assert (error.name == NULL); + + id = _dbus_keyring_get_best_key (ring1, &error); + if (id < 0) + { + fprintf (stderr, "Could not load keyring: %s\n", error.message); + dbus_error_free (&error); + goto failure; + } + + ring2 = _dbus_keyring_new_for_credentials (NULL, &context, &error); + _dbus_assert (ring2 != NULL); + _dbus_assert (error.name == NULL); + + if (ring1->n_keys != ring2->n_keys) + { + fprintf (stderr, "Different number of keys in keyrings\n"); + goto failure; + } + + /* We guarantee we load and save keeping keys in a fixed + * order + */ + i = 0; + while (i < ring1->n_keys) + { + if (ring1->keys[i].id != ring2->keys[i].id) + { + fprintf (stderr, "Keyring 1 has first key ID %d and keyring 2 has %d\n", + ring1->keys[i].id, ring2->keys[i].id); + goto failure; + } + + if (ring1->keys[i].creation_time != ring2->keys[i].creation_time) + { + fprintf (stderr, "Keyring 1 has first key time %" DBUS_INT64_MODIFIER "d and keyring 2 has %" DBUS_INT64_MODIFIER "d\n", + ring1->keys[i].creation_time, ring2->keys[i].creation_time); + goto failure; + } + + if (!_dbus_string_equal (&ring1->keys[i].secret, + &ring2->keys[i].secret)) + { + fprintf (stderr, "Keyrings 1 and 2 have different secrets for same ID/timestamp\n"); + goto failure; + } + + ++i; + } + + _dbus_test_diag (" %d keys in test", ring1->n_keys); + + /* Test ref/unref */ + _dbus_keyring_ref (ring1); + _dbus_keyring_ref (ring2); + _dbus_keyring_unref (ring1); + _dbus_keyring_unref (ring2); + + + /* really unref */ + _dbus_keyring_unref (ring1); + _dbus_keyring_unref (ring2); + + return TRUE; + + failure: + if (ring1) + _dbus_keyring_unref (ring1); + if (ring2) + _dbus_keyring_unref (ring2); + + return FALSE; +} + +#endif /* DBUS_ENABLE_EMBEDDED_TESTS */ + diff --git a/src/3rdparty/libdbus/dbus/dbus-keyring.h b/src/3rdparty/libdbus/dbus/dbus-keyring.h new file mode 100644 index 00000000..fe122b8e --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-keyring.h @@ -0,0 +1,54 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-keyring.h Store secret cookies in your homedir + * + * Copyright (C) 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_KEYRING_H +#define DBUS_KEYRING_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-errors.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-credentials.h> + +DBUS_BEGIN_DECLS + +typedef struct DBusKeyring DBusKeyring; + +DBusKeyring* _dbus_keyring_new_for_credentials (DBusCredentials *credentials, + const DBusString *context, + DBusError *error); +DBusKeyring* _dbus_keyring_ref (DBusKeyring *keyring); +void _dbus_keyring_unref (DBusKeyring *keyring); +dbus_bool_t _dbus_keyring_validate_context (const DBusString *context); +int _dbus_keyring_get_best_key (DBusKeyring *keyring, + DBusError *error); +dbus_bool_t _dbus_keyring_is_for_credentials (DBusKeyring *keyring, + DBusCredentials *credentials); +dbus_bool_t _dbus_keyring_get_hex_key (DBusKeyring *keyring, + int key_id, + DBusString *hex_key); + + +DBUS_END_DECLS + +#endif /* DBUS_KEYRING_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-list.c b/src/3rdparty/libdbus/dbus/dbus-list.c new file mode 100644 index 00000000..b3b76c75 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-list.c @@ -0,0 +1,819 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-list.c Generic linked list utility (internal to D-Bus implementation) + * + * Copyright (C) 2002 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-list.h" +#include "dbus-mempool.h" +#include "dbus-threads-internal.h" +#include <dbus/dbus-test-tap.h> + +/** + * @defgroup DBusList Linked list + * @ingroup DBusInternals + * @brief DBusList data structure + * + * Types and functions related to DBusList. + */ + +/* Protected by _DBUS_LOCK (list) */ +static DBusMemPool *list_pool; + +/** + * @defgroup DBusListInternals Linked list implementation details + * @ingroup DBusInternals + * @brief DBusList implementation details + * + * The guts of DBusList. + * + * @{ + */ + +/* the mem pool is probably a speed hit, with the thread + * lock, though it does still save memory - unknown. + */ +static DBusList* +alloc_link (void *data) +{ + DBusList *link; + + if (!_DBUS_LOCK (list)) + return FALSE; + + if (list_pool == NULL) + { + list_pool = _dbus_mem_pool_new (sizeof (DBusList), TRUE); + + if (list_pool == NULL) + { + _DBUS_UNLOCK (list); + return NULL; + } + + link = _dbus_mem_pool_alloc (list_pool); + if (link == NULL) + { + _dbus_mem_pool_free (list_pool); + list_pool = NULL; + _DBUS_UNLOCK (list); + return NULL; + } + } + else + { + link = _dbus_mem_pool_alloc (list_pool); + } + + if (link) + link->data = data; + + _DBUS_UNLOCK (list); + + return link; +} + +static void +free_link (DBusList *link) +{ + if (!_DBUS_LOCK (list)) + _dbus_assert_not_reached ("we should have initialized global locks " + "before we allocated a linked-list link"); + + if (_dbus_mem_pool_dealloc (list_pool, link)) + { + _dbus_mem_pool_free (list_pool); + list_pool = NULL; + } + + _DBUS_UNLOCK (list); +} + +static void +link_before (DBusList **list, + DBusList *before_this_link, + DBusList *link) +{ + if (*list == NULL) + { + link->prev = link; + link->next = link; + *list = link; + } + else + { + link->next = before_this_link; + link->prev = before_this_link->prev; + before_this_link->prev = link; + link->prev->next = link; + + if (before_this_link == *list) + *list = link; + } +} + +static void +link_after (DBusList **list, + DBusList *after_this_link, + DBusList *link) +{ + if (*list == NULL) + { + link->prev = link; + link->next = link; + *list = link; + } + else + { + link->prev = after_this_link; + link->next = after_this_link->next; + after_this_link->next = link; + link->next->prev = link; + } +} + +#ifdef DBUS_ENABLE_STATS +void +_dbus_list_get_stats (dbus_uint32_t *in_use_p, + dbus_uint32_t *in_free_list_p, + dbus_uint32_t *allocated_p) +{ + if (!_DBUS_LOCK (list)) + { + *in_use_p = 0; + *in_free_list_p = 0; + *allocated_p = 0; + return; + } + + _dbus_mem_pool_get_stats (list_pool, in_use_p, in_free_list_p, allocated_p); + _DBUS_UNLOCK (list); +} +#endif + +/** @} */ + +/** + * @addtogroup DBusList + * @{ + */ + +/** + * @struct DBusList + * + * A node in a linked list. + * + * DBusList is a circular list; that is, the tail of the list + * points back to the head of the list. The empty list is + * represented by a #NULL pointer. + */ + +/** + * @def _dbus_list_get_next_link + * + * Gets the next link in the list, or #NULL if + * there are no more links. Used for iteration. + * + * @code + * DBusList *link; + * link = _dbus_list_get_first_link (&list); + * while (link != NULL) + * { + * printf ("value is %p\n", link->data); + * link = _dbus_list_get_next_link (&link); + * } + * @endcode + * + * @param list address of the list head. + * @param link current link. + * @returns the next link, or %NULL if none. + * + */ + +/** + * @def _dbus_list_get_prev_link + * + * Gets the previous link in the list, or #NULL if + * there are no more links. Used for iteration. + * + * @code + * DBusList *link; + * link = _dbus_list_get_last_link (&list); + * while (link != NULL) + * { + * printf ("value is %p\n", link->data); + * link = _dbus_list_get_prev_link (&link); + * } + * @endcode + * + * @param list address of the list head. + * @param link current link. + * @returns the previous link, or %NULL if none. + * + */ + +/** + * Allocates a linked list node. Useful for preallocating + * nodes and using _dbus_list_append_link() to avoid + * allocations. + * + * @param data the value to store in the link. + * @returns a newly allocated link. + */ +DBusList* +_dbus_list_alloc_link (void *data) +{ + return alloc_link (data); +} + +/** + * Frees a linked list node allocated with _dbus_list_alloc_link. + * Does not free the data in the node. + * + * @param link the list node + */ +void +_dbus_list_free_link (DBusList *link) +{ + free_link (link); +} + + +/** + * Appends a value to the list. May return #FALSE + * if insufficient memory exists to add a list link. + * This is a constant-time operation. + * + * @param list address of the list head. + * @param data the value to append. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_list_append (DBusList **list, + void *data) +{ + if (!_dbus_list_prepend (list, data)) + return FALSE; + + /* Now cycle the list forward one so the prepended node is the tail */ + *list = (*list)->next; + + return TRUE; +} + +/** + * Prepends a value to the list. May return #FALSE + * if insufficient memory exists to add a list link. + * This is a constant-time operation. + * + * @param list address of the list head. + * @param data the value to prepend. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_list_prepend (DBusList **list, + void *data) +{ + DBusList *link; + + link = alloc_link (data); + if (link == NULL) + return FALSE; + + link_before (list, *list, link); + + return TRUE; +} + +/** + * Appends a link to the list. + * Cannot fail due to out of memory. + * This is a constant-time operation. + * + * @param list address of the list head. + * @param link the link to append. + */ +void +_dbus_list_append_link (DBusList **list, + DBusList *link) +{ + _dbus_list_prepend_link (list, link); + + /* Now cycle the list forward one so the prepended node is the tail */ + *list = (*list)->next; +} + +/** + * Prepends a link to the list. + * Cannot fail due to out of memory. + * This is a constant-time operation. + * + * @param list address of the list head. + * @param link the link to prepend. + */ +void +_dbus_list_prepend_link (DBusList **list, + DBusList *link) +{ + link_before (list, *list, link); +} + +/** + * Inserts data into the list after the given existing link. + * + * @param list the list to modify + * @param after_this_link existing link to insert after, or #NULL to prepend + * @param data the value to insert + * @returns #TRUE on success, #FALSE if memory allocation fails + */ +dbus_bool_t +_dbus_list_insert_after (DBusList **list, + DBusList *after_this_link, + void *data) +{ + DBusList *link; + + if (after_this_link == NULL) + return _dbus_list_prepend (list, data); + else + { + link = alloc_link (data); + if (link == NULL) + return FALSE; + + link_after (list, after_this_link, link); + } + + return TRUE; +} + +/** + * Inserts a link into the list before the given existing link. + * + * @param list the list to modify + * @param before_this_link existing link to insert before, or #NULL to append + * @param link the link to insert + */ +void +_dbus_list_insert_before_link (DBusList **list, + DBusList *before_this_link, + DBusList *link) +{ + if (before_this_link == NULL) + _dbus_list_append_link (list, link); + else + link_before (list, before_this_link, link); +} + +/** + * Inserts a link into the list after the given existing link. + * + * @param list the list to modify + * @param after_this_link existing link to insert after, or #NULL to prepend + * @param link the link to insert + */ +void +_dbus_list_insert_after_link (DBusList **list, + DBusList *after_this_link, + DBusList *link) +{ + if (after_this_link == NULL) + _dbus_list_prepend_link (list, link); + else + link_after (list, after_this_link, link); +} + +/** + * Removes a value from the list. Only removes the + * first value equal to the given data pointer, + * even if multiple values exist which match. + * This is a linear-time operation. + * + * @param list address of the list head. + * @param data the value to remove. + * @returns #TRUE if a value was found to remove. + */ +dbus_bool_t +_dbus_list_remove (DBusList **list, + void *data) +{ + DBusList *link; + + link = *list; + while (link != NULL) + { + if (link->data == data) + { + _dbus_list_remove_link (list, link); + return TRUE; + } + + link = _dbus_list_get_next_link (list, link); + } + + return FALSE; +} + +/** + * Removes a value from the list. Only removes the + * last value equal to the given data pointer, + * even if multiple values exist which match. + * This is a linear-time operation. + * + * @param list address of the list head. + * @param data the value to remove. + * @returns #TRUE if a value was found to remove. + */ +dbus_bool_t +_dbus_list_remove_last (DBusList **list, + void *data) +{ + DBusList *link; + + link = _dbus_list_find_last (list, data); + if (link) + { + _dbus_list_remove_link (list, link); + return TRUE; + } + else + return FALSE; +} + +/** + * Finds a value in the list. Returns the last link + * with value equal to the given data pointer. + * This is a linear-time operation. + * Returns #NULL if no value found that matches. + * + * @param list address of the list head. + * @param data the value to find. + * @returns the link if found + */ +DBusList* +_dbus_list_find_last (DBusList **list, + void *data) +{ + DBusList *link; + + link = _dbus_list_get_last_link (list); + + while (link != NULL) + { + if (link->data == data) + return link; + + link = _dbus_list_get_prev_link (list, link); + } + + return NULL; +} + +/** + * Removes the given link from the list, but doesn't + * free it. _dbus_list_remove_link() both removes the + * link and also frees it. + * + * @param list the list + * @param link the link in the list + */ +void +_dbus_list_unlink (DBusList **list, + DBusList *link) +{ + if (link->next == link) + { + /* one-element list */ + *list = NULL; + } + else + { + link->prev->next = link->next; + link->next->prev = link->prev; + + if (*list == link) + *list = link->next; + } + + link->next = NULL; + link->prev = NULL; +} + +/** + * Removes a link from the list. This is a constant-time operation. + * + * @param list address of the list head. + * @param link the list link to remove. + */ +void +_dbus_list_remove_link (DBusList **list, + DBusList *link) +{ + _dbus_list_unlink (list, link); + free_link (link); +} + +/** + * Frees all links in the list and sets the list head to #NULL. Does + * not free the data in each link, for obvious reasons. This is a + * linear-time operation. + * + * @param list address of the list head. + */ +void +_dbus_list_clear (DBusList **list) +{ + DBusList *link; + + link = *list; + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (list, link); + + free_link (link); + + link = next; + } + + *list = NULL; +} + +/** + * Free every link and every element in the list. + * + * @param list address of the head of the list. + * @param function free-function to call for each element. + * + */ +void +_dbus_list_clear_full (DBusList **list, + DBusFreeFunction function) +{ + DBusList *link; + + link = *list; + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (list, link); + + function (link->data); + free_link (link); + + link = next; + } + + *list = NULL; +} + +/** + * Gets the first link in the list. + * This is a constant-time operation. + * + * @param list address of the list head. + * @returns the first link, or #NULL for an empty list. + */ +DBusList* +_dbus_list_get_first_link (DBusList **list) +{ + return *list; +} + +/** + * Gets the last link in the list. + * This is a constant-time operation. + * + * @param list address of the list head. + * @returns the last link, or #NULL for an empty list. + */ +DBusList* +_dbus_list_get_last_link (DBusList **list) +{ + if (*list == NULL) + return NULL; + else + return (*list)->prev; +} + +/** + * Gets the last data in the list. + * This is a constant-time operation. + * + * @param list address of the list head. + * @returns the last data in the list, or #NULL for an empty list. + */ +void* +_dbus_list_get_last (DBusList **list) +{ + if (*list == NULL) + return NULL; + else + return (*list)->prev->data; +} + +/** + * Gets the first data in the list. + * This is a constant-time operation. + * + * @param list address of the list head. + * @returns the first data in the list, or #NULL for an empty list. + */ +void* +_dbus_list_get_first (DBusList **list) +{ + if (*list == NULL) + return NULL; + else + return (*list)->data; +} + +/** + * Removes the first link in the list and returns it. This is a + * constant-time operation. + * + * @param list address of the list head. + * @returns the first link in the list, or #NULL for an empty list. + */ +DBusList* +_dbus_list_pop_first_link (DBusList **list) +{ + DBusList *link; + + link = _dbus_list_get_first_link (list); + if (link == NULL) + return NULL; + + _dbus_list_unlink (list, link); + + return link; +} + +/** + * Removes the first value in the list and returns it. This is a + * constant-time operation. + * + * @param list address of the list head. + * @returns the first data in the list, or #NULL for an empty list. + */ +void* +_dbus_list_pop_first (DBusList **list) +{ + DBusList *link; + void *data; + + link = _dbus_list_get_first_link (list); + if (link == NULL) + return NULL; + + data = link->data; + _dbus_list_remove_link (list, link); + + return data; +} + +/** + * Removes the last value in the list and returns it. This is a + * constant-time operation. + * + * @param list address of the list head. + * @returns the last data in the list, or #NULL for an empty list. + */ +void* +_dbus_list_pop_last (DBusList **list) +{ + DBusList *link; + void *data; + + link = _dbus_list_get_last_link (list); + if (link == NULL) + return NULL; + + data = link->data; + _dbus_list_remove_link (list, link); + + return data; +} + +/** + * Copies a list. This is a linear-time operation. If there isn't + * enough memory to copy the entire list, the destination list will be + * set to #NULL. + * + * @param list address of the head of the list to copy. + * @param dest address where the copied list should be placed. + * @returns #TRUE on success, #FALSE if not enough memory. + */ +dbus_bool_t +_dbus_list_copy (DBusList **list, + DBusList **dest) +{ + DBusList *link; + + _dbus_assert (list != dest); + + *dest = NULL; + + link = *list; + while (link != NULL) + { + if (!_dbus_list_append (dest, link->data)) + { + /* free what we have so far */ + _dbus_list_clear (dest); + return FALSE; + } + + link = _dbus_list_get_next_link (list, link); + } + + return TRUE; +} + +/** + * Gets the length of a list. This is a linear-time + * operation. + * + * @param list address of the head of the list + * @returns number of elements in the list. + */ +int +_dbus_list_get_length (DBusList **list) +{ + DBusList *link; + int length; + + length = 0; + + link = *list; + while (link != NULL) + { + ++length; + + link = _dbus_list_get_next_link (list, link); + } + + return length; +} + +/** + * Calls the given function for each element in the list. The + * function is passed the list element as its first argument, and the + * given data as its second argument. + * + * @param list address of the head of the list. + * @param function function to call for each element. + * @param data extra data for the function. + * + */ +void +_dbus_list_foreach (DBusList **list, + DBusForeachFunction function, + void *data) +{ + DBusList *link; + + link = *list; + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (list, link); + + (* function) (link->data, data); + + link = next; + } +} + +/** + * Check whether length is exactly one. + * + * @param list the list + * @returns #TRUE if length is exactly one + */ +dbus_bool_t +_dbus_list_length_is_one (DBusList **list) +{ + return (*list != NULL && + (*list)->next == *list); +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-list.h b/src/3rdparty/libdbus/dbus/dbus-list.h new file mode 100644 index 00000000..49217df2 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-list.h @@ -0,0 +1,132 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-list.h Generic linked list utility (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_LIST_H +#define DBUS_LIST_H + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-memory.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-sysdeps.h> + +DBUS_BEGIN_DECLS + +struct DBusList +{ + DBusList *prev; /**< Previous list node. */ + DBusList *next; /**< Next list node. */ + void *data; /**< Data stored at this element. */ +}; +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_list_append (DBusList **list, + void *data); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_list_prepend (DBusList **list, + void *data); +dbus_bool_t _dbus_list_insert_before (DBusList **list, + DBusList *before_this_link, + void *data); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_list_insert_after (DBusList **list, + DBusList *after_this_link, + void *data); +DBUS_PRIVATE_EXPORT +void _dbus_list_insert_before_link (DBusList **list, + DBusList *before_this_link, + DBusList *link); +DBUS_PRIVATE_EXPORT +void _dbus_list_insert_after_link (DBusList **list, + DBusList *after_this_link, + DBusList *link); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_list_remove (DBusList **list, + void *data); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_list_remove_last (DBusList **list, + void *data); +DBUS_PRIVATE_EXPORT +void _dbus_list_remove_link (DBusList **list, + DBusList *link); +DBUS_PRIVATE_EXPORT +DBusList* _dbus_list_find_last (DBusList **list, + void *data); +DBUS_PRIVATE_EXPORT +void _dbus_list_clear (DBusList **list); +DBUS_PRIVATE_EXPORT +void _dbus_list_clear_full (DBusList **list, + DBusFreeFunction function); +DBUS_PRIVATE_EXPORT +DBusList* _dbus_list_get_first_link (DBusList **list); +DBUS_PRIVATE_EXPORT +DBusList* _dbus_list_get_last_link (DBusList **list); +DBUS_PRIVATE_EXPORT +void* _dbus_list_get_last (DBusList **list); +DBUS_PRIVATE_EXPORT +void* _dbus_list_get_first (DBusList **list); +DBUS_PRIVATE_EXPORT +void* _dbus_list_pop_first (DBusList **list); +DBUS_PRIVATE_EXPORT +void* _dbus_list_pop_last (DBusList **list); +DBUS_PRIVATE_EXPORT +DBusList* _dbus_list_pop_first_link (DBusList **list); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_list_copy (DBusList **list, + DBusList **dest); +DBUS_PRIVATE_EXPORT +int _dbus_list_get_length (DBusList **list); +DBUS_PRIVATE_EXPORT +DBusList* _dbus_list_alloc_link (void *data); +DBUS_PRIVATE_EXPORT +void _dbus_list_free_link (DBusList *link); +DBUS_PRIVATE_EXPORT +void _dbus_list_unlink (DBusList **list, + DBusList *link); +DBUS_PRIVATE_EXPORT +void _dbus_list_append_link (DBusList **list, + DBusList *link); +DBUS_PRIVATE_EXPORT +void _dbus_list_prepend_link (DBusList **list, + DBusList *link); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_list_length_is_one (DBusList **list); + + +DBUS_PRIVATE_EXPORT +void _dbus_list_foreach (DBusList **list, + DBusForeachFunction function, + void *data); + +#define _dbus_list_get_next_link(list, link) ((link)->next == *(list) ? NULL : (link)->next) +#define _dbus_list_get_prev_link(list, link) ((link) == *(list) ? NULL : (link)->prev) + +/* if DBUS_ENABLE_STATS */ +DBUS_PRIVATE_EXPORT +void _dbus_list_get_stats (dbus_uint32_t *in_use_p, + dbus_uint32_t *in_free_list_p, + dbus_uint32_t *allocated_p); + +DBUS_END_DECLS + +#endif /* DBUS_LIST_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-macros-internal.h b/src/3rdparty/libdbus/dbus/dbus-macros-internal.h new file mode 100644 index 00000000..3d7c0683 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-macros-internal.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2010-2015 Ralf Habacker + * Copyright © 2015-2019 Collabora Ltd. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifdef DBUS_INSIDE_DBUS_H +#error "You can't include dbus-macros-internal.h in the public header dbus.h" +#endif + +#ifndef DBUS_MACROS_INTERNAL_H +#define DBUS_MACROS_INTERNAL_H + +#include <dbus/dbus-macros.h> + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +# define DBUS_EMBEDDED_TESTS_EXPORT DBUS_PRIVATE_EXPORT +#else +# define DBUS_EMBEDDED_TESTS_EXPORT /* nothing */ +#endif + +#if defined(DBUS_PRIVATE_EXPORT) + /* value forced by compiler command line, don't redefine */ +#elif defined(_WIN32) +# if defined(DBUS_STATIC_BUILD) +# define DBUS_PRIVATE_EXPORT /* no decoration */ +# elif defined(dbus_1_EXPORTS) +# define DBUS_PRIVATE_EXPORT __declspec(dllexport) +# else +# define DBUS_PRIVATE_EXPORT __declspec(dllimport) +# endif +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define DBUS_PRIVATE_EXPORT __attribute__ ((__visibility__ ("default"))) +#else +# define DBUS_PRIVATE_EXPORT /* no decoration */ +#endif + +#endif diff --git a/src/3rdparty/libdbus/dbus/dbus-macros.h b/src/3rdparty/libdbus/dbus/dbus-macros.h new file mode 100644 index 00000000..5ca7bf0a --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-macros.h @@ -0,0 +1,237 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-macros.h generic macros + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_MACROS_H +#define DBUS_MACROS_H + +#ifdef __cplusplus +# define DBUS_BEGIN_DECLS extern "C" { +# define DBUS_END_DECLS } +#else +# define DBUS_BEGIN_DECLS +# define DBUS_END_DECLS +#endif + +#ifndef TRUE +# define TRUE 1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif + +#ifndef NULL +# ifdef __cplusplus +# define NULL (0L) +# else /* !__cplusplus */ +# define NULL ((void*) 0) +# endif /* !__cplusplus */ +#endif + +#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) +# define DBUS_DEPRECATED __attribute__ ((__deprecated__)) +#elif defined(_MSC_VER) && (_MSC_VER >= 1300) +# define DBUS_DEPRECATED __declspec(deprecated) +#else +# define DBUS_DEPRECATED +#endif + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8) +# define _DBUS_GNUC_EXTENSION __extension__ +#else +# define _DBUS_GNUC_EXTENSION +#endif + +#if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)) || \ + defined(__clang__) +#define _DBUS_GNUC_PRINTF( format_idx, arg_idx ) \ + __attribute__((__format__ (__printf__, format_idx, arg_idx))) +#define _DBUS_GNUC_NORETURN \ + __attribute__((__noreturn__)) +#define _DBUS_GNUC_UNUSED \ + __attribute__((__unused__)) +#else /* !__GNUC__ */ +#define _DBUS_GNUC_PRINTF( format_idx, arg_idx ) +#define _DBUS_GNUC_NORETURN +#define _DBUS_GNUC_UNUSED +#endif /* !__GNUC__ */ + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96) +#define DBUS_MALLOC __attribute__((__malloc__)) +#else +#define DBUS_MALLOC +#endif + +#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3) +#define DBUS_ALLOC_SIZE(x) __attribute__((__alloc_size__(x))) +#define DBUS_ALLOC_SIZE2(x,y) __attribute__((__alloc_size__(x,y))) +#else +#define DBUS_ALLOC_SIZE(x) +#define DBUS_ALLOC_SIZE2(x,y) +#endif + +/** @def _DBUS_WARN_UNUSED_RESULT + * + * An attribute for functions whose result must be checked by the caller. + * + * This macro is used in function declarations. Unlike gcc-specific + * attributes, to avoid compilation failure with MSVC it must appear + * somewhere before the function name in the declaration. Our preferred + * coding style is to place it before the return type, for example: + * + * DBUS_PRIVATE_EXPORT _DBUS_WARN_UNUSED_RESULT + * dbus_bool_t _dbus_user_database_lock_system (void); + */ +#if defined(_MSC_VER) && (_MSC_VER >= 1700) +#define _DBUS_WARN_UNUSED_RESULT _Must_inspect_result_ +#elif (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) +#define _DBUS_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define _DBUS_WARN_UNUSED_RESULT +#endif + +/** @def _DBUS_GNUC_PRINTF + * used to tell gcc about printf format strings + */ +/** @def _DBUS_GNUC_NORETURN + * used to tell gcc about functions that never return, such as _dbus_abort() + */ + +/* Normally docs are in .c files, but there isn't a .c file for this. */ +/** + * @defgroup DBusMacros Utility macros + * @ingroup DBus + * @brief #TRUE, #FALSE, #NULL, and so on + * + * Utility macros. + * + * @{ + */ + +/** + * @def DBUS_BEGIN_DECLS + * + * Macro used prior to declaring functions in the D-Bus header + * files. Expands to "extern "C"" when using a C++ compiler, + * and expands to nothing when using a C compiler. + * + * Please don't use this in your own code, consider it + * D-Bus internal. + */ +/** + * @def DBUS_END_DECLS + * + * Macro used after declaring functions in the D-Bus header + * files. Expands to "}" when using a C++ compiler, + * and expands to nothing when using a C compiler. + * + * Please don't use this in your own code, consider it + * D-Bus internal. + */ +/** + * @def TRUE + * + * Expands to "1" + */ +/** + * @def FALSE + * + * Expands to "0" + */ +/** + * @def NULL + * + * A null pointer, defined appropriately for C or C++. + */ +/** + * @def DBUS_DEPRECATED + * + * Tells the compiler to warn about a function or type if it's used. + * Code marked in this way should also be enclosed in + * @code + * #ifndef DBUS_DISABLE_DEPRECATED + * deprecated stuff here + * #endif + * @endcode + * + * Please don't use this in your own code, consider it + * D-Bus internal. + */ +/** + * @def _DBUS_GNUC_EXTENSION + * + * Tells gcc not to warn about extensions to the C standard in the + * following expression, even if compiling with -pedantic. Do not use + * this macro in your own code; please consider it to be internal to libdbus. + */ + +/* + * @def DBUS_EXPORT + * + * Declare the following symbol as public. This is currently a noop on + * platforms other than Windows. + */ + +#if defined(DBUS_EXPORT) + /* value forced by compiler command line, don't redefine */ +#elif defined(_WIN32) +# if defined(DBUS_STATIC_BUILD) +# define DBUS_EXPORT +# elif defined(dbus_1_EXPORTS) +# define DBUS_EXPORT __declspec(dllexport) +# else +# define DBUS_EXPORT __declspec(dllimport) +# endif +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define DBUS_EXPORT __attribute__ ((__visibility__ ("default"))) +#else +#define DBUS_EXPORT +#endif + +/* Implementation for dbus_clear_message() etc. This is not API, + * do not use it directly. + * + * We're using a specific type (T ** and T *) instead of void ** and + * void * partly for type-safety, partly to be strict-aliasing-compliant, + * and partly to keep C++ compilers happy. This code is inlined into + * users of libdbus, so we can't rely on it having dbus' own compiler + * settings. */ +#define _dbus_clear_pointer_impl(T, pointer_to_pointer, destroy) \ + do { \ + T **_pp = (pointer_to_pointer); \ + T *_value = *_pp; \ + \ + *_pp = NULL; \ + \ + if (_value != NULL) \ + destroy (_value); \ + } while (0) +/* Not (destroy) (_value) in case destroy() is a function-like macro */ + +/** @} */ + +#endif /* DBUS_MACROS_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-marshal-basic.c b/src/3rdparty/libdbus/dbus/dbus-marshal-basic.c new file mode 100644 index 00000000..64d68dae --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-marshal-basic.c @@ -0,0 +1,1993 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-marshal-basic.c Marshalling routines for basic (primitive) types + * + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2003, 2004, 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-marshal-basic.h" +#include "dbus-signature.h" +#include <dbus/dbus-test-tap.h> + +#include <string.h> + +#define _DBUS_ASSERT_ALIGNMENT(type, op, val) \ + _DBUS_STATIC_ASSERT (_DBUS_ALIGNOF (type) op val) +#define _DBUS_ASSERT_CMP_ALIGNMENT(left, op, right) \ + _DBUS_STATIC_ASSERT (_DBUS_ALIGNOF (left) op _DBUS_ALIGNOF (right)) + +/* True by definition, but just for completeness... */ +_DBUS_STATIC_ASSERT (sizeof (char) == 1); +_DBUS_ASSERT_ALIGNMENT (char, ==, 1); + +_DBUS_STATIC_ASSERT (sizeof (dbus_int16_t) == 2); +_DBUS_ASSERT_ALIGNMENT (dbus_int16_t, <=, 2); +_DBUS_STATIC_ASSERT (sizeof (dbus_uint16_t) == 2); +_DBUS_ASSERT_ALIGNMENT (dbus_uint16_t, <=, 2); +_DBUS_ASSERT_CMP_ALIGNMENT (dbus_uint16_t, ==, dbus_int16_t); + +_DBUS_STATIC_ASSERT (sizeof (dbus_int32_t) == 4); +_DBUS_ASSERT_ALIGNMENT (dbus_int32_t, <=, 4); +_DBUS_STATIC_ASSERT (sizeof (dbus_uint32_t) == 4); +_DBUS_ASSERT_ALIGNMENT (dbus_uint32_t, <=, 4); +_DBUS_ASSERT_CMP_ALIGNMENT (dbus_uint32_t, ==, dbus_int32_t); +_DBUS_STATIC_ASSERT (sizeof (dbus_bool_t) == 4); +_DBUS_ASSERT_ALIGNMENT (dbus_bool_t, <=, 4); +_DBUS_ASSERT_CMP_ALIGNMENT (dbus_uint32_t, ==, dbus_bool_t); + +_DBUS_STATIC_ASSERT (sizeof (double) == 8); +_DBUS_ASSERT_ALIGNMENT (double, <=, 8); +/* Doubles might sometimes be more strictly aligned than int64, but we + * assume they are no less strictly aligned. This means every (double *) + * has enough alignment to be treated as though it was a + * (dbus_uint64_t *). */ +_DBUS_ASSERT_CMP_ALIGNMENT (dbus_uint64_t, <=, double); + +_DBUS_STATIC_ASSERT (sizeof (dbus_int64_t) == 8); +_DBUS_ASSERT_ALIGNMENT (dbus_int64_t, <=, 8); +_DBUS_STATIC_ASSERT (sizeof (dbus_uint64_t) == 8); +_DBUS_ASSERT_ALIGNMENT (dbus_uint64_t, <=, 8); +_DBUS_ASSERT_CMP_ALIGNMENT (dbus_uint64_t, ==, dbus_int64_t); + +_DBUS_STATIC_ASSERT (sizeof (DBusBasicValue) >= 8); +/* The alignment of a DBusBasicValue might conceivably be > 8 because of the + * pointer, so we don't assert about it */ + +_DBUS_STATIC_ASSERT (sizeof (DBus8ByteStruct) == 8); +_DBUS_ASSERT_ALIGNMENT (DBus8ByteStruct, <=, 8); + +/** + * @defgroup DBusMarshal marshaling and unmarshaling + * @ingroup DBusInternals + * @brief functions to marshal/unmarshal data from the wire + * + * Types and functions related to converting primitive data types from + * wire format to native machine format, and vice versa. + * + * A signature is just a string with multiple types one after the other. + * for example a type is "i" or "(ii)", a signature is "i(ii)" + * where i is int and (ii) is struct { int; int; } + * + * @{ + */ + +static void +pack_2_octets (dbus_uint16_t value, + int byte_order, + void *data) +{ + _dbus_assert (_DBUS_ALIGN_ADDRESS (data, 2) == data); + + if ((byte_order) == DBUS_LITTLE_ENDIAN) + *((dbus_uint16_t*)(data)) = DBUS_UINT16_TO_LE (value); + else + *((dbus_uint16_t*)(data)) = DBUS_UINT16_TO_BE (value); +} + +static void +pack_4_octets (dbus_uint32_t value, + int byte_order, + void *data) +{ + _dbus_assert (_DBUS_ALIGN_ADDRESS (data, 4) == data); + + if ((byte_order) == DBUS_LITTLE_ENDIAN) + *((dbus_uint32_t*)(data)) = DBUS_UINT32_TO_LE (value); + else + *((dbus_uint32_t*)(data)) = DBUS_UINT32_TO_BE (value); +} + +static void +pack_8_octets (dbus_uint64_t value, + int byte_order, + void *data) +{ + _dbus_assert (_DBUS_ALIGN_ADDRESS (data, 8) == data); + + if ((byte_order) == DBUS_LITTLE_ENDIAN) + *((dbus_uint64_t*)(data)) = DBUS_UINT64_TO_LE (value); + else + *((dbus_uint64_t*)(data)) = DBUS_UINT64_TO_BE (value); +} + +/** + * Packs a 32 bit unsigned integer into a data pointer. + * + * @param value the value + * @param byte_order the byte order to use + * @param data the data pointer + */ +void +_dbus_pack_uint32 (dbus_uint32_t value, + int byte_order, + unsigned char *data) +{ + pack_4_octets (value, byte_order, data); +} + +static void +swap_8_octets (dbus_uint64_t *value, + int byte_order) +{ + if (byte_order != DBUS_COMPILER_BYTE_ORDER) + { + *value = DBUS_UINT64_SWAP_LE_BE (*value); + } +} + +#ifndef _dbus_unpack_uint16 +/** + * Unpacks a 16 bit unsigned integer from a data pointer + * + * @param byte_order The byte order to use + * @param data the data pointer + * @returns the integer + */ +dbus_uint16_t +_dbus_unpack_uint16 (int byte_order, + const unsigned char *data) +{ + _dbus_assert (_DBUS_ALIGN_ADDRESS (data, 2) == data); + + if (byte_order == DBUS_LITTLE_ENDIAN) + return DBUS_UINT16_FROM_LE (*(dbus_uint16_t *) (void *) data); + else + return DBUS_UINT16_FROM_BE (*(dbus_uint16_t *) (void *) data); +} +#endif /* _dbus_unpack_uint16 */ + +#ifndef _dbus_unpack_uint32 +/** + * Unpacks a 32 bit unsigned integer from a data pointer + * + * @param byte_order The byte order to use + * @param data the data pointer + * @returns the integer + */ +dbus_uint32_t +_dbus_unpack_uint32 (int byte_order, + const unsigned char *data) +{ + _dbus_assert (_DBUS_ALIGN_ADDRESS (data, 4) == data); + + if (byte_order == DBUS_LITTLE_ENDIAN) + return DBUS_UINT32_FROM_LE (*(dbus_uint32_t *) (void *) data); + else + return DBUS_UINT32_FROM_BE (*(dbus_uint32_t *) (void *) data); +} +#endif /* _dbus_unpack_uint32 */ + +static void +set_2_octets (DBusString *str, + int offset, + dbus_uint16_t value, + int byte_order) +{ + char *data; + + _dbus_assert (byte_order == DBUS_LITTLE_ENDIAN || + byte_order == DBUS_BIG_ENDIAN); + + data = _dbus_string_get_data_len (str, offset, 2); + + pack_2_octets (value, byte_order, (unsigned char *) data); +} + +static void +set_4_octets (DBusString *str, + int offset, + dbus_uint32_t value, + int byte_order) +{ + char *data; + + _dbus_assert (byte_order == DBUS_LITTLE_ENDIAN || + byte_order == DBUS_BIG_ENDIAN); + + data = _dbus_string_get_data_len (str, offset, 4); + + pack_4_octets (value, byte_order, (unsigned char *) data); +} + +static void +set_8_octets (DBusString *str, + int offset, + dbus_uint64_t value, + int byte_order) +{ + char *data; + + _dbus_assert (byte_order == DBUS_LITTLE_ENDIAN || + byte_order == DBUS_BIG_ENDIAN); + + data = _dbus_string_get_data_len (str, offset, 8); + + pack_8_octets (value, byte_order, (unsigned char *) data); +} + +/** + * Sets the 4 bytes at the given offset to a marshaled unsigned + * integer, replacing anything found there previously. + * + * @param str the string to write the marshalled int to + * @param pos the byte offset where int should be written + * @param value the value + * @param byte_order the byte order to use + * + */ +void +_dbus_marshal_set_uint32 (DBusString *str, + int pos, + dbus_uint32_t value, + int byte_order) +{ + set_4_octets (str, pos, value, byte_order); +} + +/** + * Sets the existing marshaled string at the given offset with + * a new marshaled string. The given offset must point to + * an existing string or the wrong length will be deleted + * and replaced with the new string. + * + * Note: no attempt is made by this function to re-align + * any data which has been already marshalled after this + * string. Use with caution. + * + * @param str the string to write the marshalled string to + * @param pos the position of the marshaled string length + * @param value the value + * @param byte_order the byte order to use + * @param old_end_pos place to store byte after the nul byte of the old value + * @param new_end_pos place to store byte after the nul byte of the new value + * @returns #TRUE on success, #FALSE if no memory + * + */ +static dbus_bool_t +set_string (DBusString *str, + int pos, + const char *value, + int byte_order, + int *old_end_pos, + int *new_end_pos) +{ + int old_len, new_len; + DBusString dstr; + + _dbus_string_init_const (&dstr, value); + + _dbus_assert (_DBUS_ALIGN_VALUE (pos, 4) == (unsigned) pos); + old_len = _dbus_unpack_uint32 (byte_order, + _dbus_string_get_const_udata_len (str, pos, 4)); + + new_len = _dbus_string_get_length (&dstr); + + if (!_dbus_string_replace_len (&dstr, 0, new_len, + str, pos + 4, old_len)) + return FALSE; + + _dbus_marshal_set_uint32 (str, pos, new_len, byte_order); + + if (old_end_pos) + *old_end_pos = pos + 4 + old_len + 1; + if (new_end_pos) + *new_end_pos = pos + 4 + new_len + 1; + + return TRUE; +} + +/** + * Sets the existing marshaled signature at the given offset to a new + * marshaled signature. Same basic ideas as set_string(). + * + * @param str the string to write the marshalled signature to + * @param pos the position of the marshaled signature length + * @param value the value + * @param byte_order the byte order to use + * @param old_end_pos place to store byte after the nul byte of the old value + * @param new_end_pos place to store byte after the nul byte of the new value + * @returns #TRUE on success, #FALSE if no memory + * + */ +static dbus_bool_t +set_signature (DBusString *str, + int pos, + const char *value, + int byte_order, + int *old_end_pos, + int *new_end_pos) +{ + int old_len, new_len; + DBusString dstr; + + _dbus_string_init_const (&dstr, value); + + old_len = _dbus_string_get_byte (str, pos); + new_len = _dbus_string_get_length (&dstr); + + if (!_dbus_string_replace_len (&dstr, 0, new_len, + str, pos + 1, old_len)) + return FALSE; + + _dbus_string_set_byte (str, pos, new_len); + + if (old_end_pos) + *old_end_pos = pos + 1 + old_len + 1; + if (new_end_pos) + *new_end_pos = pos + 1 + new_len + 1; + + return TRUE; +} + +/** + * Sets an existing basic type value to a new value. + * Arguments work the same way as _dbus_marshal_basic_type(). + * + * @param str the string + * @param pos location of the current value + * @param type the type of the current and new values + * @param value the address of the new value + * @param byte_order byte order for marshaling + * @param old_end_pos location to store end position of the old value, or #NULL + * @param new_end_pos location to store end position of the new value, or #NULL + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_marshal_set_basic (DBusString *str, + int pos, + int type, + const void *value, + int byte_order, + int *old_end_pos, + int *new_end_pos) +{ + /* Static assertions near the top of this file assert that signed and + * unsigned 16- and 32-bit quantities have the same alignment, and that + * doubles have alignment at least as strict as unsigned int64, so we + * don't have to distinguish further: every (double *) + * has strong enough alignment to be treated as though it was a + * (dbus_uint64_t *). Going via a (void *) means the compiler should + * know that pointers can alias each other. */ + const unsigned char *u8_p; + const dbus_uint16_t *u16_p; + const dbus_uint32_t *u32_p; + const dbus_uint64_t *u64_p; + const char * const *string_p; + + switch (type) + { + case DBUS_TYPE_BYTE: + u8_p = value; + _dbus_string_set_byte (str, pos, *u8_p); + if (old_end_pos) + *old_end_pos = pos + 1; + if (new_end_pos) + *new_end_pos = pos + 1; + return TRUE; + break; + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + u16_p = value; + pos = _DBUS_ALIGN_VALUE (pos, 2); + set_2_octets (str, pos, *u16_p, byte_order); + if (old_end_pos) + *old_end_pos = pos + 2; + if (new_end_pos) + *new_end_pos = pos + 2; + return TRUE; + break; + case DBUS_TYPE_BOOLEAN: + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_UNIX_FD: + u32_p = value; + pos = _DBUS_ALIGN_VALUE (pos, 4); + set_4_octets (str, pos, *u32_p, byte_order); + if (old_end_pos) + *old_end_pos = pos + 4; + if (new_end_pos) + *new_end_pos = pos + 4; + return TRUE; + break; + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + u64_p = value; + pos = _DBUS_ALIGN_VALUE (pos, 8); + set_8_octets (str, pos, *u64_p, byte_order); + if (old_end_pos) + *old_end_pos = pos + 8; + if (new_end_pos) + *new_end_pos = pos + 8; + return TRUE; + break; + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + string_p = value; + pos = _DBUS_ALIGN_VALUE (pos, 4); + _dbus_assert (*string_p != NULL); + return set_string (str, pos, *string_p, byte_order, + old_end_pos, new_end_pos); + break; + case DBUS_TYPE_SIGNATURE: + string_p = value; + _dbus_assert (*string_p != NULL); + return set_signature (str, pos, *string_p, byte_order, + old_end_pos, new_end_pos); + break; + default: + _dbus_assert_not_reached ("not a basic type"); + return FALSE; + break; + } +} + +/** + * Convenience function to demarshal a 32 bit unsigned integer. + * + * @param str the string containing the data + * @param byte_order the byte order + * @param pos the position in the string + * @param new_pos the new position of the string + * @returns the demarshaled integer. + */ +dbus_uint32_t +_dbus_marshal_read_uint32 (const DBusString *str, + int pos, + int byte_order, + int *new_pos) +{ + pos = _DBUS_ALIGN_VALUE (pos, 4); + + if (new_pos) + *new_pos = pos + 4; + + _dbus_assert (pos + 4 <= _dbus_string_get_length (str)); + + return _dbus_unpack_uint32 (byte_order, + _dbus_string_get_const_udata (str) + pos); +} + +/** + * Demarshals a basic-typed value. The "value" pointer is always + * the address of a variable of the basic type. So e.g. + * if the basic type is "double" then the pointer is + * a double*, and if it's "char*" then the pointer is + * a "char**". + * + * A value of type #DBusBasicValue is guaranteed to be large enough to + * hold any of the types that may be returned, which is handy if you + * are trying to do things generically. For example you can pass + * a DBusBasicValue* in to this function, and then pass the same + * DBusBasicValue* in to _dbus_marshal_basic_type() in order to + * move a value from one place to another. + * + * @param str the string containing the data + * @param pos position in the string + * @param type type of value to demarshal + * @param value pointer to return value data + * @param byte_order the byte order + * @param new_pos pointer to update with new position, or #NULL + **/ +void +_dbus_marshal_read_basic (const DBusString *str, + int pos, + int type, + void *value, + int byte_order, + int *new_pos) +{ + const char *str_data; + + _dbus_assert (dbus_type_is_basic (type)); + + str_data = _dbus_string_get_const_data (str); + + /* Below we volatile types to avoid aliasing issues; + * see http://bugs.freedesktop.org/show_bug.cgi?id=20137 + */ + + switch (type) + { + case DBUS_TYPE_BYTE: + { + volatile unsigned char *vp = value; + *vp = (unsigned char) _dbus_string_get_byte (str, pos); + (pos)++; + } + break; + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + { + volatile dbus_uint16_t *vp = value; + pos = _DBUS_ALIGN_VALUE (pos, 2); + *vp = *(dbus_uint16_t *) (void *) (str_data + pos); + if (byte_order != DBUS_COMPILER_BYTE_ORDER) + *vp = DBUS_UINT16_SWAP_LE_BE (*vp); + pos += 2; + } + break; + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_BOOLEAN: + case DBUS_TYPE_UNIX_FD: + { + volatile dbus_uint32_t *vp = value; + pos = _DBUS_ALIGN_VALUE (pos, 4); + *vp = *(dbus_uint32_t *) (void *) (str_data + pos); + if (byte_order != DBUS_COMPILER_BYTE_ORDER) + *vp = DBUS_UINT32_SWAP_LE_BE (*vp); + pos += 4; + } + break; + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + { + volatile dbus_uint64_t *vp = value; + pos = _DBUS_ALIGN_VALUE (pos, 8); + if (byte_order != DBUS_COMPILER_BYTE_ORDER) + *vp = DBUS_UINT64_SWAP_LE_BE ( + *(dbus_uint64_t *) (void *) (str_data + pos)); + else + *vp = *(dbus_uint64_t *) (void *) (str_data + pos); + pos += 8; + } + break; + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + { + int len; + volatile char **vp = value; + + len = _dbus_marshal_read_uint32 (str, pos, byte_order, &pos); + + *vp = (char*) str_data + pos; + + pos += len + 1; /* length plus nul */ + } + break; + case DBUS_TYPE_SIGNATURE: + { + int len; + volatile char **vp = value; + + len = _dbus_string_get_byte (str, pos); + pos += 1; + + *vp = (char*) str_data + pos; + + pos += len + 1; /* length plus nul */ + } + break; + default: + _dbus_warn_check_failed ("type %s %d not a basic type", + _dbus_type_to_string (type), type); + _dbus_assert_not_reached ("not a basic type"); + break; + } + + if (new_pos) + *new_pos = pos; +} + +static dbus_bool_t +marshal_2_octets (DBusString *str, + int insert_at, + dbus_uint16_t value, + int byte_order, + int *pos_after) +{ + dbus_bool_t retval; + int orig_len; + + _DBUS_STATIC_ASSERT (sizeof (value) == 2); + + if (byte_order != DBUS_COMPILER_BYTE_ORDER) + value = DBUS_UINT16_SWAP_LE_BE (value); + + orig_len = _dbus_string_get_length (str); + + retval = _dbus_string_insert_2_aligned (str, insert_at, + (const unsigned char *)&value); + + if (pos_after) + { + *pos_after = insert_at + (_dbus_string_get_length (str) - orig_len); + _dbus_assert (*pos_after <= _dbus_string_get_length (str)); + } + + return retval; +} + +static dbus_bool_t +marshal_4_octets (DBusString *str, + int insert_at, + dbus_uint32_t value, + int byte_order, + int *pos_after) +{ + dbus_bool_t retval; + int orig_len; + + _DBUS_STATIC_ASSERT (sizeof (value) == 4); + + if (byte_order != DBUS_COMPILER_BYTE_ORDER) + value = DBUS_UINT32_SWAP_LE_BE (value); + + orig_len = _dbus_string_get_length (str); + + retval = _dbus_string_insert_4_aligned (str, insert_at, + (const unsigned char *)&value); + + if (pos_after) + { + *pos_after = insert_at + (_dbus_string_get_length (str) - orig_len); + _dbus_assert (*pos_after <= _dbus_string_get_length (str)); + } + + return retval; +} + +static dbus_bool_t +marshal_8_octets (DBusString *str, + int insert_at, + dbus_uint64_t value, + int byte_order, + int *pos_after) +{ + dbus_bool_t retval; + int orig_len; + + _DBUS_STATIC_ASSERT (sizeof (value) == 8); + + swap_8_octets (&value, byte_order); + + orig_len = _dbus_string_get_length (str); + + retval = _dbus_string_insert_8_aligned (str, insert_at, + (const unsigned char *)&value); + + if (pos_after) + *pos_after = insert_at + _dbus_string_get_length (str) - orig_len; + + return retval; +} + +enum + { + MARSHAL_AS_STRING, + MARSHAL_AS_SIGNATURE, + MARSHAL_AS_BYTE_ARRAY + }; + +static dbus_bool_t +marshal_len_followed_by_bytes (int marshal_as, + DBusString *str, + int insert_at, + const unsigned char *value, + int data_len, /* doesn't include nul if any */ + int byte_order, + int *pos_after) +{ + int pos; + DBusString value_str; + int value_len; + + _dbus_assert (byte_order == DBUS_LITTLE_ENDIAN || byte_order == DBUS_BIG_ENDIAN); + if (insert_at > _dbus_string_get_length (str)) + _dbus_warn ("insert_at = %d string len = %d data_len = %d", + insert_at, _dbus_string_get_length (str), data_len); + + if (marshal_as == MARSHAL_AS_BYTE_ARRAY) + value_len = data_len; + else + value_len = data_len + 1; /* value has a nul */ + + _dbus_string_init_const_len (&value_str, (const char *) value, value_len); + + pos = insert_at; + + if (marshal_as == MARSHAL_AS_SIGNATURE) + { + _dbus_assert (data_len <= DBUS_MAXIMUM_SIGNATURE_LENGTH); + _dbus_assert (data_len <= 255); /* same as max sig len right now */ + + if (!_dbus_string_insert_byte (str, pos, data_len)) + goto oom; + + pos += 1; + } + else + { + if (!marshal_4_octets (str, pos, data_len, + byte_order, &pos)) + goto oom; + } + + if (!_dbus_string_copy_len (&value_str, 0, value_len, + str, pos)) + goto oom; + +#if 0 + /* too expensive */ + _dbus_assert (_dbus_string_equal_substring (&value_str, 0, value_len, + str, pos)); + _dbus_verbose_bytes_of_string (str, pos, value_len); +#endif + + pos += value_len; + + if (pos_after) + *pos_after = pos; + + return TRUE; + + oom: + /* Delete what we've inserted */ + _dbus_string_delete (str, insert_at, pos - insert_at); + + return FALSE; +} + +static dbus_bool_t +marshal_string (DBusString *str, + int insert_at, + const char *value, + int byte_order, + int *pos_after) +{ + return marshal_len_followed_by_bytes (MARSHAL_AS_STRING, + str, insert_at, (const unsigned char *) value, + strlen (value), + byte_order, pos_after); +} + +static dbus_bool_t +marshal_signature (DBusString *str, + int insert_at, + const char *value, + int *pos_after) +{ + return marshal_len_followed_by_bytes (MARSHAL_AS_SIGNATURE, + str, insert_at, (const unsigned char *) value, + strlen (value), + DBUS_COMPILER_BYTE_ORDER, /* irrelevant */ + pos_after); +} + +/** + * Marshals a basic-typed value. The "value" pointer is always the + * address of a variable containing the basic type value. + * So for example for int32 it will be dbus_int32_t*, and + * for string it will be const char**. This is for symmetry + * with _dbus_marshal_read_basic() and to have a simple + * consistent rule. + * + * @param str string to marshal to + * @param insert_at where to insert the value + * @param type type of value + * @param value pointer to a variable containing the value + * @param byte_order byte order + * @param pos_after #NULL or the position after the type + * @returns #TRUE on success + **/ +dbus_bool_t +_dbus_marshal_write_basic (DBusString *str, + int insert_at, + int type, + const void *value, + int byte_order, + int *pos_after) +{ + /* Static assertions near the top of this file assert that signed and + * unsigned 16- and 32-bit quantities have the same alignment, and that + * doubles have alignment at least as strict as unsigned int64, so we + * don't have to distinguish further: every (double *) + * has strong enough alignment to be treated as though it was a + * (dbus_uint64_t *). Going via a (void *) means the compiler should + * know that pointers can alias each other. */ + const unsigned char *u8_p; + const dbus_uint16_t *u16_p; + const dbus_uint32_t *u32_p; + const dbus_uint64_t *u64_p; + const char * const *string_p; + + _dbus_assert (dbus_type_is_basic (type)); + + switch (type) + { + case DBUS_TYPE_BYTE: + u8_p = value; + if (!_dbus_string_insert_byte (str, insert_at, *u8_p)) + return FALSE; + if (pos_after) + *pos_after = insert_at + 1; + return TRUE; + break; + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + u16_p = value; + return marshal_2_octets (str, insert_at, *u16_p, + byte_order, pos_after); + break; + case DBUS_TYPE_BOOLEAN: + u32_p = value; + return marshal_4_octets (str, insert_at, (*u32_p != FALSE), + byte_order, pos_after); + break; + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_UNIX_FD: + u32_p = value; + return marshal_4_octets (str, insert_at, *u32_p, + byte_order, pos_after); + break; + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + u64_p = value; + return marshal_8_octets (str, insert_at, *u64_p, byte_order, pos_after); + break; + + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + string_p = value; + _dbus_assert (*string_p != NULL); + return marshal_string (str, insert_at, *string_p, byte_order, pos_after); + break; + case DBUS_TYPE_SIGNATURE: + string_p = value; + _dbus_assert (*string_p != NULL); + return marshal_signature (str, insert_at, *string_p, pos_after); + break; + default: + _dbus_assert_not_reached ("not a basic type"); + return FALSE; + break; + } +} + +static dbus_bool_t +marshal_1_octets_array (DBusString *str, + int insert_at, + const unsigned char *value, + int n_elements, + int byte_order, + int *pos_after) +{ + int pos; + DBusString value_str; + + _dbus_string_init_const_len (&value_str, (const char *) value, n_elements); + + pos = insert_at; + + if (!_dbus_string_copy_len (&value_str, 0, n_elements, + str, pos)) + return FALSE; + + pos += n_elements; + + if (pos_after) + *pos_after = pos; + + return TRUE; +} + +/** + * Swaps the elements of an array to the opposite byte order + * + * @param data start of array + * @param n_elements number of elements + * @param alignment size of each element + */ +void +_dbus_swap_array (unsigned char *data, + int n_elements, + int alignment) +{ + void *d; + void *end; + + _dbus_assert (_DBUS_ALIGN_ADDRESS (data, alignment) == data); + + /* we use const_data and cast it off so DBusString can be a const string + * for the unit tests. don't ask. + */ + d = data; + end = data + (n_elements * alignment); + + if (alignment == 8) + { + while (d != end) + { + *((dbus_uint64_t*)d) = DBUS_UINT64_SWAP_LE_BE (*((dbus_uint64_t*)d)); + d = ((unsigned char *) d) + 8; + } + } + else if (alignment == 4) + { + while (d != end) + { + *((dbus_uint32_t*)d) = DBUS_UINT32_SWAP_LE_BE (*((dbus_uint32_t*)d)); + d = ((unsigned char *) d) + 4; + } + } + else + { + _dbus_assert (alignment == 2); + + while (d != end) + { + *((dbus_uint16_t*)d) = DBUS_UINT16_SWAP_LE_BE (*((dbus_uint16_t*)d)); + d = ((unsigned char *) d) + 2; + } + } +} + +static void +swap_array (DBusString *str, + int array_start, + int n_elements, + int byte_order, + int alignment) +{ + _dbus_assert (_DBUS_ALIGN_VALUE (array_start, alignment) == (unsigned) array_start); + + if (byte_order != DBUS_COMPILER_BYTE_ORDER) + { + /* we use const_data and cast it off so DBusString can be a const string + * for the unit tests. don't ask. + */ + _dbus_swap_array ((unsigned char*) (_dbus_string_get_const_data (str) + array_start), + n_elements, alignment); + } +} + +static dbus_bool_t +marshal_fixed_multi (DBusString *str, + int insert_at, + const void *value, + int n_elements, + int byte_order, + int alignment, + int *pos_after) +{ + int old_string_len; + int array_start; + DBusString t; + int len_in_bytes; + + _dbus_assert (n_elements <= DBUS_MAXIMUM_ARRAY_LENGTH / alignment); + + old_string_len = _dbus_string_get_length (str); + + len_in_bytes = n_elements * alignment; + array_start = insert_at; + + /* Note that we do alignment padding unconditionally + * even if the array is empty; this means that + * padding + len is always equal to the number of bytes + * in the array. + */ + + if (!_dbus_string_insert_alignment (str, &array_start, alignment)) + goto error; + + _dbus_string_init_const_len (&t, + (const char *) value, + len_in_bytes); + + if (!_dbus_string_copy (&t, 0, + str, array_start)) + goto error; + + swap_array (str, array_start, n_elements, byte_order, alignment); + + if (pos_after) + *pos_after = array_start + len_in_bytes; + + return TRUE; + + error: + _dbus_string_delete (str, insert_at, + _dbus_string_get_length (str) - old_string_len); + + return FALSE; +} + +/** + * Marshals a block of values of fixed-length type all at once, as an + * optimization. dbus_type_is_fixed() returns #TRUE for fixed-length + * types, which are the basic types minus the string-like types. + * + * The value argument should be the adddress of an + * array, so e.g. "const dbus_uint32_t**" + * + * @param str string to marshal to + * @param insert_at where to insert the value + * @param element_type type of array elements + * @param value address of an array to marshal + * @param n_elements number of elements in the array + * @param byte_order byte order + * @param pos_after #NULL or the position after the type + * @returns #TRUE on success + **/ +dbus_bool_t +_dbus_marshal_write_fixed_multi (DBusString *str, + int insert_at, + int element_type, + const void *value, + int n_elements, + int byte_order, + int *pos_after) +{ + /* Static assertions near the top of this file assert that signed and + * unsigned 16- and 32-bit quantities have the same alignment, and that + * doubles have alignment at least as strict as unsigned int64, so we + * don't have to distinguish further: every (double *) + * has strong enough alignment to be treated as though it was a + * (dbus_uint64_t *). Going via a (void *) means the compiler should + * know that pointers can alias each other. */ + const unsigned char * const *u8_pp; + const dbus_uint16_t * const *u16_pp; + const dbus_uint32_t * const *u32_pp; + const dbus_uint64_t * const *u64_pp; + + _dbus_assert (dbus_type_is_fixed (element_type)); + _dbus_assert (n_elements >= 0); + +#if 0 + _dbus_verbose ("writing %d elements of %s\n", + n_elements, _dbus_type_to_string (element_type)); +#endif + + switch (element_type) + { + case DBUS_TYPE_BYTE: + u8_pp = value; + return marshal_1_octets_array (str, insert_at, *u8_pp, n_elements, byte_order, pos_after); + break; + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + u16_pp = value; + return marshal_fixed_multi (str, insert_at, *u16_pp, n_elements, byte_order, 2, pos_after); + case DBUS_TYPE_BOOLEAN: + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_UNIX_FD: + u32_pp = value; + return marshal_fixed_multi (str, insert_at, *u32_pp, n_elements, byte_order, 4, pos_after); + break; + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + u64_pp = value; + return marshal_fixed_multi (str, insert_at, *u64_pp, n_elements, byte_order, 8, pos_after); + break; + + default: + _dbus_assert_not_reached ("non fixed type in array write"); + break; + } + + return FALSE; +} + + +/** + * Skips over a basic-typed value, reporting the following position. + * + * @param str the string containing the data + * @param type type of value to read + * @param byte_order the byte order + * @param pos pointer to position in the string, + * updated on return to new position + **/ +void +_dbus_marshal_skip_basic (const DBusString *str, + int type, + int byte_order, + int *pos) +{ + _dbus_assert (byte_order == DBUS_LITTLE_ENDIAN || + byte_order == DBUS_BIG_ENDIAN); + + switch (type) + { + case DBUS_TYPE_BYTE: + (*pos)++; + break; + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + /* Advance to the next suitably-aligned position >= *pos */ + *pos = _DBUS_ALIGN_VALUE (*pos, 2); + *pos += 2; + break; + case DBUS_TYPE_BOOLEAN: + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_UNIX_FD: + *pos = _DBUS_ALIGN_VALUE (*pos, 4); + *pos += 4; + break; + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + *pos = _DBUS_ALIGN_VALUE (*pos, 8); + *pos += 8; + break; + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + { + int len; + + /* Let len be the number of bytes of string data, and advance + * *pos to just after the length */ + len = _dbus_marshal_read_uint32 (str, *pos, byte_order, pos); + + *pos += len + 1; /* length plus nul */ + } + break; + case DBUS_TYPE_SIGNATURE: + { + int len; + + len = _dbus_string_get_byte (str, *pos); + + *pos += len + 2; /* length byte plus length plus nul */ + } + break; + default: + _dbus_warn ("type %s not a basic type", + _dbus_type_to_string (type)); + _dbus_assert_not_reached ("not a basic type"); + break; + } + + /* We had better still be in-bounds at this point (pointing either into + * the content of the string, or 1 past the logical length of the string) */ + _dbus_assert (*pos <= _dbus_string_get_length (str)); +} + +/** + * Skips an array, returning the next position. + * + * @param str the string containing the data + * @param element_type the type of array elements + * @param byte_order the byte order + * @param pos pointer to position in the string, + * updated on return to new position + */ +void +_dbus_marshal_skip_array (const DBusString *str, + int element_type, + int byte_order, + int *pos) +{ + dbus_uint32_t array_len; + int i; + int alignment; + + /* Advance to the next 4-byte-aligned position >= *pos */ + i = _DBUS_ALIGN_VALUE (*pos, 4); + + /* Let array_len be the number of bytes of array data, and advance + * i to just after the length */ + array_len = _dbus_marshal_read_uint32 (str, i, byte_order, &i); + + /* If the element type is more strictly-aligned than the length, + * advance i to the next suitably-aligned position + * (in other words, skip the padding) */ + alignment = _dbus_type_get_alignment (element_type); + + i = _DBUS_ALIGN_VALUE (i, alignment); + + /* Skip the actual array data */ + *pos = i + array_len; + + /* We had better still be in-bounds at this point (pointing either into + * the content of the string, or 1 past the logical length of the string) */ + _dbus_assert (*pos <= _dbus_string_get_length (str)); +} + +/** + * Gets the alignment requirement for the given type; + * will be 1, 2, 4, or 8. + * + * @param typecode the type + * @returns alignment of 1, 2, 4, or 8 + */ +int +_dbus_type_get_alignment (int typecode) +{ + switch (typecode) + { + case DBUS_TYPE_BYTE: + case DBUS_TYPE_VARIANT: + case DBUS_TYPE_SIGNATURE: + return 1; + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + return 2; + case DBUS_TYPE_BOOLEAN: + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_UNIX_FD: + /* this stuff is 4 since it starts with a length */ + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + case DBUS_TYPE_ARRAY: + return 4; + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + /* struct is 8 since it could contain an 8-aligned item + * and it's simpler to just always align structs to 8; + * we want the amount of padding in a struct of a given + * type to be predictable, not location-dependent. + * DICT_ENTRY is always the same as struct. + */ + case DBUS_TYPE_STRUCT: + case DBUS_TYPE_DICT_ENTRY: + return 8; + + default: + _dbus_assert_not_reached ("unknown typecode in _dbus_type_get_alignment()"); + return 0; + } +} + +/** + * Returns a string describing the given type. + * + * @param typecode the type to describe + * @returns a constant string describing the type + */ +const char * +_dbus_type_to_string (int typecode) +{ + switch (typecode) + { + case DBUS_TYPE_INVALID: + return "invalid"; + case DBUS_TYPE_BOOLEAN: + return "boolean"; + case DBUS_TYPE_BYTE: + return "byte"; + case DBUS_TYPE_INT16: + return "int16"; + case DBUS_TYPE_UINT16: + return "uint16"; + case DBUS_TYPE_INT32: + return "int32"; + case DBUS_TYPE_UINT32: + return "uint32"; + case DBUS_TYPE_INT64: + return "int64"; + case DBUS_TYPE_UINT64: + return "uint64"; + case DBUS_TYPE_DOUBLE: + return "double"; + case DBUS_TYPE_STRING: + return "string"; + case DBUS_TYPE_OBJECT_PATH: + return "object_path"; + case DBUS_TYPE_SIGNATURE: + return "signature"; + case DBUS_TYPE_STRUCT: + return "struct"; + case DBUS_TYPE_DICT_ENTRY: + return "dict_entry"; + case DBUS_TYPE_ARRAY: + return "array"; + case DBUS_TYPE_VARIANT: + return "variant"; + case DBUS_STRUCT_BEGIN_CHAR: + return "begin_struct"; + case DBUS_STRUCT_END_CHAR: + return "end_struct"; + case DBUS_DICT_ENTRY_BEGIN_CHAR: + return "begin_dict_entry"; + case DBUS_DICT_ENTRY_END_CHAR: + return "end_dict_entry"; + case DBUS_TYPE_UNIX_FD: + return "unix_fd"; + default: + return "unknown"; + } +} + +/** + * If in verbose mode, print a block of binary data. + * + * @param data the data + * @param len the length of the data + * @param offset where to start counting for byte indexes + */ +void +_dbus_verbose_bytes (const unsigned char *data, + int len, + int offset) +{ + int i; + const unsigned char *aligned; + + _dbus_assert (len >= 0); + + if (!_dbus_is_verbose()) + return; + + /* Print blanks on first row if appropriate */ + aligned = _DBUS_ALIGN_ADDRESS (data, 4); + if (aligned > data) + aligned -= 4; + _dbus_assert (aligned <= data); + + if (aligned != data) + { + _dbus_verbose ("%4ld\t%p: ", - (long)(data - aligned), aligned); + while (aligned != data) + { + _dbus_verbose (" "); + ++aligned; + } + } + + /* now print the bytes */ + i = 0; + while (i < len) + { + if (_DBUS_ALIGN_ADDRESS (&data[i], 4) == &data[i]) + { + _dbus_verbose ("%4d\t%p: ", + offset + i, &data[i]); + } + + if (data[i] >= 32 && + data[i] <= 126) + _dbus_verbose (" '%c' ", data[i]); + else + _dbus_verbose ("0x%s%x ", + data[i] <= 0xf ? "0" : "", data[i]); + + ++i; + + if (_DBUS_ALIGN_ADDRESS (&data[i], 4) == &data[i]) + { + if (i > 3) + _dbus_verbose ("BE: %d LE: %d", + _dbus_unpack_uint32 (DBUS_BIG_ENDIAN, &data[i-4]), + _dbus_unpack_uint32 (DBUS_LITTLE_ENDIAN, &data[i-4])); + + if (i > 7 && + _DBUS_ALIGN_ADDRESS (&data[i], 8) == &data[i]) + { + _dbus_verbose (" u64: 0x%" DBUS_INT64_MODIFIER "x", + *(dbus_uint64_t *) (void *) &data[i - 8]); + _dbus_verbose (" dbl: %g", *(double *) (void *) &data[i - 8]); + } + + _dbus_verbose ("\n"); + } + } + + _dbus_verbose ("\n"); +} + +/** + * Dump the given part of the string to verbose log. + * + * @param str the string + * @param start the start of range to dump + * @param len length of range + */ +void +_dbus_verbose_bytes_of_string (const DBusString *str, + int start, + int len) +{ + const char *d; + int real_len; + + real_len = _dbus_string_get_length (str); + + _dbus_assert (start >= 0); + + if (start > real_len) + { + _dbus_verbose (" [%d,%d) is not inside string of length %d\n", + start, len, real_len); + return; + } + + if ((start + len) > real_len) + { + _dbus_verbose (" [%d,%d) extends outside string of length %d\n", + start, len, real_len); + len = real_len - start; + } + + d = _dbus_string_get_const_data_len (str, start, len); + + _dbus_verbose_bytes ((const unsigned char *) d, len, start); +} + +static int +map_type_char_to_type (int t) +{ + if (t == DBUS_STRUCT_BEGIN_CHAR) + return DBUS_TYPE_STRUCT; + else if (t == DBUS_DICT_ENTRY_BEGIN_CHAR) + return DBUS_TYPE_DICT_ENTRY; + else + { + _dbus_assert (t != DBUS_STRUCT_END_CHAR); + _dbus_assert (t != DBUS_DICT_ENTRY_END_CHAR); + return t; + } +} + +/** + * Get the first type in the signature. The difference between this + * and just getting the first byte of the signature is that you won't + * get DBUS_STRUCT_BEGIN_CHAR, you'll get DBUS_TYPE_STRUCT + * instead. + * + * @param str string containing signature + * @param pos where the signature starts + * @returns the first type in the signature + */ +int +_dbus_first_type_in_signature (const DBusString *str, + int pos) +{ + return map_type_char_to_type (_dbus_string_get_byte (str, pos)); +} + +/** + * Similar to #_dbus_first_type_in_signature, but operates + * on a C string buffer. + * + * @param str a C string buffer + * @param pos where the signature starts + * @returns the first type in the signature + */ +int +_dbus_first_type_in_signature_c_str (const char *str, + int pos) +{ + return map_type_char_to_type (str[pos]); +} + +/** @} */ + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +#include "dbus-test.h" +#include <stdio.h> + +/** + * Reads a block of fixed-length basic values, as an optimization + * vs. reading each one individually into a new buffer. + * + * This function returns the data in-place; it does not make a copy, + * and it does not swap the bytes. + * + * If you ask for #DBUS_TYPE_DOUBLE you will get a "const double*" back + * and the "value" argument should be a "const double**" and so on. + * + * @param str the string to read from + * @param pos position to read from + * @param element_type type of array elements + * @param value place to return the array + * @param n_elements number of array elements to read + * @param byte_order the byte order, used to read the array length + * @param new_pos #NULL or location to store a position after the elements + */ +void +_dbus_marshal_read_fixed_multi (const DBusString *str, + int pos, + int element_type, + const void **value, + int n_elements, + int byte_order, + int *new_pos) +{ + int array_len; + int alignment; + + _dbus_assert (dbus_type_is_fixed (element_type)); + _dbus_assert (dbus_type_is_basic (element_type)); + +#if 0 + _dbus_verbose ("reading %d elements of %s\n", + n_elements, _dbus_type_to_string (element_type)); +#endif + + alignment = _dbus_type_get_alignment (element_type); + + pos = _DBUS_ALIGN_VALUE (pos, alignment); + + array_len = n_elements * alignment; + + *value = _dbus_string_get_const_data_len (str, pos, array_len); + if (new_pos) + *new_pos = pos + array_len; +} + +static void +swap_test_array (void *array, + int len_bytes, + int byte_order, + int alignment) +{ + DBusString t; + + if (alignment == 1) + return; + + _dbus_string_init_const_len (&t, array, len_bytes); + swap_array (&t, 0, len_bytes / alignment, byte_order, alignment); +} + +#define MARSHAL_BASIC(typename, byte_order, literal) \ + do { \ + v_##typename = literal; \ + if (!_dbus_marshal_write_basic (&str, pos, DBUS_TYPE_##typename, \ + &v_##typename, \ + byte_order, NULL)) \ + _dbus_test_fatal ("no memory"); \ + } while (0) + +#define DEMARSHAL_BASIC(typename, byte_order) \ + do { \ + _dbus_marshal_read_basic (&str, pos, DBUS_TYPE_##typename, &v_##typename, \ + byte_order, &pos); \ + } while (0) + +#define DEMARSHAL_BASIC_AND_CHECK(typename, byte_order, literal) \ + do { \ + DEMARSHAL_BASIC (typename, byte_order); \ + if (literal != v_##typename) \ + { \ + _dbus_verbose_bytes_of_string (&str, dump_pos, \ + _dbus_string_get_length (&str) - dump_pos); \ + _dbus_test_fatal ("demarshaled wrong value"); \ + } \ + } while (0) + +#define MARSHAL_TEST(typename, byte_order, literal) \ + do { \ + MARSHAL_BASIC (typename, byte_order, literal); \ + dump_pos = pos; \ + DEMARSHAL_BASIC_AND_CHECK (typename, byte_order, literal); \ + } while (0) + +#define MARSHAL_TEST_STRCMP(typename, byte_order, literal) \ + do { \ + MARSHAL_BASIC (typename, byte_order, literal); \ + dump_pos = pos; \ + DEMARSHAL_BASIC (typename, byte_order); \ + if (strcmp (literal, v_##typename) != 0) \ + { \ + _dbus_verbose_bytes_of_string (&str, dump_pos, \ + _dbus_string_get_length (&str) - dump_pos); \ + _dbus_warn ("literal '%s'\nvalue '%s'", literal, v_##typename); \ + _dbus_test_fatal ("demarshaled wrong value"); \ + } \ + } while (0) + +#define MARSHAL_FIXED_ARRAY(typename, byte_order, literal) \ + do { \ + int next; \ + v_UINT32 = sizeof(literal); \ + if (!_dbus_marshal_write_basic (&str, pos, DBUS_TYPE_UINT32, &v_UINT32, \ + byte_order, &next)) \ + _dbus_test_fatal ("no memory"); \ + v_ARRAY_##typename = literal; \ + if (!_dbus_marshal_write_fixed_multi (&str, next, DBUS_TYPE_##typename, \ + &v_ARRAY_##typename, _DBUS_N_ELEMENTS(literal), \ + byte_order, NULL)) \ + _dbus_test_fatal ("no memory"); \ + } while (0) + +#define DEMARSHAL_FIXED_ARRAY(typename, byte_order) \ + do { \ + int next; \ + alignment = _dbus_type_get_alignment (DBUS_TYPE_##typename); \ + v_UINT32 = _dbus_marshal_read_uint32 (&str, dump_pos, byte_order, &next); \ + _dbus_marshal_read_fixed_multi (&str, next, DBUS_TYPE_##typename, \ + (const void **) &v_ARRAY_##typename, \ + v_UINT32/alignment, \ + byte_order, NULL); \ + swap_test_array (v_ARRAY_##typename, v_UINT32, \ + byte_order, alignment); \ + } while (0) + +#define DEMARSHAL_FIXED_ARRAY_AND_CHECK(typename, byte_order, literal) \ + do { \ + DEMARSHAL_FIXED_ARRAY (typename, byte_order); \ + if (memcmp (literal, v_ARRAY_##typename, sizeof (literal)) != 0) \ + { \ + _dbus_verbose ("MARSHALED DATA\n"); \ + _dbus_verbose_bytes_of_string (&str, dump_pos, \ + _dbus_string_get_length (&str) - dump_pos); \ + _dbus_verbose ("LITERAL DATA\n"); \ + _dbus_verbose_bytes ((const unsigned char *) literal, sizeof (literal), 0); \ + _dbus_verbose ("READ DATA\n"); \ + _dbus_verbose_bytes ((const unsigned char *) v_ARRAY_##typename, sizeof (literal), 0); \ + _dbus_test_fatal ("demarshaled wrong fixed array value"); \ + } \ + } while (0) + +#define MARSHAL_TEST_FIXED_ARRAY(typename, byte_order, literal) \ + do { \ + MARSHAL_FIXED_ARRAY (typename, byte_order, literal); \ + dump_pos = pos; \ + DEMARSHAL_FIXED_ARRAY_AND_CHECK (typename, byte_order, literal); \ + } while (0) + +dbus_bool_t +_dbus_marshal_test (const char *test_data_dir _DBUS_GNUC_UNUSED) +{ + int alignment; + DBusString str; + int pos, dump_pos; + unsigned char array1[5] = { 3, 4, 0, 1, 9 }; + dbus_int16_t array2[3] = { 124, 457, 780 }; + dbus_uint16_t array2u[3] = { 124, 457, 780 }; + dbus_int32_t array4[3] = { 123, 456, 789 }; + dbus_uint32_t array4u[3] = { 123, 456, 789 }; + dbus_int64_t array8[3] = { DBUS_INT64_CONSTANT (0x123ffffffff), + DBUS_INT64_CONSTANT (0x456ffffffff), + DBUS_INT64_CONSTANT (0x789ffffffff) }; + dbus_int64_t *v_ARRAY_INT64; + unsigned char *v_ARRAY_BYTE; + dbus_int16_t *v_ARRAY_INT16; + dbus_uint16_t *v_ARRAY_UINT16; + dbus_int32_t *v_ARRAY_INT32; + dbus_uint32_t *v_ARRAY_UINT32; + DBusString t; + double v_DOUBLE; + double t_DOUBLE; + dbus_int16_t v_INT16; + dbus_uint16_t v_UINT16; + dbus_int32_t v_INT32; + dbus_uint32_t v_UINT32; + dbus_int64_t v_INT64; + dbus_uint64_t v_UINT64; + unsigned char v_BYTE; + dbus_bool_t v_BOOLEAN; + const char *v_STRING; + const char *v_SIGNATURE; + const char *v_OBJECT_PATH; + int byte_order; + + if (!_dbus_string_init (&str)) + _dbus_test_fatal ("failed to init string"); + + pos = 0; + + /* Marshal doubles */ + MARSHAL_BASIC (DOUBLE, DBUS_BIG_ENDIAN, 3.14); + DEMARSHAL_BASIC (DOUBLE, DBUS_BIG_ENDIAN); + t_DOUBLE = 3.14; + if (!_DBUS_DOUBLES_BITWISE_EQUAL (t_DOUBLE, v_DOUBLE)) + _dbus_test_fatal ("got wrong double value"); + + MARSHAL_BASIC (DOUBLE, DBUS_LITTLE_ENDIAN, 3.14); + DEMARSHAL_BASIC (DOUBLE, DBUS_LITTLE_ENDIAN); + t_DOUBLE = 3.14; + if (!_DBUS_DOUBLES_BITWISE_EQUAL (t_DOUBLE, v_DOUBLE)) + _dbus_test_fatal ("got wrong double value"); + + /* Marshal signed 16 integers */ + MARSHAL_TEST (INT16, DBUS_BIG_ENDIAN, -12345); + MARSHAL_TEST (INT16, DBUS_LITTLE_ENDIAN, -12345); + + /* Marshal unsigned 16 integers */ + MARSHAL_TEST (UINT16, DBUS_BIG_ENDIAN, 0x1234); + MARSHAL_TEST (UINT16, DBUS_LITTLE_ENDIAN, 0x1234); + + /* Marshal signed integers */ + MARSHAL_TEST (INT32, DBUS_BIG_ENDIAN, -12345678); + MARSHAL_TEST (INT32, DBUS_LITTLE_ENDIAN, -12345678); + + /* Marshal unsigned integers */ + MARSHAL_TEST (UINT32, DBUS_BIG_ENDIAN, 0x12345678); + MARSHAL_TEST (UINT32, DBUS_LITTLE_ENDIAN, 0x12345678); + + /* Marshal signed integers */ + MARSHAL_TEST (INT64, DBUS_BIG_ENDIAN, DBUS_INT64_CONSTANT (-0x123456789abc7)); + MARSHAL_TEST (INT64, DBUS_LITTLE_ENDIAN, DBUS_INT64_CONSTANT (-0x123456789abc7)); + + /* Marshal unsigned integers */ + MARSHAL_TEST (UINT64, DBUS_BIG_ENDIAN, DBUS_UINT64_CONSTANT (0x123456789abc7)); + MARSHAL_TEST (UINT64, DBUS_LITTLE_ENDIAN, DBUS_UINT64_CONSTANT (0x123456789abc7)); + + /* Marshal byte */ + MARSHAL_TEST (BYTE, DBUS_BIG_ENDIAN, 5); + MARSHAL_TEST (BYTE, DBUS_LITTLE_ENDIAN, 5); + + /* Marshal all possible bools! */ + MARSHAL_TEST (BOOLEAN, DBUS_BIG_ENDIAN, FALSE); + MARSHAL_TEST (BOOLEAN, DBUS_LITTLE_ENDIAN, FALSE); + MARSHAL_TEST (BOOLEAN, DBUS_BIG_ENDIAN, TRUE); + MARSHAL_TEST (BOOLEAN, DBUS_LITTLE_ENDIAN, TRUE); + + /* Marshal strings */ + MARSHAL_TEST_STRCMP (STRING, DBUS_BIG_ENDIAN, ""); + MARSHAL_TEST_STRCMP (STRING, DBUS_LITTLE_ENDIAN, ""); + MARSHAL_TEST_STRCMP (STRING, DBUS_BIG_ENDIAN, "This is the dbus test string"); + MARSHAL_TEST_STRCMP (STRING, DBUS_LITTLE_ENDIAN, "This is the dbus test string"); + + /* object paths */ + MARSHAL_TEST_STRCMP (OBJECT_PATH, DBUS_BIG_ENDIAN, "/a/b/c"); + MARSHAL_TEST_STRCMP (OBJECT_PATH, DBUS_LITTLE_ENDIAN, "/a/b/c"); + + /* signatures */ + MARSHAL_TEST_STRCMP (SIGNATURE, DBUS_BIG_ENDIAN, ""); + MARSHAL_TEST_STRCMP (SIGNATURE, DBUS_LITTLE_ENDIAN, ""); + MARSHAL_TEST_STRCMP (SIGNATURE, DBUS_BIG_ENDIAN, "a(ii)"); + MARSHAL_TEST_STRCMP (SIGNATURE, DBUS_LITTLE_ENDIAN, "a(ii)"); + + /* Arrays */ + MARSHAL_TEST_FIXED_ARRAY (INT16, DBUS_BIG_ENDIAN, array2); + MARSHAL_TEST_FIXED_ARRAY (INT16, DBUS_LITTLE_ENDIAN, array2); + MARSHAL_TEST_FIXED_ARRAY (UINT16, DBUS_BIG_ENDIAN, array2u); + MARSHAL_TEST_FIXED_ARRAY (UINT16, DBUS_LITTLE_ENDIAN, array2u); + + MARSHAL_TEST_FIXED_ARRAY (INT32, DBUS_BIG_ENDIAN, array4); + MARSHAL_TEST_FIXED_ARRAY (INT32, DBUS_LITTLE_ENDIAN, array4); + MARSHAL_TEST_FIXED_ARRAY (UINT32, DBUS_BIG_ENDIAN, array4u); + MARSHAL_TEST_FIXED_ARRAY (UINT32, DBUS_LITTLE_ENDIAN, array4u); + + MARSHAL_TEST_FIXED_ARRAY (BYTE, DBUS_BIG_ENDIAN, array1); + MARSHAL_TEST_FIXED_ARRAY (BYTE, DBUS_LITTLE_ENDIAN, array1); + + MARSHAL_TEST_FIXED_ARRAY (INT64, DBUS_BIG_ENDIAN, array8); + MARSHAL_TEST_FIXED_ARRAY (INT64, DBUS_LITTLE_ENDIAN, array8); + +#if 0 + + /* + * FIXME restore the set/pack tests + */ + + /* set/pack 64-bit integers */ + _dbus_string_set_length (&str, 8); + + /* signed little */ + _dbus_marshal_set_int64 (&str, DBUS_LITTLE_ENDIAN, + 0, DBUS_INT64_CONSTANT (-0x123456789abc7)); + + _dbus_assert (DBUS_INT64_CONSTANT (-0x123456789abc7) == + _dbus_unpack_int64 (DBUS_LITTLE_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* signed big */ + _dbus_marshal_set_int64 (&str, DBUS_BIG_ENDIAN, + 0, DBUS_INT64_CONSTANT (-0x123456789abc7)); + + _dbus_assert (DBUS_INT64_CONSTANT (-0x123456789abc7) == + _dbus_unpack_int64 (DBUS_BIG_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* signed little pack */ + _dbus_pack_int64 (DBUS_INT64_CONSTANT (-0x123456789abc7), + DBUS_LITTLE_ENDIAN, + _dbus_string_get_data (&str)); + + _dbus_assert (DBUS_INT64_CONSTANT (-0x123456789abc7) == + _dbus_unpack_int64 (DBUS_LITTLE_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* signed big pack */ + _dbus_pack_int64 (DBUS_INT64_CONSTANT (-0x123456789abc7), + DBUS_BIG_ENDIAN, + _dbus_string_get_data (&str)); + + _dbus_assert (DBUS_INT64_CONSTANT (-0x123456789abc7) == + _dbus_unpack_int64 (DBUS_BIG_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* unsigned little */ + _dbus_marshal_set_uint64 (&str, DBUS_LITTLE_ENDIAN, + 0, DBUS_UINT64_CONSTANT (0x123456789abc7)); + + _dbus_assert (DBUS_UINT64_CONSTANT (0x123456789abc7) == + _dbus_unpack_uint64 (DBUS_LITTLE_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* unsigned big */ + _dbus_marshal_set_uint64 (&str, DBUS_BIG_ENDIAN, + 0, DBUS_UINT64_CONSTANT (0x123456789abc7)); + + _dbus_assert (DBUS_UINT64_CONSTANT (0x123456789abc7) == + _dbus_unpack_uint64 (DBUS_BIG_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* unsigned little pack */ + _dbus_pack_uint64 (DBUS_UINT64_CONSTANT (0x123456789abc7), + DBUS_LITTLE_ENDIAN, + _dbus_string_get_data (&str)); + + _dbus_assert (DBUS_UINT64_CONSTANT (0x123456789abc7) == + _dbus_unpack_uint64 (DBUS_LITTLE_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* unsigned big pack */ + _dbus_pack_uint64 (DBUS_UINT64_CONSTANT (0x123456789abc7), + DBUS_BIG_ENDIAN, + _dbus_string_get_data (&str)); + + _dbus_assert (DBUS_UINT64_CONSTANT (0x123456789abc7) == + _dbus_unpack_uint64 (DBUS_BIG_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* set/pack 32-bit integers */ + _dbus_string_set_length (&str, 4); + + /* signed little */ + _dbus_marshal_set_int32 (&str, DBUS_LITTLE_ENDIAN, + 0, -0x123456); + + _dbus_assert (-0x123456 == + _dbus_unpack_int32 (DBUS_LITTLE_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* signed big */ + _dbus_marshal_set_int32 (&str, DBUS_BIG_ENDIAN, + 0, -0x123456); + + _dbus_assert (-0x123456 == + _dbus_unpack_int32 (DBUS_BIG_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* signed little pack */ + _dbus_pack_int32 (-0x123456, + DBUS_LITTLE_ENDIAN, + _dbus_string_get_data (&str)); + + _dbus_assert (-0x123456 == + _dbus_unpack_int32 (DBUS_LITTLE_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* signed big pack */ + _dbus_pack_int32 (-0x123456, + DBUS_BIG_ENDIAN, + _dbus_string_get_data (&str)); + + _dbus_assert (-0x123456 == + _dbus_unpack_int32 (DBUS_BIG_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* unsigned little */ + _dbus_marshal_set_uint32 (&str, + 0, 0x123456, + DBUS_LITTLE_ENDIAN); + + _dbus_assert (0x123456 == + _dbus_unpack_uint32 (DBUS_LITTLE_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* unsigned big */ + _dbus_marshal_set_uint32 (&str, + 0, 0x123456, + DBUS_BIG_ENDIAN); + + _dbus_assert (0x123456 == + _dbus_unpack_uint32 (DBUS_BIG_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* unsigned little pack */ + _dbus_pack_uint32 (0x123456, + DBUS_LITTLE_ENDIAN, + _dbus_string_get_data (&str)); + + _dbus_assert (0x123456 == + _dbus_unpack_uint32 (DBUS_LITTLE_ENDIAN, + _dbus_string_get_const_data (&str))); + + /* unsigned big pack */ + _dbus_pack_uint32 (0x123456, + DBUS_BIG_ENDIAN, + _dbus_string_get_data (&str)); + + _dbus_assert (0x123456 == + _dbus_unpack_uint32 (DBUS_BIG_ENDIAN, + _dbus_string_get_const_data (&str))); + +#endif /* set/pack tests for integers */ + + /* Strings in-place set */ + byte_order = DBUS_LITTLE_ENDIAN; + while (TRUE) + { + /* Init a string */ + _dbus_string_set_length (&str, 0); + + /* reset pos for the macros */ + pos = 0; + + MARSHAL_TEST_STRCMP (STRING, byte_order, "Hello world"); + + /* Set it to something longer */ + _dbus_string_init_const (&t, "Hello world foo"); + + v_STRING = _dbus_string_get_const_data (&t); + _dbus_marshal_set_basic (&str, 0, DBUS_TYPE_STRING, + &v_STRING, byte_order, NULL, NULL); + + _dbus_marshal_read_basic (&str, 0, DBUS_TYPE_STRING, + &v_STRING, byte_order, + NULL); + _dbus_assert (strcmp (v_STRING, "Hello world foo") == 0); + + /* Set it to something shorter */ + _dbus_string_init_const (&t, "Hello"); + + v_STRING = _dbus_string_get_const_data (&t); + _dbus_marshal_set_basic (&str, 0, DBUS_TYPE_STRING, + &v_STRING, byte_order, NULL, NULL); + _dbus_marshal_read_basic (&str, 0, DBUS_TYPE_STRING, + &v_STRING, byte_order, + NULL); + _dbus_assert (strcmp (v_STRING, "Hello") == 0); + + /* Do the other byte order */ + if (byte_order == DBUS_LITTLE_ENDIAN) + byte_order = DBUS_BIG_ENDIAN; + else + break; + } + + /* Clean up */ + _dbus_string_free (&str); + + return TRUE; +} + +#endif /* DBUS_ENABLE_EMBEDDED_TESTS */ diff --git a/src/3rdparty/libdbus/dbus/dbus-marshal-basic.h b/src/3rdparty/libdbus/dbus/dbus-marshal-basic.h new file mode 100644 index 00000000..48b90d6a --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-marshal-basic.h @@ -0,0 +1,233 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-marshal-basic.h Marshalling routines for basic (primitive) types + * + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2004, 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_MARSHAL_BASIC_H +#define DBUS_MARSHAL_BASIC_H + +#ifdef HAVE_BYTESWAP_H +#include <byteswap.h> +#endif + +#include <dbus/dbus-protocol.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-arch-deps.h> +#include <dbus/dbus-string.h> + +#ifdef WORDS_BIGENDIAN +#define DBUS_COMPILER_BYTE_ORDER DBUS_BIG_ENDIAN +#else +#define DBUS_COMPILER_BYTE_ORDER DBUS_LITTLE_ENDIAN +#endif + +#ifdef HAVE_BYTESWAP_H +#define DBUS_UINT16_SWAP_LE_BE_CONSTANT(val) bswap_16(val) +#define DBUS_UINT32_SWAP_LE_BE_CONSTANT(val) bswap_32(val) +#else /* HAVE_BYTESWAP_H */ + +#define DBUS_UINT16_SWAP_LE_BE_CONSTANT(val) ((dbus_uint16_t) ( \ + (dbus_uint16_t) ((dbus_uint16_t) (val) >> 8) | \ + (dbus_uint16_t) ((dbus_uint16_t) (val) << 8))) + +#define DBUS_UINT32_SWAP_LE_BE_CONSTANT(val) ((dbus_uint32_t) ( \ + (((dbus_uint32_t) (val) & (dbus_uint32_t) 0x000000ffU) << 24) | \ + (((dbus_uint32_t) (val) & (dbus_uint32_t) 0x0000ff00U) << 8) | \ + (((dbus_uint32_t) (val) & (dbus_uint32_t) 0x00ff0000U) >> 8) | \ + (((dbus_uint32_t) (val) & (dbus_uint32_t) 0xff000000U) >> 24))) + +#endif /* HAVE_BYTESWAP_H */ + +#ifdef HAVE_BYTESWAP_H +#define DBUS_UINT64_SWAP_LE_BE_CONSTANT(val) bswap_64(val) +#else /* HAVE_BYTESWAP_H */ + +#define DBUS_UINT64_SWAP_LE_BE_CONSTANT(val) ((dbus_uint64_t) ( \ + (((dbus_uint64_t) (val) & \ + (dbus_uint64_t) DBUS_UINT64_CONSTANT (0x00000000000000ff)) << 56) | \ + (((dbus_uint64_t) (val) & \ + (dbus_uint64_t) DBUS_UINT64_CONSTANT (0x000000000000ff00)) << 40) | \ + (((dbus_uint64_t) (val) & \ + (dbus_uint64_t) DBUS_UINT64_CONSTANT (0x0000000000ff0000)) << 24) | \ + (((dbus_uint64_t) (val) & \ + (dbus_uint64_t) DBUS_UINT64_CONSTANT (0x00000000ff000000)) << 8) | \ + (((dbus_uint64_t) (val) & \ + (dbus_uint64_t) DBUS_UINT64_CONSTANT (0x000000ff00000000)) >> 8) | \ + (((dbus_uint64_t) (val) & \ + (dbus_uint64_t) DBUS_UINT64_CONSTANT (0x0000ff0000000000)) >> 24) | \ + (((dbus_uint64_t) (val) & \ + (dbus_uint64_t) DBUS_UINT64_CONSTANT (0x00ff000000000000)) >> 40) | \ + (((dbus_uint64_t) (val) & \ + (dbus_uint64_t) DBUS_UINT64_CONSTANT (0xff00000000000000)) >> 56))) + +#endif /* HAVE_BYTESWAP_H */ + +#define DBUS_UINT16_SWAP_LE_BE(val) (DBUS_UINT16_SWAP_LE_BE_CONSTANT (val)) +#define DBUS_INT16_SWAP_LE_BE(val) ((dbus_int16_t)DBUS_UINT16_SWAP_LE_BE_CONSTANT (val)) + +#define DBUS_UINT32_SWAP_LE_BE(val) (DBUS_UINT32_SWAP_LE_BE_CONSTANT (val)) +#define DBUS_INT32_SWAP_LE_BE(val) ((dbus_int32_t)DBUS_UINT32_SWAP_LE_BE_CONSTANT (val)) + +#define DBUS_UINT64_SWAP_LE_BE(val) (DBUS_UINT64_SWAP_LE_BE_CONSTANT (val)) +#define DBUS_INT64_SWAP_LE_BE(val) ((dbus_int64_t)DBUS_UINT64_SWAP_LE_BE_CONSTANT (val)) + +#ifdef WORDS_BIGENDIAN + +# define DBUS_INT16_TO_BE(val) ((dbus_int16_t) (val)) +# define DBUS_UINT16_TO_BE(val) ((dbus_uint16_t) (val)) +# define DBUS_INT16_TO_LE(val) (DBUS_INT16_SWAP_LE_BE (val)) +# define DBUS_UINT16_TO_LE(val) (DBUS_UINT16_SWAP_LE_BE (val)) +# define DBUS_INT32_TO_BE(val) ((dbus_int32_t) (val)) +# define DBUS_UINT32_TO_BE(val) ((dbus_uint32_t) (val)) +# define DBUS_INT32_TO_LE(val) (DBUS_INT32_SWAP_LE_BE (val)) +# define DBUS_UINT32_TO_LE(val) (DBUS_UINT32_SWAP_LE_BE (val)) +# define DBUS_INT64_TO_BE(val) ((dbus_int64_t) (val)) +# define DBUS_UINT64_TO_BE(val) ((dbus_uint64_t) (val)) +# define DBUS_INT64_TO_LE(val) (DBUS_INT64_SWAP_LE_BE (val)) +# define DBUS_UINT64_TO_LE(val) (DBUS_UINT64_SWAP_LE_BE (val)) + +#else /* WORDS_BIGENDIAN */ + +# define DBUS_INT16_TO_LE(val) ((dbus_int16_t) (val)) +# define DBUS_UINT16_TO_LE(val) ((dbus_uint16_t) (val)) +# define DBUS_INT16_TO_BE(val) ((dbus_int16_t) DBUS_UINT16_SWAP_LE_BE (val)) +# define DBUS_UINT16_TO_BE(val) (DBUS_UINT16_SWAP_LE_BE (val)) +# define DBUS_INT32_TO_LE(val) ((dbus_int32_t) (val)) +# define DBUS_UINT32_TO_LE(val) ((dbus_uint32_t) (val)) +# define DBUS_INT32_TO_BE(val) ((dbus_int32_t) DBUS_UINT32_SWAP_LE_BE (val)) +# define DBUS_UINT32_TO_BE(val) (DBUS_UINT32_SWAP_LE_BE (val)) +# define DBUS_INT64_TO_LE(val) ((dbus_int64_t) (val)) +# define DBUS_UINT64_TO_LE(val) ((dbus_uint64_t) (val)) +# define DBUS_INT64_TO_BE(val) ((dbus_int64_t) DBUS_UINT64_SWAP_LE_BE (val)) +# define DBUS_UINT64_TO_BE(val) (DBUS_UINT64_SWAP_LE_BE (val)) +#endif + +/* The transformation is symmetric, so the FROM just maps to the TO. */ +#define DBUS_INT16_FROM_LE(val) (DBUS_INT16_TO_LE (val)) +#define DBUS_UINT16_FROM_LE(val) (DBUS_UINT16_TO_LE (val)) +#define DBUS_INT16_FROM_BE(val) (DBUS_INT16_TO_BE (val)) +#define DBUS_UINT16_FROM_BE(val) (DBUS_UINT16_TO_BE (val)) +#define DBUS_INT32_FROM_LE(val) (DBUS_INT32_TO_LE (val)) +#define DBUS_UINT32_FROM_LE(val) (DBUS_UINT32_TO_LE (val)) +#define DBUS_INT32_FROM_BE(val) (DBUS_INT32_TO_BE (val)) +#define DBUS_UINT32_FROM_BE(val) (DBUS_UINT32_TO_BE (val)) +#define DBUS_INT64_FROM_LE(val) (DBUS_INT64_TO_LE (val)) +#define DBUS_UINT64_FROM_LE(val) (DBUS_UINT64_TO_LE (val)) +#define DBUS_INT64_FROM_BE(val) (DBUS_INT64_TO_BE (val)) +#define DBUS_UINT64_FROM_BE(val) (DBUS_UINT64_TO_BE (val)) + +#ifdef DBUS_DISABLE_ASSERT +#define _dbus_unpack_uint16(byte_order, data) \ + (((byte_order) == DBUS_LITTLE_ENDIAN) ? \ + DBUS_UINT16_FROM_LE (*(dbus_uint16_t*)(data)) : \ + DBUS_UINT16_FROM_BE (*(dbus_uint16_t*)(data))) + +#define _dbus_unpack_uint32(byte_order, data) \ + (((byte_order) == DBUS_LITTLE_ENDIAN) ? \ + DBUS_UINT32_FROM_LE (*(dbus_uint32_t*)(data)) : \ + DBUS_UINT32_FROM_BE (*(dbus_uint32_t*)(data))) +#endif + +#ifndef _dbus_unpack_uint16 +DBUS_PRIVATE_EXPORT +dbus_uint16_t _dbus_unpack_uint16 (int byte_order, + const unsigned char *data); +#endif + +void _dbus_pack_uint32 (dbus_uint32_t value, + int byte_order, + unsigned char *data); +#ifndef _dbus_unpack_uint32 +DBUS_PRIVATE_EXPORT +dbus_uint32_t _dbus_unpack_uint32 (int byte_order, + const unsigned char *data); +#endif + +dbus_bool_t _dbus_marshal_set_basic (DBusString *str, + int pos, + int type, + const void *value, + int byte_order, + int *old_end_pos, + int *new_end_pos); +dbus_bool_t _dbus_marshal_write_basic (DBusString *str, + int insert_at, + int type, + const void *value, + int byte_order, + int *pos_after); +dbus_bool_t _dbus_marshal_write_fixed_multi (DBusString *str, + int insert_at, + int element_type, + const void *value, + int n_elements, + int byte_order, + int *pos_after); +void _dbus_marshal_read_basic (const DBusString *str, + int pos, + int type, + void *value, + int byte_order, + int *new_pos); +void _dbus_marshal_read_fixed_multi (const DBusString *str, + int pos, + int element_type, + const void **value, + int n_elements, + int byte_order, + int *new_pos); +void _dbus_marshal_skip_basic (const DBusString *str, + int type, + int byte_order, + int *pos); +void _dbus_marshal_skip_array (const DBusString *str, + int element_type, + int byte_order, + int *pos); +DBUS_PRIVATE_EXPORT +void _dbus_marshal_set_uint32 (DBusString *str, + int pos, + dbus_uint32_t value, + int byte_order); +DBUS_PRIVATE_EXPORT +dbus_uint32_t _dbus_marshal_read_uint32 (const DBusString *str, + int pos, + int byte_order, + int *new_pos); +int _dbus_type_get_alignment (int typecode); +DBUS_PRIVATE_EXPORT +const char* _dbus_type_to_string (int typecode); + +DBUS_PRIVATE_EXPORT +int _dbus_first_type_in_signature (const DBusString *str, + int pos); + +int _dbus_first_type_in_signature_c_str (const char *str, + int pos); + +void _dbus_swap_array (unsigned char *data, + int n_elements, + int alignment); + +#endif /* DBUS_MARSHAL_BASIC_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-marshal-byteswap.c b/src/3rdparty/libdbus/dbus/dbus-marshal-byteswap.c new file mode 100644 index 00000000..1aea5d07 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-marshal-byteswap.c @@ -0,0 +1,250 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-marshal-byteswap.c Swap a block of marshaled data + * + * Copyright (C) 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-marshal-byteswap.h" +#include "dbus-marshal-basic.h" +#include "dbus-signature.h" + +/** + * @addtogroup DBusMarshal + * @{ + */ + +static void +byteswap_body_helper (DBusTypeReader *reader, + dbus_bool_t walk_reader_to_end, + int old_byte_order, + int new_byte_order, + unsigned char *p, + unsigned char **new_p) +{ + int current_type; + + while ((current_type = _dbus_type_reader_get_current_type (reader)) != DBUS_TYPE_INVALID) + { + switch (current_type) + { + case DBUS_TYPE_BYTE: + ++p; + break; + + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + { + p = _DBUS_ALIGN_ADDRESS (p, 2); + *((dbus_uint16_t *) (void *) p) = + DBUS_UINT16_SWAP_LE_BE (*((dbus_uint16_t *) (void *) p)); + p += 2; + } + break; + + case DBUS_TYPE_BOOLEAN: + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_UNIX_FD: + { + p = _DBUS_ALIGN_ADDRESS (p, 4); + *((dbus_uint32_t *) (void *) p) = + DBUS_UINT32_SWAP_LE_BE (*((dbus_uint32_t *) (void *) p)); + p += 4; + } + break; + + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + { + p = _DBUS_ALIGN_ADDRESS (p, 8); + *((dbus_uint64_t *) (void *) p) = + DBUS_UINT64_SWAP_LE_BE (*((dbus_uint64_t *) (void *) p)); + p += 8; + } + break; + + case DBUS_TYPE_ARRAY: + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + { + dbus_uint32_t array_len; + + p = _DBUS_ALIGN_ADDRESS (p, 4); + + array_len = _dbus_unpack_uint32 (old_byte_order, p); + + *((dbus_uint32_t *) (void *) p) = + DBUS_UINT32_SWAP_LE_BE (*((dbus_uint32_t *) (void *) p)); + p += 4; + + if (current_type == DBUS_TYPE_ARRAY) + { + int elem_type; + int alignment; + + elem_type = _dbus_type_reader_get_element_type (reader); + alignment = _dbus_type_get_alignment (elem_type); + + _dbus_assert ((array_len / alignment) < DBUS_MAXIMUM_ARRAY_LENGTH); + + p = _DBUS_ALIGN_ADDRESS (p, alignment); + + if (dbus_type_is_fixed (elem_type)) + { + if (alignment > 1) + _dbus_swap_array (p, array_len / alignment, alignment); + p += array_len; + } + else + { + DBusTypeReader sub; + const unsigned char *array_end; + + array_end = p + array_len; + + _dbus_type_reader_recurse (reader, &sub); + + while (p < array_end) + { + byteswap_body_helper (&sub, + FALSE, + old_byte_order, + new_byte_order, + p, &p); + } + } + } + else + { + _dbus_assert (current_type == DBUS_TYPE_STRING || + current_type == DBUS_TYPE_OBJECT_PATH); + + p += (array_len + 1); /* + 1 for nul */ + } + } + break; + + case DBUS_TYPE_SIGNATURE: + { + dbus_uint32_t sig_len; + + sig_len = *p; + + p += (sig_len + 2); /* +2 for len and nul */ + } + break; + + case DBUS_TYPE_VARIANT: + { + /* 1 byte sig len, sig typecodes, align to + * contained-type-boundary, values. + */ + dbus_uint32_t sig_len; + DBusString sig; + DBusTypeReader sub; + int contained_alignment; + + sig_len = *p; + ++p; + + _dbus_string_init_const_len (&sig, (const char *) p, sig_len); + + p += (sig_len + 1); /* 1 for nul */ + + contained_alignment = _dbus_type_get_alignment (_dbus_first_type_in_signature (&sig, 0)); + + p = _DBUS_ALIGN_ADDRESS (p, contained_alignment); + + _dbus_type_reader_init_types_only (&sub, &sig, 0); + + byteswap_body_helper (&sub, FALSE, old_byte_order, new_byte_order, p, &p); + } + break; + + case DBUS_TYPE_STRUCT: + case DBUS_TYPE_DICT_ENTRY: + { + DBusTypeReader sub; + + p = _DBUS_ALIGN_ADDRESS (p, 8); + + _dbus_type_reader_recurse (reader, &sub); + + byteswap_body_helper (&sub, TRUE, old_byte_order, new_byte_order, p, &p); + } + break; + + default: + _dbus_assert_not_reached ("invalid typecode in supposedly-validated signature"); + break; + } + + if (walk_reader_to_end) + _dbus_type_reader_next (reader); + else + break; + } + + if (new_p) + *new_p = p; +} + +/** + * Byteswaps the marshaled data in the given value_str. + * + * @param signature the types in the value_str + * @param signature_start where in signature is the signature + * @param old_byte_order the old byte order + * @param new_byte_order the new byte order + * @param value_str the string containing the body + * @param value_pos where the values start + */ +void +_dbus_marshal_byteswap (const DBusString *signature, + int signature_start, + int old_byte_order, + int new_byte_order, + DBusString *value_str, + int value_pos) +{ + DBusTypeReader reader; + + _dbus_assert (value_pos >= 0); + _dbus_assert (value_pos <= _dbus_string_get_length (value_str)); + + if (old_byte_order == new_byte_order) + return; + + _dbus_type_reader_init_types_only (&reader, + signature, signature_start); + + byteswap_body_helper (&reader, TRUE, + old_byte_order, new_byte_order, + _dbus_string_get_udata_len (value_str, value_pos, 0), + NULL); +} + +/** @} */ + +/* Tests in dbus-marshal-byteswap-util.c */ diff --git a/src/3rdparty/libdbus/dbus/dbus-marshal-byteswap.h b/src/3rdparty/libdbus/dbus/dbus-marshal-byteswap.h new file mode 100644 index 00000000..b64ba385 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-marshal-byteswap.h @@ -0,0 +1,40 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-marshal-byteswap.h Swap a block of marshaled data + * + * Copyright (C) 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_MARSHAL_BYTESWAP_H +#define DBUS_MARSHAL_BYTESWAP_H + +#include <dbus/dbus-protocol.h> +#include <dbus/dbus-marshal-recursive.h> + +DBUS_PRIVATE_EXPORT +void _dbus_marshal_byteswap (const DBusString *signature, + int signature_start, + int old_byte_order, + int new_byte_order, + DBusString *value_str, + int value_pos); + +#endif /* DBUS_MARSHAL_BYTESWAP_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-marshal-header.c b/src/3rdparty/libdbus/dbus/dbus-marshal-header.c new file mode 100644 index 00000000..30636d79 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-marshal-header.c @@ -0,0 +1,1576 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-marshal-header.c Managing marshaling/demarshaling of message headers + * + * Copyright (C) 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus/dbus-shared.h" +#include "dbus-marshal-header.h" +#include "dbus-marshal-recursive.h" +#include "dbus-marshal-byteswap.h" + +/** + * @addtogroup DBusMarshal + * + * @{ + */ + + +/* Not thread locked, but strictly const/read-only so should be OK + */ +/** Static #DBusString containing the signature of a message header */ +_DBUS_STRING_DEFINE_STATIC(_dbus_header_signature_str, DBUS_HEADER_SIGNATURE); +/** Static #DBusString containing the local interface */ +_DBUS_STRING_DEFINE_STATIC(_dbus_local_interface_str, DBUS_INTERFACE_LOCAL); +/** Static #DBusString containing the local path */ +_DBUS_STRING_DEFINE_STATIC(_dbus_local_path_str, DBUS_PATH_LOCAL); + +/** Offset from start of _dbus_header_signature_str to the signature of the fields array */ +#define FIELDS_ARRAY_SIGNATURE_OFFSET 6 +/** Offset from start of _dbus_header_signature_str to the signature of an element of the fields array */ +#define FIELDS_ARRAY_ELEMENT_SIGNATURE_OFFSET 7 + + +/** Offset to byte order from start of header */ +#define BYTE_ORDER_OFFSET 0 +/** Offset to type from start of header */ +#define TYPE_OFFSET 1 +/** Offset to flags from start of header */ +#define FLAGS_OFFSET 2 +/** Offset to version from start of header */ +#define VERSION_OFFSET 3 +/** Offset to body length from start of header */ +#define BODY_LENGTH_OFFSET 4 +/** Offset to client serial from start of header */ +#define SERIAL_OFFSET 8 +/** Offset to fields array length from start of header */ +#define FIELDS_ARRAY_LENGTH_OFFSET 12 +/** Offset to first field in header */ +#define FIRST_FIELD_OFFSET 16 + +typedef struct +{ + unsigned char code; /**< the field code */ + unsigned char type; /**< the value type */ +} HeaderFieldType; + +static const HeaderFieldType +_dbus_header_field_types[DBUS_HEADER_FIELD_LAST+1] = { + { DBUS_HEADER_FIELD_INVALID, DBUS_TYPE_INVALID }, + { DBUS_HEADER_FIELD_PATH, DBUS_TYPE_OBJECT_PATH }, + { DBUS_HEADER_FIELD_INTERFACE, DBUS_TYPE_STRING }, + { DBUS_HEADER_FIELD_MEMBER, DBUS_TYPE_STRING }, + { DBUS_HEADER_FIELD_ERROR_NAME, DBUS_TYPE_STRING }, + { DBUS_HEADER_FIELD_REPLY_SERIAL, DBUS_TYPE_UINT32 }, + { DBUS_HEADER_FIELD_DESTINATION, DBUS_TYPE_STRING }, + { DBUS_HEADER_FIELD_SENDER, DBUS_TYPE_STRING }, + { DBUS_HEADER_FIELD_SIGNATURE, DBUS_TYPE_SIGNATURE }, + { DBUS_HEADER_FIELD_UNIX_FDS, DBUS_TYPE_UINT32 }, + { DBUS_HEADER_FIELD_CONTAINER_INSTANCE, DBUS_TYPE_OBJECT_PATH } +}; + +/** Macro to look up the correct type for a field */ +#define EXPECTED_TYPE_OF_FIELD(field) (_dbus_header_field_types[field].type) + +/** The most padding we could ever need for a header */ +#define MAX_POSSIBLE_HEADER_PADDING 7 +static dbus_bool_t +reserve_header_padding (DBusHeader *header) +{ + _dbus_assert (header->padding <= MAX_POSSIBLE_HEADER_PADDING); + + if (!_dbus_string_lengthen (&header->data, + MAX_POSSIBLE_HEADER_PADDING - header->padding)) + return FALSE; + header->padding = MAX_POSSIBLE_HEADER_PADDING; + return TRUE; +} + +static void +correct_header_padding (DBusHeader *header) +{ + int unpadded_len; + + _dbus_assert (header->padding == 7); + + _dbus_string_shorten (&header->data, header->padding); + unpadded_len = _dbus_string_get_length (&header->data); + + if (!_dbus_string_align_length (&header->data, 8)) + _dbus_assert_not_reached ("couldn't pad header though enough padding was preallocated"); + + header->padding = _dbus_string_get_length (&header->data) - unpadded_len; +} + +/** + * Compute the end of the header, ignoring padding. + * In the #DBusHeader diagram, this is the distance from 0 to [B]. */ +#define HEADER_END_BEFORE_PADDING(header) \ + (_dbus_string_get_length (&(header)->data) - (header)->padding) + +/** + * Invalidates all fields in the cache. This may be used when the + * cache is totally uninitialized (contains junk) so should not + * look at what's in there now. + * + * @param header the header + */ +static void +_dbus_header_cache_invalidate_all (DBusHeader *header) +{ + int i; + + i = 0; + while (i <= DBUS_HEADER_FIELD_LAST) + { + header->fields[i].value_pos = _DBUS_HEADER_FIELD_VALUE_UNKNOWN; + ++i; + } +} + +/** + * Caches one field + * + * @param header the header + * @param field_code the field + * @param variant_reader the reader for the variant in the field + */ +static void +_dbus_header_cache_one (DBusHeader *header, + int field_code, + DBusTypeReader *variant_reader) +{ + header->fields[field_code].value_pos = + _dbus_type_reader_get_value_pos (variant_reader); + +#if 0 + _dbus_verbose ("cached value_pos %d for field %d\n", + header->fields[field_code].value_pos, field_code) +#endif +} + +/** + * Returns the header's byte order. + * + * @param header the header + * @returns the byte order + */ +char +_dbus_header_get_byte_order (const DBusHeader *header) +{ + _dbus_assert (_dbus_string_get_length (&header->data) > BYTE_ORDER_OFFSET); + + return (char) _dbus_string_get_byte (&header->data, BYTE_ORDER_OFFSET); +} + +/** + * Revalidates the fields cache + * + * @param header the header + */ +static void +_dbus_header_cache_revalidate (DBusHeader *header) +{ + DBusTypeReader array; + DBusTypeReader reader; + int i; + + i = 0; + while (i <= DBUS_HEADER_FIELD_LAST) + { + header->fields[i].value_pos = _DBUS_HEADER_FIELD_VALUE_NONEXISTENT; + ++i; + } + + _dbus_type_reader_init (&reader, + _dbus_header_get_byte_order (header), + &_dbus_header_signature_str, + FIELDS_ARRAY_SIGNATURE_OFFSET, + &header->data, + FIELDS_ARRAY_LENGTH_OFFSET); + + _dbus_type_reader_recurse (&reader, &array); + + while (_dbus_type_reader_get_current_type (&array) != DBUS_TYPE_INVALID) + { + DBusTypeReader sub; + DBusTypeReader variant; + unsigned char field_code; + + _dbus_type_reader_recurse (&array, &sub); + + _dbus_assert (_dbus_type_reader_get_current_type (&sub) == DBUS_TYPE_BYTE); + _dbus_type_reader_read_basic (&sub, &field_code); + + /* Unknown fields should be ignored */ + if (field_code > DBUS_HEADER_FIELD_LAST) + goto next_field; + + _dbus_type_reader_next (&sub); + + _dbus_assert (_dbus_type_reader_get_current_type (&sub) == DBUS_TYPE_VARIANT); + _dbus_type_reader_recurse (&sub, &variant); + + _dbus_header_cache_one (header, field_code, &variant); + + next_field: + _dbus_type_reader_next (&array); + } +} + +/** + * Checks for a field, updating the cache if required. + * + * @param header the header + * @param field the field to check + * @returns #FALSE if the field doesn't exist + */ +static dbus_bool_t +_dbus_header_cache_check (DBusHeader *header, + int field) +{ + _dbus_assert (field <= DBUS_HEADER_FIELD_LAST); + + if (header->fields[field].value_pos == _DBUS_HEADER_FIELD_VALUE_UNKNOWN) + _dbus_header_cache_revalidate (header); + + if (header->fields[field].value_pos == _DBUS_HEADER_FIELD_VALUE_NONEXISTENT) + return FALSE; + + return TRUE; +} + +/** + * Checks whether a field is known not to exist. It may exist + * even if it's not known to exist. + * + * @param header the header + * @param field the field to check + * @returns #FALSE if the field definitely doesn't exist + */ +static dbus_bool_t +_dbus_header_cache_known_nonexistent (DBusHeader *header, + int field) +{ + _dbus_assert (field <= DBUS_HEADER_FIELD_LAST); + + return (header->fields[field].value_pos == _DBUS_HEADER_FIELD_VALUE_NONEXISTENT); +} + +/** + * Writes a struct of { byte, variant } with the given basic type. + * + * @param writer the writer (should be ready to write a struct) + * @param field the header field + * @param type the type of the value + * @param value the value as for _dbus_marshal_set_basic() + * @returns #FALSE if no memory + */ +static dbus_bool_t +write_basic_field (DBusTypeWriter *writer, + int field, + int type, + const void *value) +{ + DBusTypeWriter sub; + DBusTypeWriter variant; + int start; + int padding; + unsigned char field_byte; + DBusString contained_type; + char buf[2]; + + start = writer->value_pos; + padding = _dbus_string_get_length (writer->value_str) - start; + + if (!_dbus_type_writer_recurse (writer, DBUS_TYPE_STRUCT, + NULL, 0, &sub)) + goto append_failed; + + field_byte = field; + if (!_dbus_type_writer_write_basic (&sub, DBUS_TYPE_BYTE, + &field_byte)) + goto append_failed; + + buf[0] = type; + buf[1] = '\0'; + _dbus_string_init_const_len (&contained_type, buf, 1); + + if (!_dbus_type_writer_recurse (&sub, DBUS_TYPE_VARIANT, + &contained_type, 0, &variant)) + goto append_failed; + + if (!_dbus_type_writer_write_basic (&variant, type, value)) + goto append_failed; + + if (!_dbus_type_writer_unrecurse (&sub, &variant)) + goto append_failed; + + if (!_dbus_type_writer_unrecurse (writer, &sub)) + goto append_failed; + + return TRUE; + + append_failed: + _dbus_string_delete (writer->value_str, + start, + _dbus_string_get_length (writer->value_str) - start - padding); + return FALSE; +} + +/** + * Sets a struct of { byte, variant } with the given basic type. + * + * @param reader the reader (should be iterating over the array pointing at the field to set) + * @param field the header field + * @param type the type of the value + * @param value the value as for _dbus_marshal_set_basic() + * @param realign_root where to realign from + * @returns #FALSE if no memory + */ +static dbus_bool_t +set_basic_field (DBusTypeReader *reader, + int field, + int type, + const void *value, + const DBusTypeReader *realign_root) +{ + DBusTypeReader sub; + DBusTypeReader variant; + + _dbus_type_reader_recurse (reader, &sub); + + _dbus_assert (_dbus_type_reader_get_current_type (&sub) == DBUS_TYPE_BYTE); +#ifndef DBUS_DISABLE_ASSERT + { + unsigned char v_BYTE; + _dbus_type_reader_read_basic (&sub, &v_BYTE); + _dbus_assert (((int) v_BYTE) == field); + } +#endif + + if (!_dbus_type_reader_next (&sub)) + _dbus_assert_not_reached ("no variant field?"); + + _dbus_type_reader_recurse (&sub, &variant); + _dbus_assert (_dbus_type_reader_get_current_type (&variant) == type); + + if (!_dbus_type_reader_set_basic (&variant, value, realign_root)) + return FALSE; + + return TRUE; +} + +/** + * Gets the type of the message. + * + * @param header the header + * @returns the type + */ +int +_dbus_header_get_message_type (DBusHeader *header) +{ + int type; + + type = _dbus_string_get_byte (&header->data, TYPE_OFFSET); + _dbus_assert (type != DBUS_MESSAGE_TYPE_INVALID); + + return type; +} + +/** + * Sets the serial number of a header. This can only be done once on + * a header. + * + * @param header the header + * @param serial the serial + */ +void +_dbus_header_set_serial (DBusHeader *header, + dbus_uint32_t serial) +{ + /* we use this function to set the serial on outgoing + * messages, and to reset the serial in dbus_message_copy; + * this assertion should catch a double-set on outgoing. + */ + _dbus_assert (_dbus_header_get_serial (header) == 0 || + serial == 0); + + _dbus_marshal_set_uint32 (&header->data, + SERIAL_OFFSET, + serial, + _dbus_header_get_byte_order (header)); +} + +/** + * See dbus_message_get_serial() + * + * @param header the header + * @returns the client serial + */ +dbus_uint32_t +_dbus_header_get_serial (DBusHeader *header) +{ + return _dbus_marshal_read_uint32 (&header->data, + SERIAL_OFFSET, + _dbus_header_get_byte_order (header), + NULL); +} + +/** + * Re-initializes a header that was previously initialized and never + * freed. After this, to make the header valid you have to call + * _dbus_header_create(). + * + * @param header header to re-initialize + */ +void +_dbus_header_reinit (DBusHeader *header) +{ + _dbus_string_set_length (&header->data, 0); + + header->padding = 0; + + _dbus_header_cache_invalidate_all (header); +} + +/** + * Initializes a header, but doesn't prepare it for use; + * to make the header valid, you have to call _dbus_header_create(). + * + * @param header header to initialize + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_header_init (DBusHeader *header) +{ + if (!_dbus_string_init_preallocated (&header->data, 32)) + return FALSE; + + _dbus_header_reinit (header); + + return TRUE; +} + +/** + * Frees a header. + * + * @param header the header + */ +void +_dbus_header_free (DBusHeader *header) +{ + _dbus_string_free (&header->data); +} + +/** + * Initializes dest with a copy of the given header. + * Resets the message serial to 0 on the copy. + * + * @param header header to copy + * @param dest destination for copy + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_header_copy (const DBusHeader *header, + DBusHeader *dest) +{ + *dest = *header; + + if (!_dbus_string_init_preallocated (&dest->data, + _dbus_string_get_length (&header->data))) + return FALSE; + + if (!_dbus_string_copy (&header->data, 0, &dest->data, 0)) + { + _dbus_string_free (&dest->data); + return FALSE; + } + + /* Reset the serial */ + _dbus_header_set_serial (dest, 0); + + return TRUE; +} + +/** + * Fills in the primary fields of the header, so the header is ready + * for use. #NULL may be specified for some or all of the fields to + * avoid adding those fields. Some combinations of fields don't make + * sense, and passing them in will trigger an assertion failure. + * + * @param header the header + * @param byte_order byte order of the header + * @param message_type the message type + * @param destination destination field or #NULL + * @param path path field or #NULL + * @param interface interface field or #NULL + * @param member member field or #NULL + * @param error_name error name or #NULL + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_header_create (DBusHeader *header, + int byte_order, + int message_type, + const char *destination, + const char *path, + const char *interface, + const char *member, + const char *error_name) +{ + unsigned char v_BYTE; + dbus_uint32_t v_UINT32; + DBusTypeWriter writer; + DBusTypeWriter array; + + _dbus_assert (byte_order == DBUS_LITTLE_ENDIAN || + byte_order == DBUS_BIG_ENDIAN); + _dbus_assert (((interface || message_type != DBUS_MESSAGE_TYPE_SIGNAL) && member) || + (error_name) || + !(interface || member || error_name)); + _dbus_assert (_dbus_string_get_length (&header->data) == 0); + + if (!reserve_header_padding (header)) + return FALSE; + + _dbus_type_writer_init_values_only (&writer, byte_order, + &_dbus_header_signature_str, 0, + &header->data, + HEADER_END_BEFORE_PADDING (header)); + + v_BYTE = byte_order; + if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_BYTE, + &v_BYTE)) + goto oom; + + v_BYTE = message_type; + if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_BYTE, + &v_BYTE)) + goto oom; + + v_BYTE = 0; /* flags */ + if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_BYTE, + &v_BYTE)) + goto oom; + + v_BYTE = DBUS_MAJOR_PROTOCOL_VERSION; + if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_BYTE, + &v_BYTE)) + goto oom; + + v_UINT32 = 0; /* body length */ + if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_UINT32, + &v_UINT32)) + goto oom; + + v_UINT32 = 0; /* serial */ + if (!_dbus_type_writer_write_basic (&writer, DBUS_TYPE_UINT32, + &v_UINT32)) + goto oom; + + if (!_dbus_type_writer_recurse (&writer, DBUS_TYPE_ARRAY, + &_dbus_header_signature_str, + FIELDS_ARRAY_SIGNATURE_OFFSET, + &array)) + goto oom; + + /* Marshal all the fields (Marshall Fields?) */ + + if (path != NULL) + { + if (!write_basic_field (&array, + DBUS_HEADER_FIELD_PATH, + DBUS_TYPE_OBJECT_PATH, + &path)) + goto oom; + } + + if (destination != NULL) + { + if (!write_basic_field (&array, + DBUS_HEADER_FIELD_DESTINATION, + DBUS_TYPE_STRING, + &destination)) + goto oom; + } + + /* Note that test/message.c relies on this being in the middle of the + * message: if you change the order of serialization here (but why + * would you?), please find some other way to retain test coverage. */ + if (interface != NULL) + { + if (!write_basic_field (&array, + DBUS_HEADER_FIELD_INTERFACE, + DBUS_TYPE_STRING, + &interface)) + goto oom; + } + + if (member != NULL) + { + if (!write_basic_field (&array, + DBUS_HEADER_FIELD_MEMBER, + DBUS_TYPE_STRING, + &member)) + goto oom; + } + + if (error_name != NULL) + { + if (!write_basic_field (&array, + DBUS_HEADER_FIELD_ERROR_NAME, + DBUS_TYPE_STRING, + &error_name)) + goto oom; + } + + if (!_dbus_type_writer_unrecurse (&writer, &array)) + goto oom; + + correct_header_padding (header); + + return TRUE; + + oom: + _dbus_string_delete (&header->data, 0, + _dbus_string_get_length (&header->data) - header->padding); + correct_header_padding (header); + + return FALSE; +} + +/** + * Given data long enough to contain the length of the message body + * and the fields array, check whether the data is long enough to + * contain the entire message (assuming the claimed lengths are + * accurate). Also checks that the lengths are in sanity parameters. + * + * @param max_message_length maximum length of a valid message + * @param validity return location for why the data is invalid if it is + * @param byte_order return location for byte order + * @param fields_array_len return location for claimed fields array length + * @param header_len return location for claimed header length + * @param body_len return location for claimed body length + * @param str the data + * @param start start of data, 8-aligned + * @param len length of data + * @returns #TRUE if the data is long enough for the claimed length, and the lengths were valid + */ +dbus_bool_t +_dbus_header_have_message_untrusted (int max_message_length, + DBusValidity *validity, + int *byte_order, + int *fields_array_len, + int *header_len, + int *body_len, + const DBusString *str, + int start, + int len) + +{ + dbus_uint32_t header_len_unsigned; + dbus_uint32_t fields_array_len_unsigned; + dbus_uint32_t body_len_unsigned; + + _dbus_assert (start >= 0); + _dbus_assert (start < _DBUS_INT32_MAX / 2); + _dbus_assert (len >= 0); + + _dbus_assert (start == (int) _DBUS_ALIGN_VALUE (start, 8)); + + *byte_order = _dbus_string_get_byte (str, start + BYTE_ORDER_OFFSET); + + if (*byte_order != DBUS_LITTLE_ENDIAN && *byte_order != DBUS_BIG_ENDIAN) + { + *validity = DBUS_INVALID_BAD_BYTE_ORDER; + return FALSE; + } + + _dbus_assert (FIELDS_ARRAY_LENGTH_OFFSET + 4 <= len); + fields_array_len_unsigned = _dbus_marshal_read_uint32 (str, start + FIELDS_ARRAY_LENGTH_OFFSET, + *byte_order, NULL); + + if (fields_array_len_unsigned > (unsigned) max_message_length) + { + *validity = DBUS_INVALID_INSANE_FIELDS_ARRAY_LENGTH; + return FALSE; + } + + _dbus_assert (BODY_LENGTH_OFFSET + 4 < len); + body_len_unsigned = _dbus_marshal_read_uint32 (str, start + BODY_LENGTH_OFFSET, + *byte_order, NULL); + + if (body_len_unsigned > (unsigned) max_message_length) + { + *validity = DBUS_INVALID_INSANE_BODY_LENGTH; + return FALSE; + } + + header_len_unsigned = FIRST_FIELD_OFFSET + fields_array_len_unsigned; + header_len_unsigned = _DBUS_ALIGN_VALUE (header_len_unsigned, 8); + + /* overflow should be impossible since the lengths aren't allowed to + * be huge. + */ + _dbus_assert (max_message_length < _DBUS_INT32_MAX / 2); + if (body_len_unsigned + header_len_unsigned > (unsigned) max_message_length) + { + *validity = DBUS_INVALID_MESSAGE_TOO_LONG; + return FALSE; + } + + _dbus_assert (body_len_unsigned < (unsigned) _DBUS_INT32_MAX); + _dbus_assert (fields_array_len_unsigned < (unsigned) _DBUS_INT32_MAX); + _dbus_assert (header_len_unsigned < (unsigned) _DBUS_INT32_MAX); + + *body_len = body_len_unsigned; + *fields_array_len = fields_array_len_unsigned; + *header_len = header_len_unsigned; + + *validity = DBUS_VALID; + + _dbus_verbose ("have %d bytes, need body %u + header %u = %u\n", + len, body_len_unsigned, header_len_unsigned, + body_len_unsigned + header_len_unsigned); + + return (body_len_unsigned + header_len_unsigned) <= (unsigned) len; +} + +static DBusValidity +check_mandatory_fields (DBusHeader *header) +{ +#define REQUIRE_FIELD(name) do { if (header->fields[DBUS_HEADER_FIELD_##name].value_pos < 0) return DBUS_INVALID_MISSING_##name; } while (0) + + switch (_dbus_header_get_message_type (header)) + { + case DBUS_MESSAGE_TYPE_SIGNAL: + REQUIRE_FIELD (INTERFACE); + /* FALL THRU - signals also require the path and member */ + case DBUS_MESSAGE_TYPE_METHOD_CALL: + REQUIRE_FIELD (PATH); + REQUIRE_FIELD (MEMBER); + break; + case DBUS_MESSAGE_TYPE_ERROR: + REQUIRE_FIELD (ERROR_NAME); + REQUIRE_FIELD (REPLY_SERIAL); + break; + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + REQUIRE_FIELD (REPLY_SERIAL); + break; + default: + /* other message types allowed but ignored */ + break; + } + + return DBUS_VALID; +} + +static DBusValidity +load_and_validate_field (DBusHeader *header, + int field, + DBusTypeReader *variant_reader) +{ + int type; + int expected_type; + const DBusString *value_str; + int value_pos; + int str_data_pos; + dbus_uint32_t v_UINT32; + int bad_string_code; + dbus_bool_t (* string_validation_func) (const DBusString *str, + int start, int len); + + /* Supposed to have been checked already */ + _dbus_assert (field <= DBUS_HEADER_FIELD_LAST); + _dbus_assert (field != DBUS_HEADER_FIELD_INVALID); + + /* Before we can cache a field, we need to know it has the right type */ + type = _dbus_type_reader_get_current_type (variant_reader); + + _dbus_assert (_dbus_header_field_types[field].code == field); + + expected_type = EXPECTED_TYPE_OF_FIELD (field); + if (type != expected_type) + { + _dbus_verbose ("Field %d should have type %d but has %d\n", + field, expected_type, type); + return DBUS_INVALID_HEADER_FIELD_HAS_WRONG_TYPE; + } + + /* If the field was provided twice, we aren't happy */ + if (header->fields[field].value_pos >= 0) + { + _dbus_verbose ("Header field %d seen a second time\n", field); + return DBUS_INVALID_HEADER_FIELD_APPEARS_TWICE; + } + + /* Now we can cache and look at the field content */ + _dbus_verbose ("initially caching field %d\n", field); + _dbus_header_cache_one (header, field, variant_reader); + + string_validation_func = NULL; + + /* make compiler happy that all this is initialized */ + v_UINT32 = 0; + value_str = NULL; + value_pos = -1; + str_data_pos = -1; + bad_string_code = DBUS_VALID; + + if (expected_type == DBUS_TYPE_UINT32) + { + _dbus_header_get_field_basic (header, field, expected_type, + &v_UINT32); + } + else if (expected_type == DBUS_TYPE_STRING || + expected_type == DBUS_TYPE_OBJECT_PATH || + expected_type == DBUS_TYPE_SIGNATURE) + { + _dbus_header_get_field_raw (header, field, + &value_str, &value_pos); + str_data_pos = _DBUS_ALIGN_VALUE (value_pos, 4) + 4; + } + else + { + _dbus_assert_not_reached ("none of the known fields should have this type"); + } + + switch (field) + { + case DBUS_HEADER_FIELD_DESTINATION: + string_validation_func = _dbus_validate_bus_name; + bad_string_code = DBUS_INVALID_BAD_DESTINATION; + break; + case DBUS_HEADER_FIELD_INTERFACE: + string_validation_func = _dbus_validate_interface; + bad_string_code = DBUS_INVALID_BAD_INTERFACE; + + if (_dbus_string_equal_substring (&_dbus_local_interface_str, + 0, + _dbus_string_get_length (&_dbus_local_interface_str), + value_str, str_data_pos)) + { + _dbus_verbose ("Message is on the local interface\n"); + return DBUS_INVALID_USES_LOCAL_INTERFACE; + } + break; + + case DBUS_HEADER_FIELD_MEMBER: + string_validation_func = _dbus_validate_member; + bad_string_code = DBUS_INVALID_BAD_MEMBER; + break; + + case DBUS_HEADER_FIELD_ERROR_NAME: + string_validation_func = _dbus_validate_error_name; + bad_string_code = DBUS_INVALID_BAD_ERROR_NAME; + break; + + case DBUS_HEADER_FIELD_SENDER: + string_validation_func = _dbus_validate_bus_name; + bad_string_code = DBUS_INVALID_BAD_SENDER; + break; + + case DBUS_HEADER_FIELD_PATH: + /* OBJECT_PATH was validated generically due to its type */ + string_validation_func = NULL; + + if (_dbus_string_equal_substring (&_dbus_local_path_str, + 0, + _dbus_string_get_length (&_dbus_local_path_str), + value_str, str_data_pos)) + { + _dbus_verbose ("Message is from the local path\n"); + return DBUS_INVALID_USES_LOCAL_PATH; + } + break; + + case DBUS_HEADER_FIELD_REPLY_SERIAL: + /* Can't be 0 */ + if (v_UINT32 == 0) + { + return DBUS_INVALID_BAD_SERIAL; + } + break; + + case DBUS_HEADER_FIELD_UNIX_FDS: + /* Every value makes sense */ + break; + + case DBUS_HEADER_FIELD_SIGNATURE: + /* SIGNATURE validated generically due to its type */ + string_validation_func = NULL; + break; + + case DBUS_HEADER_FIELD_CONTAINER_INSTANCE: + /* OBJECT_PATH was validated generically due to its type */ + string_validation_func = NULL; + break; + + default: + _dbus_assert_not_reached ("unknown field shouldn't be seen here"); + break; + } + + if (string_validation_func) + { + dbus_uint32_t len; + + _dbus_assert (bad_string_code != DBUS_VALID); + + len = _dbus_marshal_read_uint32 (value_str, value_pos, + _dbus_header_get_byte_order (header), + NULL); + +#if 0 + _dbus_verbose ("Validating string header field; code %d if fails\n", + bad_string_code); +#endif + if (!(*string_validation_func) (value_str, str_data_pos, len)) + return bad_string_code; + } + + return DBUS_VALID; +} + +/** + * Creates a message header from potentially-untrusted data. The + * return value is #TRUE if there was enough memory and the data was + * valid. If it returns #TRUE, the header will be created. If it + * returns #FALSE and *validity == #DBUS_VALIDITY_UNKNOWN_OOM_ERROR, + * then there wasn't enough memory. If it returns #FALSE + * and *validity != #DBUS_VALIDITY_UNKNOWN_OOM_ERROR then the data was + * invalid. + * + * The byte_order, fields_array_len, and body_len args should be from + * _dbus_header_have_message_untrusted(). Validation performed in + * _dbus_header_have_message_untrusted() is assumed to have been + * already done. + * + * @param header the header (must be initialized) + * @param mode whether to do validation + * @param validity return location for invalidity reason + * @param byte_order byte order from header + * @param fields_array_len claimed length of fields array + * @param body_len claimed length of body + * @param header_len claimed length of header + * @param str a string starting with the header + * @returns #FALSE if no memory or data was invalid, #TRUE otherwise + */ +dbus_bool_t +_dbus_header_load (DBusHeader *header, + DBusValidationMode mode, + DBusValidity *validity, + int byte_order, + int fields_array_len, + int header_len, + int body_len, + const DBusString *str) +{ + int leftover; + DBusValidity v; + DBusTypeReader reader; + DBusTypeReader array_reader; + unsigned char v_byte; + dbus_uint32_t v_uint32; + dbus_uint32_t serial; + int padding_start; + int padding_len; + int i; + int len; + + len = _dbus_string_get_length (str); + + _dbus_assert (header_len <= len); + _dbus_assert (_dbus_string_get_length (&header->data) == 0); + + if (!_dbus_string_copy_len (str, 0, header_len, &header->data, 0)) + { + _dbus_verbose ("Failed to copy buffer into new header\n"); + *validity = DBUS_VALIDITY_UNKNOWN_OOM_ERROR; + return FALSE; + } + + if (mode == DBUS_VALIDATION_MODE_WE_TRUST_THIS_DATA_ABSOLUTELY) + { + leftover = len - header_len - body_len; + } + else + { + v = _dbus_validate_body_with_reason (&_dbus_header_signature_str, 0, + byte_order, + &leftover, + str, 0, len); + + if (v != DBUS_VALID) + { + *validity = v; + goto invalid; + } + } + + _dbus_assert (leftover < len); + + padding_len = header_len - (FIRST_FIELD_OFFSET + fields_array_len); + padding_start = FIRST_FIELD_OFFSET + fields_array_len; + _dbus_assert (header_len == (int) _DBUS_ALIGN_VALUE (padding_start, 8)); + _dbus_assert (header_len == padding_start + padding_len); + + if (mode != DBUS_VALIDATION_MODE_WE_TRUST_THIS_DATA_ABSOLUTELY) + { + if (!_dbus_string_validate_nul (str, padding_start, padding_len)) + { + *validity = DBUS_INVALID_ALIGNMENT_PADDING_NOT_NUL; + goto invalid; + } + } + + header->padding = padding_len; + + if (mode == DBUS_VALIDATION_MODE_WE_TRUST_THIS_DATA_ABSOLUTELY) + { + *validity = DBUS_VALID; + return TRUE; + } + + /* We now know the data is well-formed, but we have to check that + * it's valid. + */ + + _dbus_type_reader_init (&reader, + byte_order, + &_dbus_header_signature_str, 0, + str, 0); + + /* BYTE ORDER */ + _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_BYTE); + _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == BYTE_ORDER_OFFSET); + _dbus_type_reader_read_basic (&reader, &v_byte); + _dbus_type_reader_next (&reader); + + _dbus_assert (v_byte == byte_order); + + /* MESSAGE TYPE */ + _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_BYTE); + _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == TYPE_OFFSET); + _dbus_type_reader_read_basic (&reader, &v_byte); + _dbus_type_reader_next (&reader); + + /* unknown message types are supposed to be ignored, so only validation here is + * that it isn't invalid + */ + if (v_byte == DBUS_MESSAGE_TYPE_INVALID) + { + *validity = DBUS_INVALID_BAD_MESSAGE_TYPE; + goto invalid; + } + + /* FLAGS */ + _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_BYTE); + _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == FLAGS_OFFSET); + _dbus_type_reader_read_basic (&reader, &v_byte); + _dbus_type_reader_next (&reader); + + /* unknown flags should be ignored */ + + /* PROTOCOL VERSION */ + _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_BYTE); + _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == VERSION_OFFSET); + _dbus_type_reader_read_basic (&reader, &v_byte); + _dbus_type_reader_next (&reader); + + if (v_byte != DBUS_MAJOR_PROTOCOL_VERSION) + { + *validity = DBUS_INVALID_BAD_PROTOCOL_VERSION; + goto invalid; + } + + /* BODY LENGTH */ + _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_UINT32); + _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == BODY_LENGTH_OFFSET); + _dbus_type_reader_read_basic (&reader, &v_uint32); + _dbus_type_reader_next (&reader); + + _dbus_assert (body_len == (signed) v_uint32); + + /* SERIAL */ + _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_UINT32); + _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == SERIAL_OFFSET); + _dbus_type_reader_read_basic (&reader, &serial); + _dbus_type_reader_next (&reader); + + if (serial == 0) + { + *validity = DBUS_INVALID_BAD_SERIAL; + goto invalid; + } + + _dbus_assert (_dbus_type_reader_get_current_type (&reader) == DBUS_TYPE_ARRAY); + _dbus_assert (_dbus_type_reader_get_value_pos (&reader) == FIELDS_ARRAY_LENGTH_OFFSET); + + _dbus_type_reader_recurse (&reader, &array_reader); + while (_dbus_type_reader_get_current_type (&array_reader) != DBUS_TYPE_INVALID) + { + DBusTypeReader struct_reader; + DBusTypeReader variant_reader; + unsigned char field_code; + + _dbus_assert (_dbus_type_reader_get_current_type (&array_reader) == DBUS_TYPE_STRUCT); + + _dbus_type_reader_recurse (&array_reader, &struct_reader); + + _dbus_assert (_dbus_type_reader_get_current_type (&struct_reader) == DBUS_TYPE_BYTE); + _dbus_type_reader_read_basic (&struct_reader, &field_code); + _dbus_type_reader_next (&struct_reader); + + if (field_code == DBUS_HEADER_FIELD_INVALID) + { + _dbus_verbose ("invalid header field code\n"); + *validity = DBUS_INVALID_HEADER_FIELD_CODE; + goto invalid; + } + + if (field_code > DBUS_HEADER_FIELD_LAST) + { + _dbus_verbose ("unknown header field code %d, skipping\n", + field_code); + goto next_field; + } + + _dbus_assert (_dbus_type_reader_get_current_type (&struct_reader) == DBUS_TYPE_VARIANT); + _dbus_type_reader_recurse (&struct_reader, &variant_reader); + + v = load_and_validate_field (header, field_code, &variant_reader); + if (v != DBUS_VALID) + { + _dbus_verbose ("Field %d was invalid\n", field_code); + *validity = v; + goto invalid; + } + + next_field: + _dbus_type_reader_next (&array_reader); + } + + /* Anything we didn't fill in is now known not to exist */ + i = 0; + while (i <= DBUS_HEADER_FIELD_LAST) + { + if (header->fields[i].value_pos == _DBUS_HEADER_FIELD_VALUE_UNKNOWN) + header->fields[i].value_pos = _DBUS_HEADER_FIELD_VALUE_NONEXISTENT; + ++i; + } + + v = check_mandatory_fields (header); + if (v != DBUS_VALID) + { + _dbus_verbose ("Mandatory fields were missing, code %d\n", v); + *validity = v; + goto invalid; + } + + *validity = DBUS_VALID; + return TRUE; + + invalid: + _dbus_string_set_length (&header->data, 0); + return FALSE; +} + +/** + * Fills in the correct body length. + * + * @param header the header + * @param body_len the length of the body + */ +void +_dbus_header_update_lengths (DBusHeader *header, + int body_len) +{ + _dbus_marshal_set_uint32 (&header->data, + BODY_LENGTH_OFFSET, + body_len, + _dbus_header_get_byte_order (header)); +} + +/** + * Try to find the given field. + * + * @param header the header + * @param field the field code + * @param reader a type reader; on success this is left pointing at the struct + * (uv) for the field, while on failure it is left pointing into empty space + * at the end of the header fields + * @param realign_root another type reader; on success or failure it is left + * pointing to the beginning of the array of fields (i.e. the thing that might + * need realigning) + * @returns #TRUE on success + */ +static dbus_bool_t +find_field_for_modification (DBusHeader *header, + int field, + DBusTypeReader *reader, + DBusTypeReader *realign_root) +{ + dbus_bool_t retval; + + retval = FALSE; + + _dbus_type_reader_init (realign_root, + _dbus_header_get_byte_order (header), + &_dbus_header_signature_str, + FIELDS_ARRAY_SIGNATURE_OFFSET, + &header->data, + FIELDS_ARRAY_LENGTH_OFFSET); + + _dbus_type_reader_recurse (realign_root, reader); + + while (_dbus_type_reader_get_current_type (reader) != DBUS_TYPE_INVALID) + { + DBusTypeReader sub; + unsigned char field_code; + + _dbus_type_reader_recurse (reader, &sub); + + _dbus_assert (_dbus_type_reader_get_current_type (&sub) == DBUS_TYPE_BYTE); + _dbus_type_reader_read_basic (&sub, &field_code); + + if (field_code == (unsigned) field) + { + _dbus_assert (_dbus_type_reader_get_current_type (reader) == DBUS_TYPE_STRUCT); + retval = TRUE; + goto done; + } + + _dbus_type_reader_next (reader); + } + + done: + return retval; +} + +/** + * Sets the value of a field with basic type. If the value is a string + * value, it isn't allowed to be #NULL. If the field doesn't exist, + * it will be created. + * + * @param header the header + * @param field the field to set + * @param type the type of the value + * @param value the value as for _dbus_marshal_set_basic() + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_header_set_field_basic (DBusHeader *header, + int field, + int type, + const void *value) +{ + _dbus_assert (field <= DBUS_HEADER_FIELD_LAST); + + if (!reserve_header_padding (header)) + return FALSE; + + /* If the field exists we set, otherwise we append */ + if (_dbus_header_cache_check (header, field)) + { + DBusTypeReader reader; + DBusTypeReader realign_root; + + if (!find_field_for_modification (header, field, + &reader, &realign_root)) + _dbus_assert_not_reached ("field was marked present in cache but wasn't found"); + + if (!set_basic_field (&reader, field, type, value, &realign_root)) + return FALSE; + } + else + { + DBusTypeWriter writer; + DBusTypeWriter array; + + _dbus_type_writer_init_values_only (&writer, + _dbus_header_get_byte_order (header), + &_dbus_header_signature_str, + FIELDS_ARRAY_SIGNATURE_OFFSET, + &header->data, + FIELDS_ARRAY_LENGTH_OFFSET); + + /* recurse into array without creating a new length, and jump to + * end of array. + */ + if (!_dbus_type_writer_append_array (&writer, + &_dbus_header_signature_str, + FIELDS_ARRAY_ELEMENT_SIGNATURE_OFFSET, + &array)) + _dbus_assert_not_reached ("recurse into ARRAY should not have used memory"); + + _dbus_assert (array.u.array.len_pos == FIELDS_ARRAY_LENGTH_OFFSET); + _dbus_assert (array.u.array.start_pos == FIRST_FIELD_OFFSET); + _dbus_assert (array.value_pos == HEADER_END_BEFORE_PADDING (header)); + + if (!write_basic_field (&array, + field, type, value)) + return FALSE; + + if (!_dbus_type_writer_unrecurse (&writer, &array)) + _dbus_assert_not_reached ("unrecurse from ARRAY should not have used memory"); + } + + correct_header_padding (header); + + /* We could be smarter about this (only invalidate fields after the + * one we modified, or even only if the one we modified changed + * length). But this hack is a start. + */ + _dbus_header_cache_invalidate_all (header); + + return TRUE; +} + +/** + * Gets the value of a field with basic type. If the field + * doesn't exist, returns #FALSE, otherwise returns #TRUE. + * + * @param header the header + * @param field the field to get + * @param type the type of the value + * @param value the value as for _dbus_marshal_read_basic() + * @returns #FALSE if the field doesn't exist + */ +dbus_bool_t +_dbus_header_get_field_basic (DBusHeader *header, + int field, + int type, + void *value) +{ + _dbus_assert (field != DBUS_HEADER_FIELD_INVALID); + _dbus_assert (field <= DBUS_HEADER_FIELD_LAST); + _dbus_assert (_dbus_header_field_types[field].code == field); + /* in light of this you might ask why the type is passed in; + * the only rationale I can think of is so the caller has + * to specify its expectation and breaks if we change it + */ + _dbus_assert (type == EXPECTED_TYPE_OF_FIELD (field)); + + if (!_dbus_header_cache_check (header, field)) + return FALSE; + + _dbus_assert (header->fields[field].value_pos >= 0); + + _dbus_marshal_read_basic (&header->data, + header->fields[field].value_pos, + type, value, _dbus_header_get_byte_order (header), + NULL); + + return TRUE; +} + +/** + * Gets the raw marshaled data for a field. If the field doesn't + * exist, returns #FALSE, otherwise returns #TRUE. Returns the start + * of the marshaled data, i.e. usually the byte where the length + * starts (for strings and arrays) or for basic types just the value + * itself. + * + * @param header the header + * @param field the field to get + * @param str return location for the data string + * @param pos return location for start of field value + * @returns #FALSE if the field doesn't exist + */ +dbus_bool_t +_dbus_header_get_field_raw (DBusHeader *header, + int field, + const DBusString **str, + int *pos) +{ + if (!_dbus_header_cache_check (header, field)) + return FALSE; + + if (str) + *str = &header->data; + if (pos) + *pos = header->fields[field].value_pos; + + return TRUE; +} + +/** + * Deletes a field, if it exists. + * + * @param header the header + * @param field the field to delete + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_header_delete_field (DBusHeader *header, + int field) +{ + DBusTypeReader reader; + DBusTypeReader realign_root; + + if (_dbus_header_cache_known_nonexistent (header, field)) + return TRUE; /* nothing to do */ + + /* Scan to the field we want, delete and realign, reappend + * padding. Field may turn out not to exist. + */ + if (!find_field_for_modification (header, field, + &reader, &realign_root)) + return TRUE; /* nothing to do */ + + if (!reserve_header_padding (header)) + return FALSE; + + if (!_dbus_type_reader_delete (&reader, + &realign_root)) + return FALSE; + + correct_header_padding (header); + + _dbus_header_cache_invalidate_all (header); + + _dbus_assert (!_dbus_header_cache_check (header, field)); /* Expensive assertion ... */ + + return TRUE; +} + +/** + * Toggles a message flag bit, turning on the bit if value = TRUE and + * flipping it off if value = FALSE. + * + * @param header the header + * @param flag the message flag to toggle + * @param value toggle on or off + */ +void +_dbus_header_toggle_flag (DBusHeader *header, + dbus_uint32_t flag, + dbus_bool_t value) +{ + unsigned char *flags_p; + + flags_p = _dbus_string_get_udata_len (&header->data, FLAGS_OFFSET, 1); + + if (value) + *flags_p |= flag; + else + *flags_p &= ~flag; +} + +/** + * Gets a message flag bit, returning TRUE if the bit is set. + * + * @param header the header + * @param flag the message flag to get + * @returns #TRUE if the flag is set + */ +dbus_bool_t +_dbus_header_get_flag (DBusHeader *header, + dbus_uint32_t flag) +{ + const unsigned char *flags_p; + + flags_p = _dbus_string_get_const_udata_len (&header->data, FLAGS_OFFSET, 1); + + return (*flags_p & flag) != 0; +} + +/** + * Swaps the header into the given order if required. + * + * @param header the header + * @param new_order the new byte order + */ +void +_dbus_header_byteswap (DBusHeader *header, + int new_order) +{ + char byte_order; + + byte_order = _dbus_header_get_byte_order (header); + + if (byte_order == new_order) + return; + + _dbus_marshal_byteswap (&_dbus_header_signature_str, + 0, byte_order, + new_order, + &header->data, 0); + + _dbus_string_set_byte (&header->data, BYTE_ORDER_OFFSET, new_order); +} + +/** + * Remove every header field not known to this version of dbus. + * + * @param header the header + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_header_remove_unknown_fields (DBusHeader *header) +{ + DBusTypeReader array; + DBusTypeReader fields_reader; + + _dbus_type_reader_init (&fields_reader, + _dbus_header_get_byte_order (header), + &_dbus_header_signature_str, + FIELDS_ARRAY_SIGNATURE_OFFSET, + &header->data, + FIELDS_ARRAY_LENGTH_OFFSET); + + _dbus_type_reader_recurse (&fields_reader, &array); + + while (_dbus_type_reader_get_current_type (&array) != DBUS_TYPE_INVALID) + { + DBusTypeReader sub; + unsigned char field_code; + + _dbus_type_reader_recurse (&array, &sub); + + _dbus_assert (_dbus_type_reader_get_current_type (&sub) == DBUS_TYPE_BYTE); + _dbus_type_reader_read_basic (&sub, &field_code); + + if (field_code > DBUS_HEADER_FIELD_LAST) + { + if (!reserve_header_padding (header)) + return FALSE; + + if (!_dbus_type_reader_delete (&array, &fields_reader)) + return FALSE; + + correct_header_padding (header); + _dbus_header_cache_invalidate_all (header); + } + else + { + _dbus_type_reader_next (&array); + } + } + + return TRUE; +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-marshal-header.h b/src/3rdparty/libdbus/dbus/dbus-marshal-header.h new file mode 100644 index 00000000..c59033ad --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-marshal-header.h @@ -0,0 +1,179 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-marshal-header.h Managing marshaling/demarshaling of message headers + * + * Copyright (C) 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_MARSHAL_HEADER_H +#define DBUS_MARSHAL_HEADER_H + +#include <dbus/dbus-marshal-basic.h> +#include <dbus/dbus-marshal-validate.h> + +typedef struct DBusHeader DBusHeader; +typedef struct DBusHeaderField DBusHeaderField; + +#define _DBUS_HEADER_FIELD_VALUE_UNKNOWN -1 +#define _DBUS_HEADER_FIELD_VALUE_NONEXISTENT -2 + +/** + * Cached information about a header field in the message + */ +struct DBusHeaderField +{ + int value_pos; /**< Position of field value, or -1/-2 */ +}; + +/** + * Message header data and some cached details of it. + * + * A message looks like this: + * + * @code + * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | <- index % 8 + * |-------|-------|-------|------|-----|-----|-----|-----| + * | Order | Type | Flags | Vers | Body length | + * | Serial | Fields array length [A] + * [A] Code |Sig.len| Signature + \0 | Content...| <- first field + * | Content ... | Pad to 8-byte boundary| + * | Code |Sig.len| Signature + \0 | Content... | <- second field + * ... + * | Code |Sig.len| Signature | Content... | <- last field + * | Content ... [B] Padding to 8-byte boundary [C] + * [C] Body ... | + * ... + * | Body ... [D] <- no padding after natural length + * @endcode + * + * Each field is a struct<byte,variant>. All structs have 8-byte alignment, + * so each field is preceded by 0-7 bytes of padding to an 8-byte boundary + * (for the first field it happens to be 0 bytes). The overall header + * is followed by 0-7 bytes of padding to align the body. + * + * Key to content, with variable name references for _dbus_header_load(): + * + * Order: byte order, currently 'l' or 'B' (byte_order) + * Type: message type such as DBUS_MESSAGE_TYPE_METHOD_CALL + * Flags: message flags such as DBUS_HEADER_FLAG_NO_REPLY_EXPECTED + * Vers: D-Bus wire protocol version, currently always 1 + * Body length: Distance from [C] to [D] + * Serial: Message serial number + * Fields array length: Distance from [A] to [B] (fields_array_len) + * + * To understand _dbus_header_load(): + * + * [A] is FIRST_FIELD_OFFSET. + * header_len is from 0 to [C]. + * padding_start is [B]. + * padding_len is the padding from [B] to [C]. + */ +struct DBusHeader +{ + DBusString data; /**< Header network data, stored + * separately from body so we can + * independently realloc it. Its length includes + * up to 8 bytes of padding to align the body to + * an 8-byte boundary. + * + * In a steady state, this has length [C]. During + * editing, it is temporarily extended to have the + * maximum possible padding. + */ + + DBusHeaderField fields[DBUS_HEADER_FIELD_LAST + 1]; /**< Track the location + * of each field in header + */ + + dbus_uint32_t padding : 3; /**< 0-7 bytes of alignment in header, + the distance from [B] to [C] */ + dbus_uint32_t byte_order : 8; /**< byte order of header (must always + match the content of byte 0) */ +}; + +dbus_bool_t _dbus_header_init (DBusHeader *header); +void _dbus_header_free (DBusHeader *header); +void _dbus_header_reinit (DBusHeader *header); +dbus_bool_t _dbus_header_create (DBusHeader *header, + int byte_order, + int type, + const char *destination, + const char *path, + const char *interface, + const char *member, + const char *error_name); +dbus_bool_t _dbus_header_copy (const DBusHeader *header, + DBusHeader *dest); +int _dbus_header_get_message_type (DBusHeader *header); +void _dbus_header_set_serial (DBusHeader *header, + dbus_uint32_t serial); +dbus_uint32_t _dbus_header_get_serial (DBusHeader *header); +void _dbus_header_update_lengths (DBusHeader *header, + int body_len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_header_set_field_basic (DBusHeader *header, + int field, + int type, + const void *value); +dbus_bool_t _dbus_header_get_field_basic (DBusHeader *header, + int field, + int type, + void *value); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_header_get_field_raw (DBusHeader *header, + int field, + const DBusString **str, + int *pos); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_header_delete_field (DBusHeader *header, + int field); +void _dbus_header_toggle_flag (DBusHeader *header, + dbus_uint32_t flag, + dbus_bool_t value); +dbus_bool_t _dbus_header_get_flag (DBusHeader *header, + dbus_uint32_t flag); +dbus_bool_t _dbus_header_ensure_signature (DBusHeader *header, + DBusString **type_str, + int *type_pos); +dbus_bool_t _dbus_header_have_message_untrusted (int max_message_length, + DBusValidity *validity, + int *byte_order, + int *fields_array_len, + int *header_len, + int *body_len, + const DBusString *str, + int start, + int len); +dbus_bool_t _dbus_header_load (DBusHeader *header, + DBusValidationMode mode, + DBusValidity *validity, + int byte_order, + int fields_array_len, + int header_len, + int body_len, + const DBusString *str); +void _dbus_header_byteswap (DBusHeader *header, + int new_order); +DBUS_PRIVATE_EXPORT +char _dbus_header_get_byte_order (const DBusHeader *header); +dbus_bool_t _dbus_header_remove_unknown_fields (DBusHeader *header); + +#endif /* DBUS_MARSHAL_HEADER_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-marshal-recursive.c b/src/3rdparty/libdbus/dbus/dbus-marshal-recursive.c new file mode 100644 index 00000000..a85aabd8 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-marshal-recursive.c @@ -0,0 +1,2754 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-marshal-recursive.c Marshalling routines for recursive types + * + * Copyright (C) 2004, 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-marshal-recursive.h" +#include "dbus-marshal-basic.h" +#include "dbus-signature.h" +#include "dbus-internals.h" + +/** + * @addtogroup DBusMarshal + * @{ + */ + +static dbus_bool_t _dbus_type_reader_greater_than (const DBusTypeReader *lhs, + const DBusTypeReader *rhs); + +static void _dbus_type_writer_set_enabled (DBusTypeWriter *writer, + dbus_bool_t enabled); +static dbus_bool_t _dbus_type_writer_write_reader_partial (DBusTypeWriter *writer, + DBusTypeReader *reader, + const DBusTypeReader *start_after, + int start_after_new_pos, + int start_after_new_len, + DBusList **fixups); + +/** turn this on to get deluged in TypeReader verbose spam */ +#define RECURSIVE_MARSHAL_READ_TRACE 0 + +/** turn this on to get deluged in TypeWriter verbose spam */ +#define RECURSIVE_MARSHAL_WRITE_TRACE 0 + +static void +free_fixups (DBusList **fixups) +{ + DBusList *link; + + link = _dbus_list_get_first_link (fixups); + while (link != NULL) + { + DBusList *next; + + next = _dbus_list_get_next_link (fixups, link); + + dbus_free (link->data); + _dbus_list_free_link (link); + + link = next; + } + + *fixups = NULL; +} + +static void +apply_and_free_fixups (DBusList **fixups, + DBusTypeReader *reader) +{ + DBusList *link; + +#if RECURSIVE_MARSHAL_WRITE_TRACE + if (*fixups) + _dbus_verbose (" %d FIXUPS to apply\n", + _dbus_list_get_length (fixups)); +#endif + + link = _dbus_list_get_first_link (fixups); + while (link != NULL) + { + DBusList *next; + + next = _dbus_list_get_next_link (fixups, link); + + if (reader) + { + DBusArrayLenFixup *f; + + f = link->data; + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" applying FIXUP to reader %p at pos %d new_len = %d old len %d\n", + reader, f->len_pos_in_reader, f->new_len, + _dbus_marshal_read_uint32 (reader->value_str, + f->len_pos_in_reader, + reader->byte_order, NULL)); +#endif + + _dbus_marshal_set_uint32 ((DBusString*) reader->value_str, + f->len_pos_in_reader, + f->new_len, + reader->byte_order); + } + + dbus_free (link->data); + _dbus_list_free_link (link); + + link = next; + } + + *fixups = NULL; +} + +/** + * Virtual table for a type reader. + */ +struct DBusTypeReaderClass +{ + const char *name; /**< name for debugging */ + int id; /**< index in all_reader_classes */ + dbus_bool_t types_only; /**< only iterates over types, not values */ + void (* recurse) (DBusTypeReader *sub, + DBusTypeReader *parent); /**< recurse with this reader as sub */ + dbus_bool_t (* check_finished) (const DBusTypeReader *reader); /**< check whether reader is at the end */ + void (* next) (DBusTypeReader *reader, + int current_type); /**< go to the next value */ +}; + +static int +element_type_get_alignment (const DBusString *str, + int pos) +{ + return _dbus_type_get_alignment (_dbus_first_type_in_signature (str, pos)); +} + +static void +reader_init (DBusTypeReader *reader, + int byte_order, + const DBusString *type_str, + int type_pos, + const DBusString *value_str, + int value_pos) +{ + _DBUS_ZERO (*reader); + reader->byte_order = byte_order; + reader->finished = FALSE; + reader->type_str = type_str; + reader->type_pos = type_pos; + reader->value_str = value_str; + reader->value_pos = value_pos; +} + +static void +base_reader_recurse (DBusTypeReader *sub, + DBusTypeReader *parent) +{ + /* point subreader at the same place as parent */ + reader_init (sub, + parent->byte_order, + parent->type_str, + parent->type_pos, + parent->value_str, + parent->value_pos); +} + +static void +struct_or_dict_entry_types_only_reader_recurse (DBusTypeReader *sub, + DBusTypeReader *parent) +{ + base_reader_recurse (sub, parent); + + _dbus_assert (_dbus_string_get_byte (sub->type_str, + sub->type_pos) == DBUS_STRUCT_BEGIN_CHAR || + _dbus_string_get_byte (sub->type_str, + sub->type_pos) == DBUS_DICT_ENTRY_BEGIN_CHAR); + + sub->type_pos += 1; +} + +static void +struct_or_dict_entry_reader_recurse (DBusTypeReader *sub, + DBusTypeReader *parent) +{ + struct_or_dict_entry_types_only_reader_recurse (sub, parent); + + /* struct and dict entry have 8 byte alignment */ + sub->value_pos = _DBUS_ALIGN_VALUE (sub->value_pos, 8); +} + +static void +array_types_only_reader_recurse (DBusTypeReader *sub, + DBusTypeReader *parent) +{ + base_reader_recurse (sub, parent); + + /* point type_pos at the array element type */ + sub->type_pos += 1; + + /* Init with values likely to crash things if misused */ + sub->u.array.start_pos = _DBUS_INT_MAX; + sub->array_len_offset = 7; +} + +/** compute position of array length given array_len_offset, which is + the offset back from start_pos to end of the len */ +#define ARRAY_READER_LEN_POS(reader) \ + ((reader)->u.array.start_pos - ((int)(reader)->array_len_offset) - 4) + +static int +array_reader_get_array_len (const DBusTypeReader *reader) +{ + dbus_uint32_t array_len; + int len_pos; + + len_pos = ARRAY_READER_LEN_POS (reader); + + _dbus_assert (_DBUS_ALIGN_VALUE (len_pos, 4) == (unsigned) len_pos); + array_len = _dbus_unpack_uint32 (reader->byte_order, + _dbus_string_get_const_udata_len (reader->value_str, len_pos, 4)); + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" reader %p len_pos %d array len %u len_offset %d\n", + reader, len_pos, array_len, reader->array_len_offset); +#endif + + _dbus_assert (reader->u.array.start_pos - len_pos - 4 < 8); + + return array_len; +} + +static void +array_reader_recurse (DBusTypeReader *sub, + DBusTypeReader *parent) +{ + int alignment; + int len_pos; + + array_types_only_reader_recurse (sub, parent); + + sub->value_pos = _DBUS_ALIGN_VALUE (sub->value_pos, 4); + + len_pos = sub->value_pos; + + sub->value_pos += 4; /* for the length */ + + alignment = element_type_get_alignment (sub->type_str, + sub->type_pos); + + sub->value_pos = _DBUS_ALIGN_VALUE (sub->value_pos, alignment); + + sub->u.array.start_pos = sub->value_pos; + _dbus_assert ((sub->u.array.start_pos - (len_pos + 4)) < 8); /* only 3 bits in array_len_offset */ + sub->array_len_offset = sub->u.array.start_pos - (len_pos + 4); + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" type reader %p array start = %d len_offset = %d array len = %d array element type = %s\n", + sub, + sub->u.array.start_pos, + sub->array_len_offset, + array_reader_get_array_len (sub), + _dbus_type_to_string (_dbus_first_type_in_signature (sub->type_str, + sub->type_pos))); +#endif +} + +static void +variant_reader_recurse (DBusTypeReader *sub, + DBusTypeReader *parent) +{ + int sig_len; + int contained_alignment; + + base_reader_recurse (sub, parent); + + /* Variant is 1 byte sig length (without nul), signature with nul, + * padding to 8-boundary, then values + */ + + sig_len = _dbus_string_get_byte (sub->value_str, sub->value_pos); + + sub->type_str = sub->value_str; + sub->type_pos = sub->value_pos + 1; + + sub->value_pos = sub->type_pos + sig_len + 1; + + contained_alignment = _dbus_type_get_alignment (_dbus_first_type_in_signature (sub->type_str, + sub->type_pos)); + + sub->value_pos = _DBUS_ALIGN_VALUE (sub->value_pos, contained_alignment); + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" type reader %p variant containing '%s'\n", + sub, + _dbus_string_get_const_data_len (sub->type_str, + sub->type_pos, 0)); +#endif +} + +/* return true if no more elements remain */ +static dbus_bool_t +array_reader_check_finished (const DBusTypeReader *reader) +{ + int end_pos; + + end_pos = reader->u.array.start_pos + array_reader_get_array_len (reader); + + _dbus_assert (reader->value_pos <= end_pos); + _dbus_assert (reader->value_pos >= reader->u.array.start_pos); + + return reader->value_pos == end_pos; +} + +static void +skip_one_complete_type (const DBusString *type_str, + int *type_pos) +{ + _dbus_type_signature_next (_dbus_string_get_const_data (type_str), + type_pos); +} + +/** + * Skips to the next "complete" type inside a type signature. + * The signature is read starting at type_pos, and the next + * type position is stored in the same variable. + * + * @param type_str a type signature (must be valid) + * @param type_pos an integer position in the type signature (in and out) + */ +void +_dbus_type_signature_next (const char *type_str, + int *type_pos) +{ + const unsigned char *p; + const unsigned char *start; + + _dbus_assert (type_str != NULL); + _dbus_assert (type_pos != NULL); + + start = (const unsigned char *)type_str; + p = start + *type_pos; + + _dbus_assert (*p != DBUS_STRUCT_END_CHAR); + _dbus_assert (*p != DBUS_DICT_ENTRY_END_CHAR); + + while (*p == DBUS_TYPE_ARRAY) + ++p; + + _dbus_assert (*p != DBUS_STRUCT_END_CHAR); + _dbus_assert (*p != DBUS_DICT_ENTRY_END_CHAR); + + if (*p == DBUS_STRUCT_BEGIN_CHAR) + { + int depth; + + depth = 1; + + while (TRUE) + { + _dbus_assert (*p != DBUS_TYPE_INVALID); + + ++p; + + _dbus_assert (*p != DBUS_TYPE_INVALID); + + if (*p == DBUS_STRUCT_BEGIN_CHAR) + depth += 1; + else if (*p == DBUS_STRUCT_END_CHAR) + { + depth -= 1; + if (depth == 0) + { + ++p; + break; + } + } + } + } + else if (*p == DBUS_DICT_ENTRY_BEGIN_CHAR) + { + int depth; + + depth = 1; + + while (TRUE) + { + _dbus_assert (*p != DBUS_TYPE_INVALID); + + ++p; + + _dbus_assert (*p != DBUS_TYPE_INVALID); + + if (*p == DBUS_DICT_ENTRY_BEGIN_CHAR) + depth += 1; + else if (*p == DBUS_DICT_ENTRY_END_CHAR) + { + depth -= 1; + if (depth == 0) + { + ++p; + break; + } + } + } + } + else + { + ++p; + } + + *type_pos = (int) (p - start); +} + +static int +find_len_of_complete_type (const DBusString *type_str, + int type_pos) +{ + int end; + + end = type_pos; + + skip_one_complete_type (type_str, &end); + + return end - type_pos; +} + +static void +base_reader_next (DBusTypeReader *reader, + int current_type) +{ + switch (current_type) + { + case DBUS_TYPE_DICT_ENTRY: + case DBUS_TYPE_STRUCT: + case DBUS_TYPE_VARIANT: + /* Scan forward over the entire container contents */ + { + DBusTypeReader sub; + + if (reader->klass->types_only && current_type == DBUS_TYPE_VARIANT) + ; + else + { + /* Recurse into the struct or variant */ + _dbus_type_reader_recurse (reader, &sub); + + /* Skip everything in this subreader */ + while (_dbus_type_reader_next (&sub)) + { + /* nothing */; + } + } + if (!reader->klass->types_only) + reader->value_pos = sub.value_pos; + + /* Now we are at the end of this container; for variants, the + * subreader's type_pos is totally inapplicable (it's in the + * value string) but we know that we increment by one past the + * DBUS_TYPE_VARIANT + */ + if (current_type == DBUS_TYPE_VARIANT) + reader->type_pos += 1; + else + reader->type_pos = sub.type_pos; + } + break; + + case DBUS_TYPE_ARRAY: + { + if (!reader->klass->types_only) + _dbus_marshal_skip_array (reader->value_str, + _dbus_first_type_in_signature (reader->type_str, + reader->type_pos + 1), + reader->byte_order, + &reader->value_pos); + + skip_one_complete_type (reader->type_str, &reader->type_pos); + } + break; + + default: + if (!reader->klass->types_only) + _dbus_marshal_skip_basic (reader->value_str, + current_type, reader->byte_order, + &reader->value_pos); + + reader->type_pos += 1; + break; + } +} + +static void +struct_reader_next (DBusTypeReader *reader, + int current_type) +{ + int t; + + base_reader_next (reader, current_type); + + /* for STRUCT containers we return FALSE at the end of the struct, + * for INVALID we return FALSE at the end of the signature. + * In both cases we arrange for get_current_type() to return INVALID + * which is defined to happen iff we're at the end (no more next()) + */ + t = _dbus_string_get_byte (reader->type_str, reader->type_pos); + if (t == DBUS_STRUCT_END_CHAR) + { + reader->type_pos += 1; + reader->finished = TRUE; + } +} + +static void +dict_entry_reader_next (DBusTypeReader *reader, + int current_type) +{ + int t; + + base_reader_next (reader, current_type); + + /* for STRUCT containers we return FALSE at the end of the struct, + * for INVALID we return FALSE at the end of the signature. + * In both cases we arrange for get_current_type() to return INVALID + * which is defined to happen iff we're at the end (no more next()) + */ + t = _dbus_string_get_byte (reader->type_str, reader->type_pos); + if (t == DBUS_DICT_ENTRY_END_CHAR) + { + reader->type_pos += 1; + reader->finished = TRUE; + } +} + +static void +array_types_only_reader_next (DBusTypeReader *reader, + int current_type) +{ + /* We have one "element" to be iterated over + * in each array, which is its element type. + * So the finished flag indicates whether we've + * iterated over it yet or not. + */ + reader->finished = TRUE; +} + +static void +array_reader_next (DBusTypeReader *reader, + int current_type) +{ + /* Skip one array element */ + int end_pos; + + end_pos = reader->u.array.start_pos + array_reader_get_array_len (reader); + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" reader %p array next START start_pos = %d end_pos = %d value_pos = %d current_type = %s\n", + reader, + reader->u.array.start_pos, + end_pos, reader->value_pos, + _dbus_type_to_string (current_type)); +#endif + + _dbus_assert (reader->value_pos < end_pos); + _dbus_assert (reader->value_pos >= reader->u.array.start_pos); + + switch (_dbus_first_type_in_signature (reader->type_str, + reader->type_pos)) + { + case DBUS_TYPE_DICT_ENTRY: + case DBUS_TYPE_STRUCT: + case DBUS_TYPE_VARIANT: + { + DBusTypeReader sub; + + /* Recurse into the struct or variant */ + _dbus_type_reader_recurse (reader, &sub); + + /* Skip everything in this element */ + while (_dbus_type_reader_next (&sub)) + { + /* nothing */; + } + + /* Now we are at the end of this element */ + reader->value_pos = sub.value_pos; + } + break; + + case DBUS_TYPE_ARRAY: + { + _dbus_marshal_skip_array (reader->value_str, + _dbus_first_type_in_signature (reader->type_str, + reader->type_pos + 1), + reader->byte_order, + &reader->value_pos); + } + break; + + default: + { + _dbus_marshal_skip_basic (reader->value_str, + current_type, reader->byte_order, + &reader->value_pos); + } + break; + } + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" reader %p array next END start_pos = %d end_pos = %d value_pos = %d current_type = %s\n", + reader, + reader->u.array.start_pos, + end_pos, reader->value_pos, + _dbus_type_to_string (current_type)); +#endif + + _dbus_assert (reader->value_pos <= end_pos); + + if (reader->value_pos == end_pos) + { + skip_one_complete_type (reader->type_str, + &reader->type_pos); + } +} + +static const DBusTypeReaderClass body_reader_class = { + "body", 0, + FALSE, + NULL, /* body is always toplevel, so doesn't get recursed into */ + NULL, + base_reader_next +}; + +static const DBusTypeReaderClass body_types_only_reader_class = { + "body types", 1, + TRUE, + NULL, /* body is always toplevel, so doesn't get recursed into */ + NULL, + base_reader_next +}; + +static const DBusTypeReaderClass struct_reader_class = { + "struct", 2, + FALSE, + struct_or_dict_entry_reader_recurse, + NULL, + struct_reader_next +}; + +static const DBusTypeReaderClass struct_types_only_reader_class = { + "struct types", 3, + TRUE, + struct_or_dict_entry_types_only_reader_recurse, + NULL, + struct_reader_next +}; + +static const DBusTypeReaderClass dict_entry_reader_class = { + "dict_entry", 4, + FALSE, + struct_or_dict_entry_reader_recurse, + NULL, + dict_entry_reader_next +}; + +static const DBusTypeReaderClass dict_entry_types_only_reader_class = { + "dict_entry types", 5, + TRUE, + struct_or_dict_entry_types_only_reader_recurse, + NULL, + dict_entry_reader_next +}; + +static const DBusTypeReaderClass array_reader_class = { + "array", 6, + FALSE, + array_reader_recurse, + array_reader_check_finished, + array_reader_next +}; + +static const DBusTypeReaderClass array_types_only_reader_class = { + "array types", 7, + TRUE, + array_types_only_reader_recurse, + NULL, + array_types_only_reader_next +}; + +static const DBusTypeReaderClass variant_reader_class = { + "variant", 8, + FALSE, + variant_reader_recurse, + NULL, + base_reader_next +}; + +#ifndef DBUS_DISABLE_ASSERT +static const DBusTypeReaderClass * const +all_reader_classes[] = { + &body_reader_class, + &body_types_only_reader_class, + &struct_reader_class, + &struct_types_only_reader_class, + &dict_entry_reader_class, + &dict_entry_types_only_reader_class, + &array_reader_class, + &array_types_only_reader_class, + &variant_reader_class +}; +#endif + +/** + * Initializes a type reader. + * + * @param reader the reader + * @param byte_order the byte order of the block to read + * @param type_str the signature of the block to read + * @param type_pos location of signature + * @param value_str the string containing values block + * @param value_pos start of values block + */ +void +_dbus_type_reader_init (DBusTypeReader *reader, + int byte_order, + const DBusString *type_str, + int type_pos, + const DBusString *value_str, + int value_pos) +{ + reader_init (reader, byte_order, type_str, type_pos, + value_str, value_pos); + + reader->klass = &body_reader_class; + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" type reader %p init type_pos = %d value_pos = %d remaining sig '%s'\n", + reader, reader->type_pos, reader->value_pos, + _dbus_string_get_const_data_len (reader->type_str, reader->type_pos, 0)); +#endif +} + +/** + * Like _dbus_type_reader_init() but the iteration is over the + * signature, not over values. + * + * @param reader the reader + * @param type_str the signature string + * @param type_pos location in the signature string + */ +void +_dbus_type_reader_init_types_only (DBusTypeReader *reader, + const DBusString *type_str, + int type_pos) +{ + reader_init (reader, DBUS_COMPILER_BYTE_ORDER /* irrelevant */, + type_str, type_pos, NULL, _DBUS_INT_MAX /* crashes if we screw up */); + + reader->klass = &body_types_only_reader_class; + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" type reader %p init types only type_pos = %d remaining sig '%s'\n", + reader, reader->type_pos, + _dbus_string_get_const_data_len (reader->type_str, reader->type_pos, 0)); +#endif +} + +/** + * Gets the type of the value the reader is currently pointing to; + * or for a types-only reader gets the type it's currently pointing to. + * If the reader is at the end of a block or end of a container such + * as an array, returns #DBUS_TYPE_INVALID. + * + * @param reader the reader + */ +int +_dbus_type_reader_get_current_type (const DBusTypeReader *reader) +{ + int t; + + if (reader->finished || + (reader->klass->check_finished && + (* reader->klass->check_finished) (reader))) + t = DBUS_TYPE_INVALID; + else + t = _dbus_first_type_in_signature (reader->type_str, + reader->type_pos); + + _dbus_assert (t != DBUS_STRUCT_END_CHAR); + _dbus_assert (t != DBUS_STRUCT_BEGIN_CHAR); + _dbus_assert (t != DBUS_DICT_ENTRY_END_CHAR); + _dbus_assert (t != DBUS_DICT_ENTRY_BEGIN_CHAR); + +#if 0 + _dbus_verbose (" type reader %p current type_pos = %d type = %s\n", + reader, reader->type_pos, + _dbus_type_to_string (t)); +#endif + + return t; +} + +/** + * Gets the type of an element of the array the reader is currently + * pointing to. It's an error to call this if + * _dbus_type_reader_get_current_type() doesn't return #DBUS_TYPE_ARRAY + * for this reader. + * + * @param reader the reader + */ +int +_dbus_type_reader_get_element_type (const DBusTypeReader *reader) +{ + int element_type; + + _dbus_assert (_dbus_type_reader_get_current_type (reader) == DBUS_TYPE_ARRAY); + + element_type = _dbus_first_type_in_signature (reader->type_str, + reader->type_pos + 1); + + return element_type; +} + +/** + * Gets the current position in the value block + * @param reader the reader + */ +int +_dbus_type_reader_get_value_pos (const DBusTypeReader *reader) +{ + return reader->value_pos; +} + +/** + * Get the address of the marshaled value in the data being read. The + * address may not be aligned; you have to align it to the type of the + * value you want to read. Most of the demarshal routines do this for + * you. + * + * @param reader the reader + * @param value_location the address of the marshaled value + */ +void +_dbus_type_reader_read_raw (const DBusTypeReader *reader, + const unsigned char **value_location) +{ + _dbus_assert (!reader->klass->types_only); + + *value_location = _dbus_string_get_const_udata_len (reader->value_str, + reader->value_pos, + 0); +} + +/** + * Reads a basic-typed value, as with _dbus_marshal_read_basic(). + * + * @param reader the reader + * @param value the address of the value + */ +void +_dbus_type_reader_read_basic (const DBusTypeReader *reader, + void *value) +{ + int t; + + _dbus_assert (!reader->klass->types_only); + + t = _dbus_type_reader_get_current_type (reader); + + _dbus_marshal_read_basic (reader->value_str, + reader->value_pos, + t, value, + reader->byte_order, + NULL); + + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" type reader %p read basic type_pos = %d value_pos = %d remaining sig '%s'\n", + reader, reader->type_pos, reader->value_pos, + _dbus_string_get_const_data_len (reader->type_str, reader->type_pos, 0)); +#endif +} + +/** + * Returns the number of bytes in the array. + * + * @param reader the reader to read from + * @returns the number of bytes in the array + */ +int +_dbus_type_reader_get_array_length (const DBusTypeReader *reader) +{ + _dbus_assert (!reader->klass->types_only); + _dbus_assert (reader->klass == &array_reader_class); + + return array_reader_get_array_len (reader); +} + +/** + * Reads a block of fixed-length basic values, from the current point + * in an array to the end of the array. Does not work for arrays of + * string or container types. + * + * This function returns the array in-place; it does not make a copy, + * and it does not swap the bytes. + * + * If you ask for #DBUS_TYPE_DOUBLE you will get a "const double*" back + * and the "value" argument should be a "const double**" and so on. + * + * @param reader the reader to read from + * @param value place to return the array values + * @param n_elements place to return number of array elements + */ +void +_dbus_type_reader_read_fixed_multi (const DBusTypeReader *reader, + const void **value, + int *n_elements) +{ + int element_type; + int end_pos; + int remaining_len; + int alignment; + int total_len; + + _dbus_assert (!reader->klass->types_only); + _dbus_assert (reader->klass == &array_reader_class); + + element_type = _dbus_first_type_in_signature (reader->type_str, + reader->type_pos); + + _dbus_assert (element_type != DBUS_TYPE_INVALID); /* why we don't use get_current_type() */ + _dbus_assert (dbus_type_is_fixed (element_type)); + + alignment = _dbus_type_get_alignment (element_type); + + _dbus_assert (reader->value_pos >= reader->u.array.start_pos); + + total_len = array_reader_get_array_len (reader); + end_pos = reader->u.array.start_pos + total_len; + remaining_len = end_pos - reader->value_pos; + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose ("end_pos %d total_len %d remaining_len %d value_pos %d\n", + end_pos, total_len, remaining_len, reader->value_pos); +#endif + + _dbus_assert (remaining_len <= total_len); + + if (remaining_len == 0) + *value = NULL; + else + *value = _dbus_string_get_const_data_len (reader->value_str, + reader->value_pos, + remaining_len); + + *n_elements = remaining_len / alignment; + _dbus_assert ((remaining_len % alignment) == 0); + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" type reader %p read fixed array type_pos = %d value_pos = %d remaining sig '%s'\n", + reader, reader->type_pos, reader->value_pos, + _dbus_string_get_const_data_len (reader->type_str, reader->type_pos, 0)); +#endif +} + +/** + * Initialize a new reader pointing to the first type and + * corresponding value that's a child of the current container. It's + * an error to call this if the current type is a non-container. + * + * Note that DBusTypeReader traverses values, not types. So if you + * have an empty array of array of int, you can't recurse into it. You + * can only recurse into each element. + * + * @param reader the reader + * @param sub a reader to init pointing to the first child + */ +void +_dbus_type_reader_recurse (DBusTypeReader *reader, + DBusTypeReader *sub) +{ + int t; + const DBusTypeReaderClass *klass = NULL; + + t = _dbus_first_type_in_signature (reader->type_str, reader->type_pos); + + switch (t) + { + case DBUS_TYPE_STRUCT: + if (reader->klass->types_only) + klass = &struct_types_only_reader_class; + else + klass = &struct_reader_class; + break; + case DBUS_TYPE_DICT_ENTRY: + if (reader->klass->types_only) + klass = &dict_entry_types_only_reader_class; + else + klass = &dict_entry_reader_class; + break; + case DBUS_TYPE_ARRAY: + if (reader->klass->types_only) + klass = &array_types_only_reader_class; + else + klass = &array_reader_class; + break; + case DBUS_TYPE_VARIANT: + if (reader->klass->types_only) + _dbus_assert_not_reached ("can't recurse into variant typecode"); + else + klass = &variant_reader_class; + break; + default: + _dbus_verbose ("recursing into type %s\n", _dbus_type_to_string (t)); +#ifndef DBUS_DISABLE_CHECKS + if (t == DBUS_TYPE_INVALID) + _dbus_warn_check_failed ("You can't recurse into an empty array or off the end of a message body"); +#endif /* DBUS_DISABLE_CHECKS */ + + _dbus_assert_not_reached ("don't yet handle recursing into this type"); + } + + _dbus_assert (klass != NULL); + _dbus_assert (klass == all_reader_classes[klass->id]); + + (* klass->recurse) (sub, reader); + sub->klass = klass; + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" type reader %p RECURSED type_pos = %d value_pos = %d remaining sig '%s'\n", + sub, sub->type_pos, sub->value_pos, + _dbus_string_get_const_data_len (sub->type_str, sub->type_pos, 0)); +#endif +} + +/** + * Skip to the next value on this "level". e.g. the next field in a + * struct, the next value in an array. Returns FALSE at the end of the + * current container. + * + * @param reader the reader + * @returns FALSE if nothing more to read at or below this level + */ +dbus_bool_t +_dbus_type_reader_next (DBusTypeReader *reader) +{ + int t; + + t = _dbus_type_reader_get_current_type (reader); + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" type reader %p START next() { type_pos = %d value_pos = %d remaining sig '%s' current_type = %s\n", + reader, reader->type_pos, reader->value_pos, + _dbus_string_get_const_data_len (reader->type_str, reader->type_pos, 0), + _dbus_type_to_string (t)); +#endif + + if (t == DBUS_TYPE_INVALID) + return FALSE; + + (* reader->klass->next) (reader, t); + +#if RECURSIVE_MARSHAL_READ_TRACE + _dbus_verbose (" type reader %p END next() type_pos = %d value_pos = %d remaining sig '%s' current_type = %s\n", + reader, reader->type_pos, reader->value_pos, + _dbus_string_get_const_data_len (reader->type_str, reader->type_pos, 0), + _dbus_type_to_string (_dbus_type_reader_get_current_type (reader))); +#endif + + return _dbus_type_reader_get_current_type (reader) != DBUS_TYPE_INVALID; +} + +/** + * Check whether there's another value on this "level". e.g. the next + * field in a struct, the next value in an array. Returns FALSE at the + * end of the current container. + * + * You probably don't want to use this; it makes for an awkward for/while + * loop. A nicer one is "while ((current_type = get_current_type()) != INVALID)" + * + * @param reader the reader + * @returns FALSE if nothing more to read at or below this level + */ +dbus_bool_t +_dbus_type_reader_has_next (const DBusTypeReader *reader) +{ + /* Not efficient but works for now. */ + DBusTypeReader copy; + + copy = *reader; + return _dbus_type_reader_next (©); +} + +/** + * Gets the string and range of said string containing the signature + * of the current value. Essentially a more complete version of + * _dbus_type_reader_get_current_type() (returns the full type + * rather than only the outside of the onion). + * + * Note though that the first byte in a struct signature is + * #DBUS_STRUCT_BEGIN_CHAR while the current type will be + * #DBUS_TYPE_STRUCT so it isn't true that the first byte of the + * signature is always the same as the current type. Another + * difference is that this function will still return a signature when + * inside an empty array; say you recurse into empty array of int32, + * the signature is "i" but the current type will always be + * #DBUS_TYPE_INVALID since there are no elements to be currently + * pointing to. + * + * @param reader the reader + * @param str_p place to return the string with the type in it + * @param start_p place to return start of the type + * @param len_p place to return the length of the type + */ +void +_dbus_type_reader_get_signature (const DBusTypeReader *reader, + const DBusString **str_p, + int *start_p, + int *len_p) +{ + *str_p = reader->type_str; + *start_p = reader->type_pos; + *len_p = find_len_of_complete_type (reader->type_str, reader->type_pos); +} + +typedef struct +{ + DBusString replacement; /**< Marshaled value including alignment padding */ + int padding; /**< How much of the replacement block is padding */ +} ReplacementBlock; + +static dbus_bool_t +replacement_block_init (ReplacementBlock *block, + DBusTypeReader *reader) +{ + if (!_dbus_string_init (&block->replacement)) + return FALSE; + + /* % 8 is the padding to have the same align properties in + * our replacement string as we do at the position being replaced + */ + block->padding = reader->value_pos % 8; + + if (!_dbus_string_lengthen (&block->replacement, block->padding)) + goto oom; + + return TRUE; + + oom: + _dbus_string_free (&block->replacement); + return FALSE; +} + +static dbus_bool_t +replacement_block_replace (ReplacementBlock *block, + DBusTypeReader *reader, + const DBusTypeReader *realign_root) +{ + DBusTypeWriter writer; + DBusTypeReader realign_reader; + DBusList *fixups; + int orig_len; + + _dbus_assert (realign_root != NULL); + + orig_len = _dbus_string_get_length (&block->replacement); + + realign_reader = *realign_root; + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("INITIALIZING replacement block writer %p at value_pos %d\n", + &writer, _dbus_string_get_length (&block->replacement)); +#endif + _dbus_type_writer_init_values_only (&writer, + realign_reader.byte_order, + realign_reader.type_str, + realign_reader.type_pos, + &block->replacement, + _dbus_string_get_length (&block->replacement)); + + _dbus_assert (realign_reader.value_pos <= reader->value_pos); + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("COPYING from reader at value_pos %d to writer %p starting after value_pos %d\n", + realign_reader.value_pos, &writer, reader->value_pos); +#endif + fixups = NULL; + if (!_dbus_type_writer_write_reader_partial (&writer, + &realign_reader, + reader, + block->padding, + _dbus_string_get_length (&block->replacement) - block->padding, + &fixups)) + goto oom; + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("REPLACEMENT at padding %d len %d\n", block->padding, + _dbus_string_get_length (&block->replacement) - block->padding); + _dbus_verbose_bytes_of_string (&block->replacement, block->padding, + _dbus_string_get_length (&block->replacement) - block->padding); + _dbus_verbose ("TO BE REPLACED at value_pos = %d (align pad %d) len %d realign_reader.value_pos %d\n", + reader->value_pos, reader->value_pos % 8, + realign_reader.value_pos - reader->value_pos, + realign_reader.value_pos); + _dbus_verbose_bytes_of_string (reader->value_str, + reader->value_pos, + realign_reader.value_pos - reader->value_pos); +#endif + + /* Move the replacement into position + * (realign_reader should now be at the end of the block to be replaced) + */ + if (!_dbus_string_replace_len (&block->replacement, block->padding, + _dbus_string_get_length (&block->replacement) - block->padding, + (DBusString*) reader->value_str, + reader->value_pos, + realign_reader.value_pos - reader->value_pos)) + goto oom; + + /* Process our fixups now that we can't have an OOM error */ + apply_and_free_fixups (&fixups, reader); + + return TRUE; + + oom: + _dbus_string_set_length (&block->replacement, orig_len); + free_fixups (&fixups); + return FALSE; +} + +static void +replacement_block_free (ReplacementBlock *block) +{ + _dbus_string_free (&block->replacement); +} + +/* In the variable-length case, we have to fix alignment after we insert. + * The strategy is as follows: + * + * - pad a new string to have the same alignment as the + * start of the current basic value + * - write the new basic value + * - copy from the original reader to the new string, + * which will fix the alignment of types following + * the new value + * - this copy has to start at realign_root, + * but not really write anything until it + * passes the value being set + * - as an optimization, we can stop copying + * when the source and dest values are both + * on an 8-boundary, since we know all following + * padding and alignment will be identical + * - copy the new string back to the original + * string, replacing the relevant part of the + * original string + * - now any arrays in the original string that + * contained the replaced string may have the + * wrong length; so we have to fix that + */ +static dbus_bool_t +reader_set_basic_variable_length (DBusTypeReader *reader, + int current_type, + const void *value, + const DBusTypeReader *realign_root) +{ + dbus_bool_t retval; + ReplacementBlock block; + DBusTypeWriter writer; + + _dbus_assert (realign_root != NULL); + + retval = FALSE; + + if (!replacement_block_init (&block, reader)) + return FALSE; + + /* Write the new basic value */ +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("INITIALIZING writer %p to write basic value at value_pos %d of replacement string\n", + &writer, _dbus_string_get_length (&block.replacement)); +#endif + _dbus_type_writer_init_values_only (&writer, + reader->byte_order, + reader->type_str, + reader->type_pos, + &block.replacement, + _dbus_string_get_length (&block.replacement)); +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("WRITING basic value to writer %p (replacement string)\n", &writer); +#endif + if (!_dbus_type_writer_write_basic (&writer, current_type, value)) + goto out; + + if (!replacement_block_replace (&block, + reader, + realign_root)) + goto out; + + retval = TRUE; + + out: + replacement_block_free (&block); + return retval; +} + +static void +reader_set_basic_fixed_length (DBusTypeReader *reader, + int current_type, + const void *value) +{ + _dbus_marshal_set_basic ((DBusString*) reader->value_str, + reader->value_pos, + current_type, + value, + reader->byte_order, + NULL, NULL); +} + +/** + * Sets a new value for the basic type value pointed to by the reader, + * leaving the reader valid to continue reading. Any other readers + * will be invalidated if you set a variable-length type such as a + * string. + * + * The provided realign_root is the reader to start from when + * realigning the data that follows the newly-set value. The reader + * parameter must point to a value below the realign_root parameter. + * If the type being set is fixed-length, then realign_root may be + * #NULL. Only values reachable from realign_root will be realigned, + * so if your string contains other values you will need to deal with + * those somehow yourself. It is OK if realign_root is the same + * reader as the reader parameter, though if you aren't setting the + * root it may not be such a good idea. + * + * @todo DBusTypeReader currently takes "const" versions of the type + * and value strings, and this function modifies those strings by + * casting away the const, which is of course bad if we want to get + * picky. (To be truly clean you'd have an object which contained the + * type and value strings and set_basic would be a method on that + * object... this would also make DBusTypeReader the same thing as + * DBusTypeMark. But since DBusMessage is effectively that object for + * D-Bus it doesn't seem worth creating some random object.) + * + * @todo optimize this by only rewriting until the old and new values + * are at the same alignment. Frequently this should result in only + * replacing the value that's immediately at hand. + * + * @param reader reader indicating where to set a new value + * @param value address of the value to set + * @param realign_root realign from here + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_type_reader_set_basic (DBusTypeReader *reader, + const void *value, + const DBusTypeReader *realign_root) +{ + int current_type; + + _dbus_assert (!reader->klass->types_only); + _dbus_assert (reader->value_str == realign_root->value_str); + _dbus_assert (reader->value_pos >= realign_root->value_pos); + + current_type = _dbus_type_reader_get_current_type (reader); + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" SET BASIC type reader %p type_pos = %d value_pos = %d remaining sig '%s' realign_root = %p with value_pos %d current_type = %s\n", + reader, reader->type_pos, reader->value_pos, + _dbus_string_get_const_data_len (reader->type_str, reader->type_pos, 0), + realign_root, + realign_root ? realign_root->value_pos : -1, + _dbus_type_to_string (current_type)); + _dbus_verbose_bytes_of_string (realign_root->value_str, realign_root->value_pos, + _dbus_string_get_length (realign_root->value_str) - + realign_root->value_pos); +#endif + + _dbus_assert (dbus_type_is_basic (current_type)); + + if (dbus_type_is_fixed (current_type)) + { + reader_set_basic_fixed_length (reader, current_type, value); + return TRUE; + } + else + { + _dbus_assert (realign_root != NULL); + return reader_set_basic_variable_length (reader, current_type, + value, realign_root); + } +} + +/** + * Recursively deletes any value pointed to by the reader, leaving the + * reader valid to continue reading. Any other readers will be + * invalidated. + * + * The provided realign_root is the reader to start from when + * realigning the data that follows the newly-set value. + * See _dbus_type_reader_set_basic() for more details on the + * realign_root paramter. + * + * @todo for now this does not delete the typecodes associated with + * the value, so this function should only be used for array elements. + * + * @param reader reader indicating where to delete a value + * @param realign_root realign from here + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_type_reader_delete (DBusTypeReader *reader, + const DBusTypeReader *realign_root) +{ + dbus_bool_t retval; + ReplacementBlock block; + + _dbus_assert (realign_root != NULL); + _dbus_assert (reader->klass == &array_reader_class); + + retval = FALSE; + + if (!replacement_block_init (&block, reader)) + return FALSE; + + if (!replacement_block_replace (&block, + reader, + realign_root)) + goto out; + + retval = TRUE; + + out: + replacement_block_free (&block); + return retval; +} + +/* + * Compares two readers, which must be iterating over the same value data. + * Returns #TRUE if the first parameter is further along than the second parameter. + * + * @param lhs left-hand-side (first) parameter + * @param rhs left-hand-side (first) parameter + * @returns whether lhs is greater than rhs + */ +static dbus_bool_t +_dbus_type_reader_greater_than (const DBusTypeReader *lhs, + const DBusTypeReader *rhs) +{ + _dbus_assert (lhs->value_str == rhs->value_str); + + return lhs->value_pos > rhs->value_pos; +} + +/* + * + * + * DBusTypeWriter + * + * + * + */ + +/** + * Initialize a write iterator, which is used to write out values in + * serialized D-Bus format. + * + * The type_pos passed in is expected to be inside an already-valid, + * though potentially empty, type signature. This means that the byte + * after type_pos must be either #DBUS_TYPE_INVALID (aka nul) or some + * other valid type. #DBusTypeWriter won't enforce that the signature + * is already valid (you can append the nul byte at the end if you + * like), but just be aware that you need the nul byte eventually and + * #DBusTypeWriter isn't going to write it for you. + * + * @param writer the writer to init + * @param byte_order the byte order to marshal into + * @param type_str the string to write typecodes into + * @param type_pos where to insert typecodes + * @param value_str the string to write values into + * @param value_pos where to insert values + * + */ +void +_dbus_type_writer_init (DBusTypeWriter *writer, + int byte_order, + DBusString *type_str, + int type_pos, + DBusString *value_str, + int value_pos) +{ + writer->byte_order = byte_order; + writer->type_str = type_str; + writer->type_pos = type_pos; + writer->value_str = value_str; + writer->value_pos = value_pos; + writer->container_type = DBUS_TYPE_INVALID; + writer->type_pos_is_expectation = FALSE; + writer->enabled = TRUE; + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("writer %p init remaining sig '%s'\n", writer, + writer->type_str ? + _dbus_string_get_const_data_len (writer->type_str, writer->type_pos, 0) : + "unknown"); +#endif +} + +/** + * Initialize a write iterator, with the signature to be provided + * later. + * + * @param writer the writer to init + * @param byte_order the byte order to marshal into + * @param value_str the string to write values into + * @param value_pos where to insert values + * + */ +void +_dbus_type_writer_init_types_delayed (DBusTypeWriter *writer, + int byte_order, + DBusString *value_str, + int value_pos) +{ + _dbus_type_writer_init (writer, byte_order, + NULL, 0, value_str, value_pos); +} + +/** + * Adds type string to the writer, if it had none. + * + * @param writer the writer to init + * @param type_str type string to add + * @param type_pos type position + * + */ +void +_dbus_type_writer_add_types (DBusTypeWriter *writer, + DBusString *type_str, + int type_pos) +{ + if (writer->type_str == NULL) /* keeps us from using this as setter */ + { + writer->type_str = type_str; + writer->type_pos = type_pos; + } +} + +/** + * Removes type string from the writer. + * + * @param writer the writer to remove from + */ +void +_dbus_type_writer_remove_types (DBusTypeWriter *writer) +{ + writer->type_str = NULL; + writer->type_pos = -1; +} + +/** + * Like _dbus_type_writer_init(), except the type string + * passed in should correspond to an existing signature that + * matches what you're going to write out. The writer will + * check what you write vs. this existing signature. + * + * @param writer the writer to init + * @param byte_order the byte order to marshal into + * @param type_str the string with signature + * @param type_pos start of signature + * @param value_str the string to write values into + * @param value_pos where to insert values + * + */ +void +_dbus_type_writer_init_values_only (DBusTypeWriter *writer, + int byte_order, + const DBusString *type_str, + int type_pos, + DBusString *value_str, + int value_pos) +{ + _dbus_type_writer_init (writer, byte_order, + (DBusString*)type_str, type_pos, + value_str, value_pos); + + writer->type_pos_is_expectation = TRUE; +} + +static dbus_bool_t +_dbus_type_writer_write_basic_no_typecode (DBusTypeWriter *writer, + int type, + const void *value) +{ + if (writer->enabled) + return _dbus_marshal_write_basic (writer->value_str, + writer->value_pos, + type, + value, + writer->byte_order, + &writer->value_pos); + else + return TRUE; +} + +/* If our parent is an array, things are a little bit complicated. + * + * The parent must have a complete element type, such as + * "i" or "aai" or "(ii)" or "a(ii)". There can't be + * unclosed parens, or an "a" with no following type. + * + * To recurse, the only allowed operation is to recurse into the + * first type in the element type. So for "i" you can't recurse, for + * "ai" you can recurse into the array, for "(ii)" you can recurse + * into the struct. + * + * If you recurse into the array for "ai", then you must specify + * "i" for the element type of the array you recurse into. + * + * While inside an array at any level, we need to avoid writing to + * type_str, since the type only appears once for the whole array, + * it does not appear for each array element. + * + * While inside an array type_pos points to the expected next + * typecode, rather than the next place we could write a typecode. + */ +static void +writer_recurse_init_and_check (DBusTypeWriter *writer, + int container_type, + DBusTypeWriter *sub) +{ + _dbus_type_writer_init (sub, + writer->byte_order, + writer->type_str, + writer->type_pos, + writer->value_str, + writer->value_pos); + + sub->container_type = container_type; + + if (writer->type_pos_is_expectation || + (sub->container_type == DBUS_TYPE_ARRAY || sub->container_type == DBUS_TYPE_VARIANT)) + sub->type_pos_is_expectation = TRUE; + else + sub->type_pos_is_expectation = FALSE; + + sub->enabled = writer->enabled; + +#ifndef DBUS_DISABLE_CHECKS + if (writer->type_pos_is_expectation && writer->type_str) + { + int expected; + + expected = _dbus_first_type_in_signature (writer->type_str, writer->type_pos); + + if (expected != sub->container_type) + { + if (expected != DBUS_TYPE_INVALID) + _dbus_warn_check_failed ("Writing an element of type %s, but the expected type here is %s\n" + "The overall signature expected here was '%s' and we are on byte %d of that signature.", + _dbus_type_to_string (sub->container_type), + _dbus_type_to_string (expected), + _dbus_string_get_const_data (writer->type_str), writer->type_pos); + else + _dbus_warn_check_failed ("Writing an element of type %s, but no value is expected here\n" + "The overall signature expected here was '%s' and we are on byte %d of that signature.", + _dbus_type_to_string (sub->container_type), + _dbus_string_get_const_data (writer->type_str), writer->type_pos); + + _dbus_assert_not_reached ("bad array element or variant content written"); + } + } +#endif /* DBUS_DISABLE_CHECKS */ + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" type writer %p recurse parent %s type_pos = %d value_pos = %d is_expectation = %d remaining sig '%s' enabled = %d\n", + writer, + _dbus_type_to_string (writer->container_type), + writer->type_pos, writer->value_pos, writer->type_pos_is_expectation, + writer->type_str ? + _dbus_string_get_const_data_len (writer->type_str, writer->type_pos, 0) : + "unknown", + writer->enabled); + _dbus_verbose (" type writer %p recurse sub %s type_pos = %d value_pos = %d is_expectation = %d enabled = %d\n", + sub, + _dbus_type_to_string (sub->container_type), + sub->type_pos, sub->value_pos, + sub->type_pos_is_expectation, + sub->enabled); +#endif +} + +static dbus_bool_t +write_or_verify_typecode (DBusTypeWriter *writer, + int typecode) +{ + /* A subwriter inside an array or variant will have type_pos + * pointing to the expected typecode; a writer not inside an array + * or variant has type_pos pointing to the next place to insert a + * typecode. + */ +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" type writer %p write_or_verify start type_pos = %d remaining sig '%s' enabled = %d\n", + writer, writer->type_pos, + writer->type_str ? + _dbus_string_get_const_data_len (writer->type_str, writer->type_pos, 0) : + "unknown", + writer->enabled); +#endif + + if (writer->type_str == NULL) + return TRUE; + + if (writer->type_pos_is_expectation) + { +#ifndef DBUS_DISABLE_CHECKS + { + int expected; + + expected = _dbus_string_get_byte (writer->type_str, writer->type_pos); + + if (expected != typecode) + { + if (expected != DBUS_TYPE_INVALID) + _dbus_warn_check_failed ("Array or variant type requires that type %s be written, but %s was written.\n" + "The overall signature expected here was '%s' and we are on byte %d of that signature.", + _dbus_type_to_string (expected), _dbus_type_to_string (typecode), + _dbus_string_get_const_data (writer->type_str), writer->type_pos); + else + _dbus_warn_check_failed ("Array or variant type wasn't expecting any more values to be written into it, but a value %s was written.\n" + "The overall signature expected here was '%s' and we are on byte %d of that signature.", + _dbus_type_to_string (typecode), + _dbus_string_get_const_data (writer->type_str), writer->type_pos); + _dbus_assert_not_reached ("bad type inserted somewhere inside an array or variant"); + } + } +#endif /* DBUS_DISABLE_CHECKS */ + + /* if immediately inside an array we'd always be appending an element, + * so the expected type doesn't change; if inside a struct or something + * below an array, we need to move through said struct or something. + */ + if (writer->container_type != DBUS_TYPE_ARRAY) + writer->type_pos += 1; + } + else + { + if (!_dbus_string_insert_byte (writer->type_str, + writer->type_pos, + typecode)) + return FALSE; + + writer->type_pos += 1; + } + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" type writer %p write_or_verify end type_pos = %d remaining sig '%s'\n", + writer, writer->type_pos, + _dbus_string_get_const_data_len (writer->type_str, writer->type_pos, 0)); +#endif + + return TRUE; +} + +static dbus_bool_t +writer_recurse_struct_or_dict_entry (DBusTypeWriter *writer, + int begin_char, + const DBusString *contained_type, + int contained_type_start, + int contained_type_len, + DBusTypeWriter *sub) +{ + /* FIXME right now contained_type is ignored; we could probably + * almost trivially fix the code so if it's present we + * write it out and then set type_pos_is_expectation + */ + + /* Ensure that we'll be able to add alignment padding and the typecode */ + if (writer->enabled) + { + if (!_dbus_string_alloc_space (sub->value_str, 8)) + return FALSE; + } + + if (!write_or_verify_typecode (sub, begin_char)) + _dbus_assert_not_reached ("failed to insert struct typecode after prealloc"); + + if (writer->enabled) + { + if (!_dbus_string_insert_bytes (sub->value_str, + sub->value_pos, + _DBUS_ALIGN_VALUE (sub->value_pos, 8) - sub->value_pos, + '\0')) + _dbus_assert_not_reached ("should not have failed to insert alignment padding for struct"); + sub->value_pos = _DBUS_ALIGN_VALUE (sub->value_pos, 8); + } + + return TRUE; +} + + +static dbus_bool_t +writer_recurse_array (DBusTypeWriter *writer, + const DBusString *contained_type, + int contained_type_start, + int contained_type_len, + DBusTypeWriter *sub, + dbus_bool_t is_array_append) +{ + dbus_uint32_t value = 0; + int alignment; + int aligned; + +#ifndef DBUS_DISABLE_CHECKS + if (writer->container_type == DBUS_TYPE_ARRAY && + writer->type_str) + { + if (!_dbus_string_equal_substring (contained_type, + contained_type_start, + contained_type_len, + writer->type_str, + writer->u.array.element_type_pos + 1)) + { + _dbus_warn_check_failed ("Writing an array of '%s' but this is incompatible with the expected type of elements in the parent array", + _dbus_string_get_const_data_len (contained_type, + contained_type_start, + contained_type_len)); + _dbus_assert_not_reached ("incompatible type for child array"); + } + } +#endif /* DBUS_DISABLE_CHECKS */ + + if (writer->enabled && !is_array_append) + { + /* 3 pad + 4 bytes for the array length, and 4 bytes possible padding + * before array values + */ + if (!_dbus_string_alloc_space (sub->value_str, 3 + 4 + 4)) + return FALSE; + } + + if (writer->type_str != NULL) + { + sub->type_pos += 1; /* move to point to the element type, since type_pos + * should be the expected type for further writes + */ + sub->u.array.element_type_pos = sub->type_pos; + } + + if (!writer->type_pos_is_expectation) + { + /* sub is a toplevel/outermost array so we need to write the type data */ + + /* alloc space for array typecode, element signature */ + if (!_dbus_string_alloc_space (writer->type_str, 1 + contained_type_len)) + return FALSE; + + if (!_dbus_string_insert_byte (writer->type_str, + writer->type_pos, + DBUS_TYPE_ARRAY)) + _dbus_assert_not_reached ("failed to insert array typecode after prealloc"); + + if (!_dbus_string_copy_len (contained_type, + contained_type_start, contained_type_len, + sub->type_str, + sub->u.array.element_type_pos)) + _dbus_assert_not_reached ("should not have failed to insert array element typecodes"); + } + + if (writer->type_str != NULL) + { + /* If the parent is an array, we hold type_pos pointing at the array element type; + * otherwise advance it to reflect the array value we just recursed into + */ + if (writer->container_type != DBUS_TYPE_ARRAY) + writer->type_pos += 1 + contained_type_len; + else + _dbus_assert (writer->type_pos_is_expectation); /* because it's an array */ + } + + if (writer->enabled) + { + /* Write (or jump over, if is_array_append) the length */ + sub->u.array.len_pos = _DBUS_ALIGN_VALUE (sub->value_pos, 4); + + if (is_array_append) + { + sub->value_pos += 4; + } + else + { + if (!_dbus_type_writer_write_basic_no_typecode (sub, DBUS_TYPE_UINT32, + &value)) + _dbus_assert_not_reached ("should not have failed to insert array len"); + } + + _dbus_assert (sub->u.array.len_pos == sub->value_pos - 4); + + /* Write alignment padding for array elements + * Note that we write the padding *even for empty arrays* + * to avoid wonky special cases + */ + alignment = element_type_get_alignment (contained_type, contained_type_start); + + aligned = _DBUS_ALIGN_VALUE (sub->value_pos, alignment); + if (aligned != sub->value_pos) + { + if (!is_array_append) + { + if (!_dbus_string_insert_bytes (sub->value_str, + sub->value_pos, + aligned - sub->value_pos, + '\0')) + _dbus_assert_not_reached ("should not have failed to insert alignment padding"); + } + + sub->value_pos = aligned; + } + + sub->u.array.start_pos = sub->value_pos; + + if (is_array_append) + { + dbus_uint32_t len; + + _dbus_assert (_DBUS_ALIGN_VALUE (sub->u.array.len_pos, 4) == + (unsigned) sub->u.array.len_pos); + len = _dbus_unpack_uint32 (sub->byte_order, + _dbus_string_get_const_udata_len (sub->value_str, + sub->u.array.len_pos, + 4)); + + sub->value_pos += len; + } + } + else + { + /* not enabled, so we won't write the len_pos; set it to -1 to so indicate */ + sub->u.array.len_pos = -1; + sub->u.array.start_pos = sub->value_pos; + } + + _dbus_assert (sub->u.array.len_pos < sub->u.array.start_pos); + _dbus_assert (is_array_append || sub->u.array.start_pos == sub->value_pos); + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" type writer %p recurse array done remaining sig '%s' array start_pos = %d len_pos = %d value_pos = %d\n", sub, + sub->type_str ? + _dbus_string_get_const_data_len (sub->type_str, sub->type_pos, 0) : + "unknown", + sub->u.array.start_pos, sub->u.array.len_pos, sub->value_pos); +#endif + + return TRUE; +} + +/* Variant value will normally have: + * 1 byte signature length not including nul + * signature typecodes (nul terminated) + * padding to alignment of contained type + * body according to signature + * + * The signature string can only have a single type + * in it but that type may be complex/recursive. + * + * So a typical variant type with the integer 3 will have these + * octets: + * 0x1 'i' '\0' [1 byte padding to alignment boundary] 0x0 0x0 0x0 0x3 + * + * The main world of hurt for writing out a variant is that the type + * string is the same string as the value string. Which means + * inserting to the type string will move the value_pos; and it means + * that inserting to the type string could break type alignment. + */ +static dbus_bool_t +writer_recurse_variant (DBusTypeWriter *writer, + const DBusString *contained_type, + int contained_type_start, + int contained_type_len, + DBusTypeWriter *sub) +{ + int contained_alignment; + + if (writer->enabled) + { + /* Allocate space for the worst case, which is 1 byte sig + * length, nul byte at end of sig, and 7 bytes padding to + * 8-boundary. + */ + if (!_dbus_string_alloc_space (sub->value_str, contained_type_len + 9)) + return FALSE; + } + + /* write VARIANT typecode to the parent's type string */ + if (!write_or_verify_typecode (writer, DBUS_TYPE_VARIANT)) + return FALSE; + + /* If not enabled, mark that we have no type_str anymore ... */ + + if (!writer->enabled) + { + sub->type_str = NULL; + sub->type_pos = -1; + + return TRUE; + } + + /* If we're enabled then continue ... */ + + if (!_dbus_string_insert_byte (sub->value_str, + sub->value_pos, + contained_type_len)) + _dbus_assert_not_reached ("should not have failed to insert variant type sig len"); + + sub->value_pos += 1; + + /* Here we switch over to the expected type sig we're about to write */ + sub->type_str = sub->value_str; + sub->type_pos = sub->value_pos; + + if (!_dbus_string_copy_len (contained_type, contained_type_start, contained_type_len, + sub->value_str, sub->value_pos)) + _dbus_assert_not_reached ("should not have failed to insert variant type sig"); + + sub->value_pos += contained_type_len; + + if (!_dbus_string_insert_byte (sub->value_str, + sub->value_pos, + DBUS_TYPE_INVALID)) + _dbus_assert_not_reached ("should not have failed to insert variant type nul termination"); + + sub->value_pos += 1; + + contained_alignment = _dbus_type_get_alignment (_dbus_first_type_in_signature (contained_type, contained_type_start)); + + if (!_dbus_string_insert_bytes (sub->value_str, + sub->value_pos, + _DBUS_ALIGN_VALUE (sub->value_pos, contained_alignment) - sub->value_pos, + '\0')) + _dbus_assert_not_reached ("should not have failed to insert alignment padding for variant body"); + sub->value_pos = _DBUS_ALIGN_VALUE (sub->value_pos, contained_alignment); + + return TRUE; +} + +static dbus_bool_t +_dbus_type_writer_recurse_contained_len (DBusTypeWriter *writer, + int container_type, + const DBusString *contained_type, + int contained_type_start, + int contained_type_len, + DBusTypeWriter *sub, + dbus_bool_t is_array_append) +{ + writer_recurse_init_and_check (writer, container_type, sub); + + switch (container_type) + { + case DBUS_TYPE_STRUCT: + return writer_recurse_struct_or_dict_entry (writer, + DBUS_STRUCT_BEGIN_CHAR, + contained_type, + contained_type_start, contained_type_len, + sub); + break; + case DBUS_TYPE_DICT_ENTRY: + return writer_recurse_struct_or_dict_entry (writer, + DBUS_DICT_ENTRY_BEGIN_CHAR, + contained_type, + contained_type_start, contained_type_len, + sub); + break; + case DBUS_TYPE_ARRAY: + return writer_recurse_array (writer, + contained_type, contained_type_start, contained_type_len, + sub, is_array_append); + break; + case DBUS_TYPE_VARIANT: + return writer_recurse_variant (writer, + contained_type, contained_type_start, contained_type_len, + sub); + break; + default: + _dbus_assert_not_reached ("tried to recurse into type that doesn't support that"); + return FALSE; + break; + } +} + +/** + * Opens a new container and writes out the initial information for that container. + * + * @param writer the writer + * @param container_type the type of the container to open + * @param contained_type the array element type or variant content type + * @param contained_type_start position to look for the type + * @param sub the new sub-writer to write container contents + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_type_writer_recurse (DBusTypeWriter *writer, + int container_type, + const DBusString *contained_type, + int contained_type_start, + DBusTypeWriter *sub) +{ + int contained_type_len; + + if (contained_type) + contained_type_len = find_len_of_complete_type (contained_type, contained_type_start); + else + contained_type_len = 0; + + return _dbus_type_writer_recurse_contained_len (writer, container_type, + contained_type, + contained_type_start, + contained_type_len, + sub, + FALSE); +} + +/** + * Append to an existing array. Essentially, the writer will read an + * existing length at the write location; jump over that length; and + * write new fields. On unrecurse(), the existing length will be + * updated. + * + * @param writer the writer + * @param contained_type element type + * @param contained_type_start position of element type + * @param sub the subwriter to init + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_type_writer_append_array (DBusTypeWriter *writer, + const DBusString *contained_type, + int contained_type_start, + DBusTypeWriter *sub) +{ + int contained_type_len; + + if (contained_type) + contained_type_len = find_len_of_complete_type (contained_type, contained_type_start); + else + contained_type_len = 0; + + return _dbus_type_writer_recurse_contained_len (writer, DBUS_TYPE_ARRAY, + contained_type, + contained_type_start, + contained_type_len, + sub, + TRUE); +} + +static int +writer_get_array_len (DBusTypeWriter *writer) +{ + _dbus_assert (writer->container_type == DBUS_TYPE_ARRAY); + return writer->value_pos - writer->u.array.start_pos; +} + +/** + * Closes a container created by _dbus_type_writer_recurse() + * and writes any additional information to the values block. + * + * @param writer the writer + * @param sub the sub-writer created by _dbus_type_writer_recurse() + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_type_writer_unrecurse (DBusTypeWriter *writer, + DBusTypeWriter *sub) +{ + /* type_pos_is_expectation never gets unset once set, or we'd get all hosed */ + _dbus_assert (!writer->type_pos_is_expectation || + (writer->type_pos_is_expectation && sub->type_pos_is_expectation)); + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" type writer %p unrecurse type_pos = %d value_pos = %d is_expectation = %d container_type = %s\n", + writer, writer->type_pos, writer->value_pos, writer->type_pos_is_expectation, + _dbus_type_to_string (writer->container_type)); + _dbus_verbose (" type writer %p unrecurse sub type_pos = %d value_pos = %d is_expectation = %d container_type = %s\n", + sub, sub->type_pos, sub->value_pos, + sub->type_pos_is_expectation, + _dbus_type_to_string (sub->container_type)); +#endif + + if (sub->container_type == DBUS_TYPE_STRUCT) + { + if (!write_or_verify_typecode (sub, DBUS_STRUCT_END_CHAR)) + return FALSE; + } + else if (sub->container_type == DBUS_TYPE_DICT_ENTRY) + { + if (!write_or_verify_typecode (sub, DBUS_DICT_ENTRY_END_CHAR)) + return FALSE; + } + else if (sub->container_type == DBUS_TYPE_ARRAY) + { + if (sub->u.array.len_pos >= 0) /* len_pos == -1 if we weren't enabled when we passed it */ + { + dbus_uint32_t len; + + /* Set the array length */ + len = writer_get_array_len (sub); + _dbus_marshal_set_uint32 (sub->value_str, + sub->u.array.len_pos, + len, + sub->byte_order); +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" filled in sub array len to %u at len_pos %d\n", + len, sub->u.array.len_pos); +#endif + } +#if RECURSIVE_MARSHAL_WRITE_TRACE + else + { + _dbus_verbose (" not filling in sub array len because we were disabled when we passed the len\n"); + } +#endif + } + + /* Now get type_pos right for the parent writer. Here are the cases: + * + * Cases !writer->type_pos_is_expectation: + * (in these cases we want to update to the new insertion point) + * + * - if we recursed into a STRUCT then we didn't know in advance + * what the types in the struct would be; so we have to fill in + * that information now. + * writer->type_pos = sub->type_pos + * + * - if we recursed into anything else, we knew the full array + * type, or knew the single typecode marking VARIANT, so + * writer->type_pos is already correct. + * writer->type_pos should remain as-is + * + * - note that the parent is never an ARRAY or VARIANT, if it were + * then type_pos_is_expectation would be TRUE. The parent + * is thus known to be a toplevel or STRUCT. + * + * Cases where writer->type_pos_is_expectation: + * (in these cases we want to update to next expected type to write) + * + * - we recursed from STRUCT into STRUCT and we didn't increment + * type_pos in the parent just to stay consistent with the + * !writer->type_pos_is_expectation case (though we could + * special-case this in recurse_struct instead if we wanted) + * writer->type_pos = sub->type_pos + * + * - we recursed from STRUCT into ARRAY or VARIANT and type_pos + * for parent should have been incremented already + * writer->type_pos should remain as-is + * + * - we recursed from ARRAY into a sub-element, so type_pos in the + * parent is the element type and should remain the element type + * for the benefit of the next child element + * writer->type_pos should remain as-is + * + * - we recursed from VARIANT into its value, so type_pos in the + * parent makes no difference since there's only one value + * and we just finished writing it and won't use type_pos again + * writer->type_pos should remain as-is + * + * + * For all these, DICT_ENTRY is the same as STRUCT + */ + if (writer->type_str != NULL) + { + if ((sub->container_type == DBUS_TYPE_STRUCT || + sub->container_type == DBUS_TYPE_DICT_ENTRY) && + (writer->container_type == DBUS_TYPE_STRUCT || + writer->container_type == DBUS_TYPE_DICT_ENTRY || + writer->container_type == DBUS_TYPE_INVALID)) + { + /* Advance the parent to the next struct field */ + writer->type_pos = sub->type_pos; + } + } + + writer->value_pos = sub->value_pos; + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" type writer %p unrecursed type_pos = %d value_pos = %d remaining sig '%s'\n", + writer, writer->type_pos, writer->value_pos, + writer->type_str ? + _dbus_string_get_const_data_len (writer->type_str, writer->type_pos, 0) : + "unknown"); +#endif + + return TRUE; +} + +/** + * Writes out a basic type. + * + * @param writer the writer + * @param type the type to write + * @param value the address of the value to write + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_type_writer_write_basic (DBusTypeWriter *writer, + int type, + const void *value) +{ + dbus_bool_t retval; + + /* First ensure that our type realloc will succeed */ + if (!writer->type_pos_is_expectation && writer->type_str != NULL) + { + if (!_dbus_string_alloc_space (writer->type_str, 1)) + return FALSE; + } + + retval = FALSE; + + if (!_dbus_type_writer_write_basic_no_typecode (writer, type, value)) + goto out; + + if (!write_or_verify_typecode (writer, type)) + _dbus_assert_not_reached ("failed to write typecode after prealloc"); + + retval = TRUE; + + out: +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" type writer %p basic type_pos = %d value_pos = %d is_expectation = %d enabled = %d\n", + writer, writer->type_pos, writer->value_pos, writer->type_pos_is_expectation, + writer->enabled); +#endif + + return retval; +} + +/** + * Writes a block of fixed-length basic values, i.e. those that are + * both dbus_type_is_fixed() and _dbus_type_is_basic(). The block + * must be written inside an array. + * + * The value parameter should be the address of said array of values, + * so e.g. if it's an array of double, pass in "const double**" + * + * @param writer the writer + * @param element_type type of stuff in the array + * @param value address of the array + * @param n_elements number of elements in the array + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_type_writer_write_fixed_multi (DBusTypeWriter *writer, + int element_type, + const void *value, + int n_elements) +{ + _dbus_assert (writer->container_type == DBUS_TYPE_ARRAY); + _dbus_assert (dbus_type_is_fixed (element_type)); + _dbus_assert (writer->type_pos_is_expectation); + _dbus_assert (n_elements >= 0); + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" type writer %p entering fixed multi type_pos = %d value_pos = %d n_elements %d\n", + writer, writer->type_pos, writer->value_pos, n_elements); +#endif + + if (!write_or_verify_typecode (writer, element_type)) + _dbus_assert_not_reached ("OOM should not happen if only verifying typecode"); + + if (writer->enabled) + { + if (!_dbus_marshal_write_fixed_multi (writer->value_str, + writer->value_pos, + element_type, + value, + n_elements, + writer->byte_order, + &writer->value_pos)) + return FALSE; + } + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose (" type writer %p fixed multi written new type_pos = %d new value_pos = %d n_elements %d\n", + writer, writer->type_pos, writer->value_pos, n_elements); +#endif + + return TRUE; +} + +static void +enable_if_after (DBusTypeWriter *writer, + DBusTypeReader *reader, + const DBusTypeReader *start_after) +{ + if (start_after) + { + if (!writer->enabled && _dbus_type_reader_greater_than (reader, start_after)) + { + _dbus_type_writer_set_enabled (writer, TRUE); +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("ENABLING writer %p at %d because reader at value_pos %d is after reader at value_pos %d\n", + writer, writer->value_pos, reader->value_pos, start_after->value_pos); +#endif + } + + _dbus_assert ((!writer->enabled && !_dbus_type_reader_greater_than (reader, start_after)) || + (writer->enabled && _dbus_type_reader_greater_than (reader, start_after))); + } +} + +static dbus_bool_t +append_fixup (DBusList **fixups, + const DBusArrayLenFixup *fixup) +{ + DBusArrayLenFixup *f; + + f = dbus_new (DBusArrayLenFixup, 1); + if (f == NULL) + return FALSE; + + *f = *fixup; + + if (!_dbus_list_append (fixups, f)) + { + dbus_free (f); + return FALSE; + } + + _dbus_assert (f->len_pos_in_reader == fixup->len_pos_in_reader); + _dbus_assert (f->new_len == fixup->new_len); + + return TRUE; +} + +/* This loop is trivial if you ignore all the start_after nonsense, + * so if you're trying to figure it out, start by ignoring that + */ +static dbus_bool_t +writer_write_reader_helper (DBusTypeWriter *writer, + DBusTypeReader *reader, + const DBusTypeReader *start_after, + int start_after_new_pos, + int start_after_new_len, + DBusList **fixups, + dbus_bool_t inside_start_after) +{ + int current_type; + + while ((current_type = _dbus_type_reader_get_current_type (reader)) != DBUS_TYPE_INVALID) + { + if (dbus_type_is_container (current_type)) + { + DBusTypeReader subreader; + DBusTypeWriter subwriter; + const DBusString *sig_str; + int sig_start; + int sig_len; + dbus_bool_t enabled_at_recurse; + dbus_bool_t past_start_after; + int reader_array_len_pos; + int reader_array_start_pos; + dbus_bool_t this_is_start_after; + + /* type_pos is checked since e.g. in a struct the struct + * and its first field have the same value_pos. + * type_str will differ in reader/start_after for variants + * where type_str is inside the value_str + */ + if (!inside_start_after && start_after && + reader->value_pos == start_after->value_pos && + reader->type_str == start_after->type_str && + reader->type_pos == start_after->type_pos) + this_is_start_after = TRUE; + else + this_is_start_after = FALSE; + + _dbus_type_reader_recurse (reader, &subreader); + + if (current_type == DBUS_TYPE_ARRAY) + { + reader_array_len_pos = ARRAY_READER_LEN_POS (&subreader); + reader_array_start_pos = subreader.u.array.start_pos; + } + else + { + /* quiet gcc */ + reader_array_len_pos = -1; + reader_array_start_pos = -1; + } + + _dbus_type_reader_get_signature (&subreader, &sig_str, + &sig_start, &sig_len); + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("about to recurse into %s reader at %d subreader at %d writer at %d start_after reader at %d write target len %d inside_start_after = %d this_is_start_after = %d\n", + _dbus_type_to_string (current_type), + reader->value_pos, + subreader.value_pos, + writer->value_pos, + start_after ? start_after->value_pos : -1, + _dbus_string_get_length (writer->value_str), + inside_start_after, this_is_start_after); +#endif + + if (!inside_start_after && !this_is_start_after) + enable_if_after (writer, &subreader, start_after); + enabled_at_recurse = writer->enabled; + if (!_dbus_type_writer_recurse_contained_len (writer, current_type, + sig_str, sig_start, sig_len, + &subwriter, FALSE)) + goto oom; + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("recursed into subwriter at %d write target len %d\n", + subwriter.value_pos, + _dbus_string_get_length (subwriter.value_str)); +#endif + + if (!writer_write_reader_helper (&subwriter, &subreader, start_after, + start_after_new_pos, start_after_new_len, + fixups, + inside_start_after || + this_is_start_after)) + goto oom; + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("about to unrecurse from %s subreader at %d writer at %d subwriter at %d write target len %d\n", + _dbus_type_to_string (current_type), + subreader.value_pos, + writer->value_pos, + subwriter.value_pos, + _dbus_string_get_length (writer->value_str)); +#endif + + if (!inside_start_after && !this_is_start_after) + enable_if_after (writer, &subreader, start_after); + past_start_after = writer->enabled; + if (!_dbus_type_writer_unrecurse (writer, &subwriter)) + goto oom; + + /* If we weren't enabled when we recursed, we didn't + * write an array len; if we passed start_after + * somewhere inside the array, then we need to generate + * a fixup. + */ + if (start_after != NULL && + !enabled_at_recurse && past_start_after && + current_type == DBUS_TYPE_ARRAY && + fixups != NULL) + { + DBusArrayLenFixup fixup; + int bytes_written_after_start_after; + int bytes_before_start_after; + int old_len; + + /* this subwriter access is moderately unkosher since we + * already unrecursed, but it works as long as unrecurse + * doesn't break us on purpose + */ + bytes_written_after_start_after = writer_get_array_len (&subwriter); + + bytes_before_start_after = + start_after->value_pos - reader_array_start_pos; + + fixup.len_pos_in_reader = reader_array_len_pos; + fixup.new_len = + bytes_before_start_after + + start_after_new_len + + bytes_written_after_start_after; + + _dbus_assert (_DBUS_ALIGN_VALUE (fixup.len_pos_in_reader, 4) == + (unsigned) fixup.len_pos_in_reader); + + old_len = _dbus_unpack_uint32 (reader->byte_order, + _dbus_string_get_const_udata_len (reader->value_str, + fixup.len_pos_in_reader, 4)); + + if (old_len != fixup.new_len && !append_fixup (fixups, &fixup)) + goto oom; + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("Generated fixup len_pos_in_reader = %d new_len = %d reader_array_start_pos = %d start_after->value_pos = %d bytes_before_start_after = %d start_after_new_len = %d bytes_written_after_start_after = %d\n", + fixup.len_pos_in_reader, + fixup.new_len, + reader_array_start_pos, + start_after->value_pos, + bytes_before_start_after, + start_after_new_len, + bytes_written_after_start_after); +#endif + } + } + else + { + DBusBasicValue val; + + _dbus_assert (dbus_type_is_basic (current_type)); + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("Reading basic value %s at %d\n", + _dbus_type_to_string (current_type), + reader->value_pos); +#endif + + _dbus_type_reader_read_basic (reader, &val); + +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("Writing basic value %s at %d write target len %d inside_start_after = %d\n", + _dbus_type_to_string (current_type), + writer->value_pos, + _dbus_string_get_length (writer->value_str), + inside_start_after); +#endif + if (!inside_start_after) + enable_if_after (writer, reader, start_after); + if (!_dbus_type_writer_write_basic (writer, current_type, &val)) + goto oom; +#if RECURSIVE_MARSHAL_WRITE_TRACE + _dbus_verbose ("Wrote basic value %s, new value_pos %d write target len %d\n", + _dbus_type_to_string (current_type), + writer->value_pos, + _dbus_string_get_length (writer->value_str)); +#endif + } + + _dbus_type_reader_next (reader); + } + + return TRUE; + + oom: + if (fixups) + apply_and_free_fixups (fixups, NULL); /* NULL for reader to apply to */ + + return FALSE; +} + +/* + * Iterate through all values in the given reader, writing a copy of + * each value to the writer. The reader will be moved forward to its + * end position. + * + * If a reader start_after is provided, it should be a reader for the + * same data as the reader to be written. Only values occurring after + * the value pointed to by start_after will be written to the writer. + * + * If start_after is provided, then the copy of the reader will be + * partial. This means that array lengths will not have been copied. + * The assumption is that you wrote a new version of the value at + * start_after to the writer. You have to pass in the start position + * and length of the new value. (If you are deleting the value + * at start_after, pass in 0 for the length.) + * + * If the fixups parameter is non-#NULL, then any array length that + * was read but not written due to start_after will be provided + * as a #DBusArrayLenFixup. The fixup contains the position of the + * array length in the source data, and the correct array length + * assuming you combine the source data before start_after with + * the written data at start_after and beyond. + * + * @param writer the writer to copy to + * @param reader the reader to copy from + * @param start_after #NULL or a reader showing where to start + * @param start_after_new_pos the position of start_after equivalent in the target data + * @param start_after_new_len the length of start_after equivalent in the target data + * @param fixups list to append #DBusArrayLenFixup if the write was partial + * @returns #FALSE if no memory + */ +static dbus_bool_t +_dbus_type_writer_write_reader_partial (DBusTypeWriter *writer, + DBusTypeReader *reader, + const DBusTypeReader *start_after, + int start_after_new_pos, + int start_after_new_len, + DBusList **fixups) +{ + DBusTypeWriter orig; + int orig_type_len; + int orig_value_len; + int new_bytes; + int orig_enabled; + + orig = *writer; + orig_type_len = _dbus_string_get_length (writer->type_str); + orig_value_len = _dbus_string_get_length (writer->value_str); + orig_enabled = writer->enabled; + + if (start_after) + _dbus_type_writer_set_enabled (writer, FALSE); + + if (!writer_write_reader_helper (writer, reader, start_after, + start_after_new_pos, + start_after_new_len, + fixups, FALSE)) + goto oom; + + _dbus_type_writer_set_enabled (writer, orig_enabled); + return TRUE; + + oom: + if (!writer->type_pos_is_expectation) + { + new_bytes = _dbus_string_get_length (writer->type_str) - orig_type_len; + _dbus_string_delete (writer->type_str, orig.type_pos, new_bytes); + } + new_bytes = _dbus_string_get_length (writer->value_str) - orig_value_len; + _dbus_string_delete (writer->value_str, orig.value_pos, new_bytes); + + *writer = orig; + + return FALSE; +} + +/** + * Iterate through all values in the given reader, writing a copy of + * each value to the writer. The reader will be moved forward to its + * end position. + * + * @param writer the writer to copy to + * @param reader the reader to copy from + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_type_writer_write_reader (DBusTypeWriter *writer, + DBusTypeReader *reader) +{ + return _dbus_type_writer_write_reader_partial (writer, reader, NULL, 0, 0, NULL); +} + +/* + * If disabled, a writer can still be iterated forward and recursed/unrecursed + * but won't write any values. Types will still be written unless the + * writer is a "values only" writer, because the writer needs access to + * a valid signature to be able to iterate. + * + * @param writer the type writer + * @param enabled #TRUE if values should be written + */ +static void +_dbus_type_writer_set_enabled (DBusTypeWriter *writer, + dbus_bool_t enabled) +{ + writer->enabled = enabled != FALSE; +} + +/** @} */ /* end of DBusMarshal group */ + +/* tests in dbus-marshal-recursive-util.c */ diff --git a/src/3rdparty/libdbus/dbus/dbus-marshal-recursive.h b/src/3rdparty/libdbus/dbus/dbus-marshal-recursive.h new file mode 100644 index 00000000..4aa61ba1 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-marshal-recursive.h @@ -0,0 +1,203 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-marshal-recursive.h Marshalling routines for recursive types + * + * Copyright (C) 2004, 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_MARSHAL_RECURSIVE_H +#define DBUS_MARSHAL_RECURSIVE_H + +#include <dbus/dbus-protocol.h> +#include <dbus/dbus-list.h> + +typedef struct DBusTypeReader DBusTypeReader; +typedef struct DBusTypeWriter DBusTypeWriter; +typedef struct DBusTypeReaderClass DBusTypeReaderClass; +typedef struct DBusArrayLenFixup DBusArrayLenFixup; + +/** + * The type reader is an iterator for reading values from a block of + * values. + */ +struct DBusTypeReader +{ + const DBusTypeReaderClass *klass; /**< the vtable for the reader */ + const DBusString *type_str; /**< string containing signature of block */ + const DBusString *value_str; /**< string containing values of block */ + + dbus_uint32_t byte_order : 8; /**< byte order of the block */ + + dbus_uint32_t finished : 1; /**< marks we're at end iterator for cases + * where we don't have another way to tell + */ + dbus_uint32_t array_len_offset : 3; /**< bytes back from start_pos that len ends */ + int type_pos; /**< current position in signature */ + int value_pos; /**< current position in values */ + + union + { + struct { + int start_pos; /**< for array readers, the start of the array values */ + } array; + } u; /**< class-specific data */ +}; + +/** + * The type writer is an iterator for writing to a block of values. + */ +struct DBusTypeWriter +{ + DBusString *type_str; /**< where to write typecodes (or read type expectations) */ + DBusString *value_str; /**< where to write values */ + dbus_uint32_t byte_order : 8; /**< byte order to write values with */ + + dbus_uint32_t container_type : 8; /**< what are we inside? (e.g. struct, variant, array) */ + + dbus_uint32_t type_pos_is_expectation : 1; /**< type_pos can be either an insertion point for or an expected next type */ + + dbus_uint32_t enabled : 1; /**< whether to write values */ + + int type_pos; /**< current pos in type_str */ + int value_pos; /**< next position to write */ + + union + { + struct { + int start_pos; /**< position of first element in the array */ + int len_pos; /**< position of length of the array */ + int element_type_pos; /**< position of array element type in type_str */ + } array; + } u; /**< class-specific data */ +}; + +/** + * When modifying an existing block of values, array lengths may need + * to be adjusted; those adjustments are described by this struct. + */ +struct DBusArrayLenFixup +{ + int len_pos_in_reader; /**< where the length was in the original block */ + int new_len; /**< the new value of the length in the written-out block */ +}; + +DBUS_PRIVATE_EXPORT +void _dbus_type_reader_init (DBusTypeReader *reader, + int byte_order, + const DBusString *type_str, + int type_pos, + const DBusString *value_str, + int value_pos); +DBUS_PRIVATE_EXPORT +void _dbus_type_reader_init_types_only (DBusTypeReader *reader, + const DBusString *type_str, + int type_pos); +DBUS_PRIVATE_EXPORT +int _dbus_type_reader_get_current_type (const DBusTypeReader *reader); +DBUS_PRIVATE_EXPORT +int _dbus_type_reader_get_element_type (const DBusTypeReader *reader); +DBUS_PRIVATE_EXPORT +int _dbus_type_reader_get_value_pos (const DBusTypeReader *reader); +DBUS_PRIVATE_EXPORT +void _dbus_type_reader_read_basic (const DBusTypeReader *reader, + void *value); +int _dbus_type_reader_get_array_length (const DBusTypeReader *reader); +DBUS_PRIVATE_EXPORT +void _dbus_type_reader_read_fixed_multi (const DBusTypeReader *reader, + const void **value, + int *n_elements); +void _dbus_type_reader_read_raw (const DBusTypeReader *reader, + const unsigned char **value_location); +DBUS_PRIVATE_EXPORT +void _dbus_type_reader_recurse (DBusTypeReader *reader, + DBusTypeReader *subreader); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_type_reader_next (DBusTypeReader *reader); +dbus_bool_t _dbus_type_reader_has_next (const DBusTypeReader *reader); +DBUS_PRIVATE_EXPORT +void _dbus_type_reader_get_signature (const DBusTypeReader *reader, + const DBusString **str_p, + int *start_p, + int *len_p); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_type_reader_set_basic (DBusTypeReader *reader, + const void *value, + const DBusTypeReader *realign_root); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_type_reader_delete (DBusTypeReader *reader, + const DBusTypeReader *realign_root); + +dbus_bool_t _dbus_type_reader_equal_values (const DBusTypeReader *lhs, + const DBusTypeReader *rhs); + +void _dbus_type_signature_next (const char *signature, + int *type_pos); + +DBUS_PRIVATE_EXPORT +void _dbus_type_writer_init (DBusTypeWriter *writer, + int byte_order, + DBusString *type_str, + int type_pos, + DBusString *value_str, + int value_pos); +void _dbus_type_writer_init_types_delayed (DBusTypeWriter *writer, + int byte_order, + DBusString *value_str, + int value_pos); +void _dbus_type_writer_add_types (DBusTypeWriter *writer, + DBusString *type_str, + int type_pos); +void _dbus_type_writer_remove_types (DBusTypeWriter *writer); +DBUS_PRIVATE_EXPORT +void _dbus_type_writer_init_values_only (DBusTypeWriter *writer, + int byte_order, + const DBusString *type_str, + int type_pos, + DBusString *value_str, + int value_pos); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_type_writer_write_basic (DBusTypeWriter *writer, + int type, + const void *value); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_type_writer_write_fixed_multi (DBusTypeWriter *writer, + int element_type, + const void *value, + int n_elements); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_type_writer_recurse (DBusTypeWriter *writer, + int container_type, + const DBusString *contained_type, + int contained_type_start, + DBusTypeWriter *sub); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_type_writer_unrecurse (DBusTypeWriter *writer, + DBusTypeWriter *sub); +dbus_bool_t _dbus_type_writer_append_array (DBusTypeWriter *writer, + const DBusString *contained_type, + int contained_type_start, + DBusTypeWriter *sub); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_type_writer_write_reader (DBusTypeWriter *writer, + DBusTypeReader *reader); + + +#endif /* DBUS_MARSHAL_RECURSIVE_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-marshal-validate.c b/src/3rdparty/libdbus/dbus/dbus-marshal-validate.c new file mode 100644 index 00000000..53378b61 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-marshal-validate.c @@ -0,0 +1,1296 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-marshal-validate.c Validation routines for marshaled data + * + * Copyright (C) 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-marshal-validate.h" +#include "dbus-marshal-recursive.h" +#include "dbus-marshal-basic.h" +#include "dbus-signature.h" +#include "dbus-string.h" + +/** + * @addtogroup DBusMarshal + * + * @{ + */ + +/** + * Verifies that the range of type_str from type_pos to type_end is a + * valid signature. If this function returns #TRUE, it will be safe + * to iterate over the signature with a types-only #DBusTypeReader. + * The range passed in should NOT include the terminating + * nul/DBUS_TYPE_INVALID. + * + * @param type_str the string + * @param type_pos where the typecodes start + * @param len length of typecodes + * @returns #DBUS_VALID if valid, reason why invalid otherwise + */ +DBusValidity +_dbus_validate_signature_with_reason (const DBusString *type_str, + int type_pos, + int len) +{ + const unsigned char *p; + const unsigned char *end; + int last; + int struct_depth; + int array_depth; + int dict_entry_depth; + DBusValidity result; + + int element_count; + DBusList *element_count_stack; + char opened_brackets[DBUS_MAXIMUM_TYPE_RECURSION_DEPTH * 2 + 1] = { '\0' }; + char last_bracket; + + result = DBUS_VALID; + element_count_stack = NULL; + + if (!_dbus_list_append (&element_count_stack, _DBUS_INT_TO_POINTER (0))) + { + result = DBUS_VALIDITY_UNKNOWN_OOM_ERROR; + goto out; + } + + _dbus_assert (type_str != NULL); + _dbus_assert (type_pos < _DBUS_INT32_MAX - len); + _dbus_assert (len >= 0); + _dbus_assert (type_pos >= 0); + + if (len > DBUS_MAXIMUM_SIGNATURE_LENGTH) + { + result = DBUS_INVALID_SIGNATURE_TOO_LONG; + goto out; + } + + p = _dbus_string_get_const_udata_len (type_str, type_pos, 0); + + end = _dbus_string_get_const_udata_len (type_str, type_pos + len, 0); + struct_depth = 0; + array_depth = 0; + dict_entry_depth = 0; + last = DBUS_TYPE_INVALID; + + while (p != end) + { + _dbus_assert (struct_depth + dict_entry_depth >= 0); + _dbus_assert (struct_depth + dict_entry_depth < _DBUS_N_ELEMENTS (opened_brackets)); + _dbus_assert (opened_brackets[struct_depth + dict_entry_depth] == '\0'); + + switch (*p) + { + case DBUS_TYPE_BYTE: + case DBUS_TYPE_BOOLEAN: + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_UNIX_FD: + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + case DBUS_TYPE_SIGNATURE: + case DBUS_TYPE_VARIANT: + break; + + case DBUS_TYPE_ARRAY: + array_depth += 1; + if (array_depth > DBUS_MAXIMUM_TYPE_RECURSION_DEPTH) + { + result = DBUS_INVALID_EXCEEDED_MAXIMUM_ARRAY_RECURSION; + goto out; + } + break; + + case DBUS_STRUCT_BEGIN_CHAR: + struct_depth += 1; + + if (struct_depth > DBUS_MAXIMUM_TYPE_RECURSION_DEPTH) + { + result = DBUS_INVALID_EXCEEDED_MAXIMUM_STRUCT_RECURSION; + goto out; + } + + if (!_dbus_list_append (&element_count_stack, + _DBUS_INT_TO_POINTER (0))) + { + result = DBUS_VALIDITY_UNKNOWN_OOM_ERROR; + goto out; + } + + _dbus_assert (struct_depth + dict_entry_depth >= 1); + _dbus_assert (struct_depth + dict_entry_depth < _DBUS_N_ELEMENTS (opened_brackets)); + _dbus_assert (opened_brackets[struct_depth + dict_entry_depth - 1] == '\0'); + opened_brackets[struct_depth + dict_entry_depth - 1] = DBUS_STRUCT_BEGIN_CHAR; + break; + + case DBUS_STRUCT_END_CHAR: + if (struct_depth == 0) + { + result = DBUS_INVALID_STRUCT_ENDED_BUT_NOT_STARTED; + goto out; + } + + if (last == DBUS_STRUCT_BEGIN_CHAR) + { + result = DBUS_INVALID_STRUCT_HAS_NO_FIELDS; + goto out; + } + + _dbus_assert (struct_depth + dict_entry_depth >= 1); + _dbus_assert (struct_depth + dict_entry_depth < _DBUS_N_ELEMENTS (opened_brackets)); + last_bracket = opened_brackets[struct_depth + dict_entry_depth - 1]; + + if (last_bracket != DBUS_STRUCT_BEGIN_CHAR) + { + result = DBUS_INVALID_STRUCT_ENDED_BUT_NOT_STARTED; + goto out; + } + + _dbus_list_pop_last (&element_count_stack); + + struct_depth -= 1; + opened_brackets[struct_depth + dict_entry_depth] = '\0'; + break; + + case DBUS_DICT_ENTRY_BEGIN_CHAR: + if (last != DBUS_TYPE_ARRAY) + { + result = DBUS_INVALID_DICT_ENTRY_NOT_INSIDE_ARRAY; + goto out; + } + + dict_entry_depth += 1; + + if (dict_entry_depth > DBUS_MAXIMUM_TYPE_RECURSION_DEPTH) + { + result = DBUS_INVALID_EXCEEDED_MAXIMUM_DICT_ENTRY_RECURSION; + goto out; + } + + if (!_dbus_list_append (&element_count_stack, + _DBUS_INT_TO_POINTER (0))) + { + result = DBUS_VALIDITY_UNKNOWN_OOM_ERROR; + goto out; + } + + _dbus_assert (struct_depth + dict_entry_depth >= 1); + _dbus_assert (struct_depth + dict_entry_depth < _DBUS_N_ELEMENTS (opened_brackets)); + _dbus_assert (opened_brackets[struct_depth + dict_entry_depth - 1] == '\0'); + opened_brackets[struct_depth + dict_entry_depth - 1] = DBUS_DICT_ENTRY_BEGIN_CHAR; + break; + + case DBUS_DICT_ENTRY_END_CHAR: + if (dict_entry_depth == 0) + { + result = DBUS_INVALID_DICT_ENTRY_ENDED_BUT_NOT_STARTED; + goto out; + } + + _dbus_assert (struct_depth + dict_entry_depth >= 1); + _dbus_assert (struct_depth + dict_entry_depth < _DBUS_N_ELEMENTS (opened_brackets)); + last_bracket = opened_brackets[struct_depth + dict_entry_depth - 1]; + + if (last_bracket != DBUS_DICT_ENTRY_BEGIN_CHAR) + { + result = DBUS_INVALID_DICT_ENTRY_ENDED_BUT_NOT_STARTED; + goto out; + } + + dict_entry_depth -= 1; + opened_brackets[struct_depth + dict_entry_depth] = '\0'; + + element_count = + _DBUS_POINTER_TO_INT (_dbus_list_pop_last (&element_count_stack)); + + if (element_count != 2) + { + if (element_count == 0) + result = DBUS_INVALID_DICT_ENTRY_HAS_NO_FIELDS; + else if (element_count == 1) + result = DBUS_INVALID_DICT_ENTRY_HAS_ONLY_ONE_FIELD; + else + result = DBUS_INVALID_DICT_ENTRY_HAS_TOO_MANY_FIELDS; + + goto out; + } + break; + + case DBUS_TYPE_STRUCT: /* doesn't appear in signatures */ + case DBUS_TYPE_DICT_ENTRY: /* ditto */ + default: + result = DBUS_INVALID_UNKNOWN_TYPECODE; + goto out; + } + + if (*p != DBUS_TYPE_ARRAY && + *p != DBUS_DICT_ENTRY_BEGIN_CHAR && + *p != DBUS_STRUCT_BEGIN_CHAR) + { + element_count = + _DBUS_POINTER_TO_INT (_dbus_list_pop_last (&element_count_stack)); + + ++element_count; + + if (!_dbus_list_append (&element_count_stack, + _DBUS_INT_TO_POINTER (element_count))) + { + result = DBUS_VALIDITY_UNKNOWN_OOM_ERROR; + goto out; + } + } + + if (array_depth > 0) + { + if (*p == DBUS_TYPE_ARRAY && p != end) + { + const unsigned char *p1; + p1 = p + 1; + if (*p1 == DBUS_STRUCT_END_CHAR || + *p1 == DBUS_DICT_ENTRY_END_CHAR) + { + result = DBUS_INVALID_MISSING_ARRAY_ELEMENT_TYPE; + goto out; + } + } + else + { + array_depth = 0; + } + } + + if (last == DBUS_DICT_ENTRY_BEGIN_CHAR) + { + if (!(dbus_type_is_valid (*p) && dbus_type_is_basic (*p))) + { + result = DBUS_INVALID_DICT_KEY_MUST_BE_BASIC_TYPE; + goto out; + } + } + + last = *p; + ++p; + } + + + if (array_depth > 0) + { + result = DBUS_INVALID_MISSING_ARRAY_ELEMENT_TYPE; + goto out; + } + + if (struct_depth > 0) + { + result = DBUS_INVALID_STRUCT_STARTED_BUT_NOT_ENDED; + goto out; + } + + if (dict_entry_depth > 0) + { + result = DBUS_INVALID_DICT_ENTRY_STARTED_BUT_NOT_ENDED; + goto out; + } + + _dbus_assert (last != DBUS_TYPE_ARRAY); + _dbus_assert (last != DBUS_STRUCT_BEGIN_CHAR); + _dbus_assert (last != DBUS_DICT_ENTRY_BEGIN_CHAR); + + result = DBUS_VALID; + +out: + _dbus_list_clear (&element_count_stack); + return result; +} + +/* note: this function is also used to validate the header's values, + * since the header is a valid body with a particular signature. + */ +static DBusValidity +validate_body_helper (DBusTypeReader *reader, + int byte_order, + dbus_bool_t walk_reader_to_end, + int total_depth, + const unsigned char *p, + const unsigned char *end, + const unsigned char **new_p) +{ + int current_type; + + /* The spec allows arrays and structs to each nest 32, for total + * nesting of 2*32. We want to impose the same limit on "dynamic" + * value nesting (not visible in the signature) which is introduced + * by DBUS_TYPE_VARIANT. + */ + if (total_depth > (DBUS_MAXIMUM_TYPE_RECURSION_DEPTH * 2)) + { + return DBUS_INVALID_NESTED_TOO_DEEPLY; + } + + while ((current_type = _dbus_type_reader_get_current_type (reader)) != DBUS_TYPE_INVALID) + { + const unsigned char *a; + int alignment; + +#if 0 + _dbus_verbose (" validating value of type %s type reader %p type_pos %d p %p end %p %d remain\n", + _dbus_type_to_string (current_type), reader, reader->type_pos, p, end, + (int) (end - p)); +#endif + + /* Guarantee that p has one byte to look at */ + if (p == end) + return DBUS_INVALID_NOT_ENOUGH_DATA; + + switch (current_type) + { + /* Special case of fixed-length types: every byte is valid */ + case DBUS_TYPE_BYTE: + ++p; + break; + + /* Multi-byte fixed-length types require padding to their alignment */ + case DBUS_TYPE_BOOLEAN: + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_UNIX_FD: + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + alignment = _dbus_type_get_alignment (current_type); + a = _DBUS_ALIGN_ADDRESS (p, alignment); + if (a >= end) + return DBUS_INVALID_NOT_ENOUGH_DATA; + while (p != a) + { + if (*p != '\0') + return DBUS_INVALID_ALIGNMENT_PADDING_NOT_NUL; + ++p; + } + + if (current_type == DBUS_TYPE_BOOLEAN) + { + dbus_uint32_t v; + + if (p + 4 > end) + return DBUS_INVALID_NOT_ENOUGH_DATA; + + v = _dbus_unpack_uint32 (byte_order, p); + + if (!(v == 0 || v == 1)) + return DBUS_INVALID_BOOLEAN_NOT_ZERO_OR_ONE; + } + + p += alignment; + break; + + /* Types that start with a 4-byte length */ + case DBUS_TYPE_ARRAY: + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + { + dbus_uint32_t claimed_len; + + a = _DBUS_ALIGN_ADDRESS (p, 4); + if (a + 4 > end) + return DBUS_INVALID_NOT_ENOUGH_DATA; + while (p != a) + { + if (*p != '\0') + return DBUS_INVALID_ALIGNMENT_PADDING_NOT_NUL; + ++p; + } + + claimed_len = _dbus_unpack_uint32 (byte_order, p); + p += 4; + + /* p may now be == end */ + _dbus_assert (p <= end); + + /* Arrays have padding between the length and the first + * array item, if it's necessary for the array's element type. + * This padding is not counted as part of the length + * claimed_len. */ + if (current_type == DBUS_TYPE_ARRAY) + { + int array_elem_type = _dbus_type_reader_get_element_type (reader); + + if (!dbus_type_is_valid (array_elem_type)) + { + return DBUS_INVALID_UNKNOWN_TYPECODE; + } + + alignment = _dbus_type_get_alignment (array_elem_type); + + a = _DBUS_ALIGN_ADDRESS (p, alignment); + + /* a may now be == end */ + if (a > end) + return DBUS_INVALID_NOT_ENOUGH_DATA; + + while (p != a) + { + if (*p != '\0') + return DBUS_INVALID_ALIGNMENT_PADDING_NOT_NUL; + ++p; + } + } + + if (claimed_len > (unsigned long) (end - p)) + return DBUS_INVALID_LENGTH_OUT_OF_BOUNDS; + + if (current_type == DBUS_TYPE_OBJECT_PATH) + { + DBusString str; + _dbus_string_init_const_len (&str, (const char *) p, claimed_len); + if (!_dbus_validate_path (&str, 0, + _dbus_string_get_length (&str))) + return DBUS_INVALID_BAD_PATH; + + p += claimed_len; + } + else if (current_type == DBUS_TYPE_STRING) + { + DBusString str; + _dbus_string_init_const_len (&str, (const char *) p, claimed_len); + if (!_dbus_string_validate_utf8 (&str, 0, + _dbus_string_get_length (&str))) + return DBUS_INVALID_BAD_UTF8_IN_STRING; + + p += claimed_len; + } + else if (current_type == DBUS_TYPE_ARRAY && claimed_len > 0) + { + DBusTypeReader sub; + DBusValidity validity; + const unsigned char *array_end; + int array_elem_type; + + if (claimed_len > DBUS_MAXIMUM_ARRAY_LENGTH) + return DBUS_INVALID_ARRAY_LENGTH_EXCEEDS_MAXIMUM; + + /* Remember that the reader is types only, so we can't + * use it to iterate over elements. It stays the same + * for all elements. + */ + _dbus_type_reader_recurse (reader, &sub); + + array_end = p + claimed_len; + /* We effectively already checked this, by checking that + * claimed_len <= (end - p) */ + _dbus_assert (array_end <= end); + + array_elem_type = _dbus_type_reader_get_element_type (reader); + + /* avoid recursive call to validate_body_helper if this is an array + * of fixed-size elements + */ + if (dbus_type_is_fixed (array_elem_type)) + { + /* Note that fixed-size types all have sizes equal to + * their alignments, so this is really the item size. */ + alignment = _dbus_type_get_alignment (array_elem_type); + _dbus_assert (alignment == 1 || alignment == 2 || + alignment == 4 || alignment == 8); + + /* Because the alignment is a power of 2, this is + * equivalent to: (claimed_len % alignment) != 0, + * but avoids slower integer division */ + if ((claimed_len & (alignment - 1)) != 0) + return DBUS_INVALID_ARRAY_LENGTH_INCORRECT; + + /* bools need to be handled differently, because they can + * have an invalid value + */ + if (array_elem_type == DBUS_TYPE_BOOLEAN) + { + dbus_uint32_t v; + + while (p < array_end) + { + v = _dbus_unpack_uint32 (byte_order, p); + + if (!(v == 0 || v == 1)) + return DBUS_INVALID_BOOLEAN_NOT_ZERO_OR_ONE; + + p += alignment; + } + } + + else + { + p = array_end; + } + } + + else + { + while (p < array_end) + { + validity = validate_body_helper (&sub, byte_order, FALSE, + total_depth + 1, + p, end, &p); + if (validity != DBUS_VALID) + return validity; + } + } + + if (p != array_end) + return DBUS_INVALID_ARRAY_LENGTH_INCORRECT; + } + + /* check nul termination */ + if (current_type != DBUS_TYPE_ARRAY) + { + if (p == end) + return DBUS_INVALID_NOT_ENOUGH_DATA; + + if (*p != '\0') + return DBUS_INVALID_STRING_MISSING_NUL; + ++p; + } + } + break; + + case DBUS_TYPE_SIGNATURE: + { + dbus_uint32_t claimed_len; + DBusString str; + DBusValidity validity; + + claimed_len = *p; + ++p; + + /* 1 is for nul termination */ + if (claimed_len + 1 > (unsigned long) (end - p)) + return DBUS_INVALID_SIGNATURE_LENGTH_OUT_OF_BOUNDS; + + _dbus_string_init_const_len (&str, (const char *) p, claimed_len); + validity = + _dbus_validate_signature_with_reason (&str, 0, + _dbus_string_get_length (&str)); + + if (validity != DBUS_VALID) + return validity; + + p += claimed_len; + + _dbus_assert (p < end); + if (*p != DBUS_TYPE_INVALID) + return DBUS_INVALID_SIGNATURE_MISSING_NUL; + + ++p; + + _dbus_verbose ("p = %p end = %p claimed_len %u\n", p, end, claimed_len); + } + break; + + case DBUS_TYPE_VARIANT: + { + /* 1 byte sig len, sig typecodes, align to + * contained-type-boundary, values. + */ + + /* In addition to normal signature validation, we need to be sure + * the signature contains only a single (possibly container) type. + */ + dbus_uint32_t claimed_len; + DBusString sig; + DBusTypeReader sub; + DBusValidity validity; + int contained_alignment; + int contained_type; + DBusValidity reason; + + claimed_len = *p; + ++p; + + /* + 1 for nul */ + if (claimed_len + 1 > (unsigned long) (end - p)) + return DBUS_INVALID_VARIANT_SIGNATURE_LENGTH_OUT_OF_BOUNDS; + + _dbus_string_init_const_len (&sig, (const char *) p, claimed_len); + reason = _dbus_validate_signature_with_reason (&sig, 0, + _dbus_string_get_length (&sig)); + if (!(reason == DBUS_VALID)) + { + if (reason == DBUS_VALIDITY_UNKNOWN_OOM_ERROR) + return reason; + else + return DBUS_INVALID_VARIANT_SIGNATURE_BAD; + } + + p += claimed_len; + + if (*p != DBUS_TYPE_INVALID) + return DBUS_INVALID_VARIANT_SIGNATURE_MISSING_NUL; + ++p; + + contained_type = _dbus_first_type_in_signature (&sig, 0); + if (contained_type == DBUS_TYPE_INVALID) + return DBUS_INVALID_VARIANT_SIGNATURE_EMPTY; + + contained_alignment = _dbus_type_get_alignment (contained_type); + + a = _DBUS_ALIGN_ADDRESS (p, contained_alignment); + if (a > end) + return DBUS_INVALID_NOT_ENOUGH_DATA; + while (p != a) + { + if (*p != '\0') + return DBUS_INVALID_ALIGNMENT_PADDING_NOT_NUL; + ++p; + } + + _dbus_type_reader_init_types_only (&sub, &sig, 0); + + _dbus_assert (_dbus_type_reader_get_current_type (&sub) != DBUS_TYPE_INVALID); + + validity = validate_body_helper (&sub, byte_order, FALSE, + total_depth + 1, + p, end, &p); + if (validity != DBUS_VALID) + return validity; + + if (_dbus_type_reader_next (&sub)) + return DBUS_INVALID_VARIANT_SIGNATURE_SPECIFIES_MULTIPLE_VALUES; + + _dbus_assert (_dbus_type_reader_get_current_type (&sub) == DBUS_TYPE_INVALID); + } + break; + + case DBUS_TYPE_DICT_ENTRY: + case DBUS_TYPE_STRUCT: + { + DBusTypeReader sub; + DBusValidity validity; + + a = _DBUS_ALIGN_ADDRESS (p, 8); + if (a > end) + return DBUS_INVALID_NOT_ENOUGH_DATA; + while (p != a) + { + if (*p != '\0') + return DBUS_INVALID_ALIGNMENT_PADDING_NOT_NUL; + ++p; + } + + _dbus_type_reader_recurse (reader, &sub); + + validity = validate_body_helper (&sub, byte_order, TRUE, + total_depth + 1, + p, end, &p); + if (validity != DBUS_VALID) + return validity; + } + break; + + default: + _dbus_assert_not_reached ("invalid typecode in supposedly-validated signature"); + break; + } + +#if 0 + _dbus_verbose (" validated value of type %s type reader %p type_pos %d p %p end %p %d remain\n", + _dbus_type_to_string (current_type), reader, reader->type_pos, p, end, + (int) (end - p)); +#endif + + if (p > end) + { + _dbus_verbose ("not enough data!!! p = %p end = %p end-p = %d\n", + p, end, (int) (end - p)); + return DBUS_INVALID_NOT_ENOUGH_DATA; + } + + if (walk_reader_to_end) + _dbus_type_reader_next (reader); + else + break; + } + + if (new_p) + *new_p = p; + + return DBUS_VALID; +} + +/** + * Verifies that the range of value_str from value_pos to value_end is + * a legitimate value of type expected_signature. If this function + * returns #TRUE, it will be safe to iterate over the values with + * #DBusTypeReader. The signature is assumed to be already valid. + * + * If bytes_remaining is not #NULL, then leftover bytes will be stored + * there and #DBUS_VALID returned. If it is #NULL, then + * #DBUS_INVALID_TOO_MUCH_DATA will be returned if bytes are left + * over. + * + * @param expected_signature the expected types in the value_str + * @param expected_signature_start where in expected_signature is the signature + * @param byte_order the byte order + * @param bytes_remaining place to store leftover bytes + * @param value_str the string containing the body + * @param value_pos where the values start + * @param len length of values after value_pos + * @returns #DBUS_VALID if valid, reason why invalid otherwise + */ +DBusValidity +_dbus_validate_body_with_reason (const DBusString *expected_signature, + int expected_signature_start, + int byte_order, + int *bytes_remaining, + const DBusString *value_str, + int value_pos, + int len) +{ + DBusTypeReader reader; + const unsigned char *p; + const unsigned char *end; + DBusValidity validity; + + _dbus_assert (len >= 0); + _dbus_assert (value_pos >= 0); + _dbus_assert (value_pos <= _dbus_string_get_length (value_str) - len); + + _dbus_verbose ("validating body from pos %d len %d sig '%s'\n", + value_pos, len, _dbus_string_get_const_data_len (expected_signature, + expected_signature_start, + 0)); + + _dbus_type_reader_init_types_only (&reader, + expected_signature, expected_signature_start); + + p = _dbus_string_get_const_udata_len (value_str, value_pos, len); + end = p + len; + + validity = validate_body_helper (&reader, byte_order, TRUE, 0, p, end, &p); + if (validity != DBUS_VALID) + return validity; + + if (bytes_remaining) + { + *bytes_remaining = end - p; + return DBUS_VALID; + } + else if (p < end) + return DBUS_INVALID_TOO_MUCH_DATA; + else + { + _dbus_assert (p == end); + return DBUS_VALID; + } +} + +/** + * Determine wether the given character is valid as the first character + * in a name. + */ +#define VALID_INITIAL_NAME_CHARACTER(c) \ + ( ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + ((c) == '_') ) + +/** + * Determine wether the given character is valid as a second or later + * character in a name + */ +#define VALID_NAME_CHARACTER(c) \ + ( ((c) >= '0' && (c) <= '9') || \ + ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + ((c) == '_') ) + +/** + * Checks that the given range of the string is a valid object path + * name in the D-Bus protocol. Part of the validation ensures that + * the object path contains only ASCII. + * + * @todo this is inconsistent with most of DBusString in that + * it allows a start,len range that extends past the string end. + * + * @todo change spec to disallow more things, such as spaces in the + * path name + * + * @param str the string + * @param start first byte index to check + * @param len number of bytes to check + * @returns #TRUE if the byte range exists and is a valid name + */ +dbus_bool_t +_dbus_validate_path (const DBusString *str, + int start, + int len) +{ + const unsigned char *s; + const unsigned char *end; + const unsigned char *last_slash; + + _dbus_assert (start >= 0); + _dbus_assert (len >= 0); + _dbus_assert (start <= _dbus_string_get_length (str)); + + if (len > _dbus_string_get_length (str) - start) + return FALSE; + + if (len == 0) + return FALSE; + + s = _dbus_string_get_const_udata (str) + start; + end = s + len; + + if (*s != '/') + return FALSE; + last_slash = s; + ++s; + + while (s != end) + { + if (*s == '/') + { + if ((s - last_slash) < 2) + return FALSE; /* no empty path components allowed */ + + last_slash = s; + } + else + { + if (_DBUS_UNLIKELY (!VALID_NAME_CHARACTER (*s))) + return FALSE; + } + + ++s; + } + + if ((end - last_slash) < 2 && + len > 1) + return FALSE; /* trailing slash not allowed unless the string is "/" */ + + return TRUE; +} + +const char * +_dbus_validity_to_error_message (DBusValidity validity) +{ + switch (validity) + { + case DBUS_VALIDITY_UNKNOWN_OOM_ERROR: return "Out of memory"; + case DBUS_INVALID_FOR_UNKNOWN_REASON: return "Unknown reason"; + case DBUS_VALID_BUT_INCOMPLETE: return "Valid but incomplete"; + case DBUS_VALIDITY_UNKNOWN: return "Validity unknown"; + case DBUS_VALID: return "Valid"; + case DBUS_INVALID_UNKNOWN_TYPECODE: return "Unknown typecode"; + case DBUS_INVALID_MISSING_ARRAY_ELEMENT_TYPE: return "Missing array element type"; + case DBUS_INVALID_SIGNATURE_TOO_LONG: return "Signature is too long"; + case DBUS_INVALID_EXCEEDED_MAXIMUM_ARRAY_RECURSION: return "Exceeded maximum array recursion"; + case DBUS_INVALID_EXCEEDED_MAXIMUM_STRUCT_RECURSION: return "Exceeded maximum struct recursion"; + case DBUS_INVALID_STRUCT_ENDED_BUT_NOT_STARTED: return "Struct ended but not started"; + case DBUS_INVALID_STRUCT_STARTED_BUT_NOT_ENDED: return "Struct started but not ended"; + case DBUS_INVALID_STRUCT_HAS_NO_FIELDS: return "Struct has no fields"; + case DBUS_INVALID_ALIGNMENT_PADDING_NOT_NUL: return "Alignment padding not null"; + case DBUS_INVALID_BOOLEAN_NOT_ZERO_OR_ONE: return "Boolean is not zero or one"; + case DBUS_INVALID_NOT_ENOUGH_DATA: return "Not enough data"; + case DBUS_INVALID_TOO_MUCH_DATA: return "Too much data"; + case DBUS_INVALID_BAD_BYTE_ORDER: return "Bad byte order"; + case DBUS_INVALID_BAD_PROTOCOL_VERSION: return "Bad protocol version"; + case DBUS_INVALID_BAD_MESSAGE_TYPE: return "Bad message type"; + case DBUS_INVALID_BAD_SERIAL: return "Bad serial"; + case DBUS_INVALID_INSANE_FIELDS_ARRAY_LENGTH: return "Insane fields array length"; + case DBUS_INVALID_INSANE_BODY_LENGTH: return "Insane body length"; + case DBUS_INVALID_MESSAGE_TOO_LONG: return "Message too long"; + case DBUS_INVALID_HEADER_FIELD_CODE: return "Header field code"; + case DBUS_INVALID_HEADER_FIELD_HAS_WRONG_TYPE: return "Header field has wrong type"; + case DBUS_INVALID_USES_LOCAL_INTERFACE: return "Uses local interface"; + case DBUS_INVALID_USES_LOCAL_PATH: return "Uses local path"; + case DBUS_INVALID_HEADER_FIELD_APPEARS_TWICE: return "Header field appears twice"; + case DBUS_INVALID_BAD_DESTINATION: return "Bad destination"; + case DBUS_INVALID_BAD_INTERFACE: return "Bad interface"; + case DBUS_INVALID_BAD_MEMBER: return "Bad member"; + case DBUS_INVALID_BAD_ERROR_NAME: return "Bad error name"; + case DBUS_INVALID_BAD_SENDER: return "Bad sender"; + case DBUS_INVALID_MISSING_PATH: return "Missing path"; + case DBUS_INVALID_MISSING_INTERFACE: return "Missing interface"; + case DBUS_INVALID_MISSING_MEMBER: return "Missing member"; + case DBUS_INVALID_MISSING_ERROR_NAME: return "Missing error name"; + case DBUS_INVALID_MISSING_REPLY_SERIAL: return "Missing reply serial"; + case DBUS_INVALID_LENGTH_OUT_OF_BOUNDS: return "Length out of bounds"; + case DBUS_INVALID_ARRAY_LENGTH_EXCEEDS_MAXIMUM: return "Array length exceeds maximum"; + case DBUS_INVALID_BAD_PATH: return "Bad path"; + case DBUS_INVALID_SIGNATURE_LENGTH_OUT_OF_BOUNDS: return "Signature length out of bounds"; + case DBUS_INVALID_BAD_UTF8_IN_STRING: return "Bad utf8 in string"; + case DBUS_INVALID_ARRAY_LENGTH_INCORRECT: return "Array length incorrect"; + case DBUS_INVALID_VARIANT_SIGNATURE_LENGTH_OUT_OF_BOUNDS: return "Variant signature length out of bounds"; + case DBUS_INVALID_VARIANT_SIGNATURE_BAD: return "Variant signature bad"; + case DBUS_INVALID_VARIANT_SIGNATURE_EMPTY: return "Variant signature empty"; + case DBUS_INVALID_VARIANT_SIGNATURE_SPECIFIES_MULTIPLE_VALUES: return "Variant signature specifies multiple values"; + case DBUS_INVALID_VARIANT_SIGNATURE_MISSING_NUL: return "Variant signature missing nul"; + case DBUS_INVALID_STRING_MISSING_NUL: return "String missing nul"; + case DBUS_INVALID_SIGNATURE_MISSING_NUL: return "Signature missing nul"; + case DBUS_INVALID_EXCEEDED_MAXIMUM_DICT_ENTRY_RECURSION: return "Exceeded maximum dict entry recursion"; + case DBUS_INVALID_DICT_ENTRY_ENDED_BUT_NOT_STARTED: return "Dict entry ended but not started"; + case DBUS_INVALID_DICT_ENTRY_STARTED_BUT_NOT_ENDED: return "Dict entry started but not ended"; + case DBUS_INVALID_DICT_ENTRY_HAS_NO_FIELDS: return "Dict entry has no fields"; + case DBUS_INVALID_DICT_ENTRY_HAS_ONLY_ONE_FIELD: return "Dict entry has only one field"; + case DBUS_INVALID_DICT_ENTRY_HAS_TOO_MANY_FIELDS: return "Dict entry has too many fields"; + case DBUS_INVALID_DICT_ENTRY_NOT_INSIDE_ARRAY: return "Dict entry not inside array"; + case DBUS_INVALID_DICT_KEY_MUST_BE_BASIC_TYPE: return "Dict key must be basic type"; + case DBUS_INVALID_MISSING_UNIX_FDS: return "Unix file descriptor missing"; + case DBUS_INVALID_NESTED_TOO_DEEPLY: return "Variants cannot be used to create a hugely recursive tree of values"; + case DBUS_VALIDITY_LAST: + default: + return "Invalid"; + } +} + +/** + * Checks that the given range of the string is a valid interface name + * in the D-Bus protocol. This includes a length restriction and an + * ASCII subset, see the specification. + * + * @todo this is inconsistent with most of DBusString in that + * it allows a start,len range that extends past the string end. + * + * @param str the string + * @param start first byte index to check + * @param len number of bytes to check + * @returns #TRUE if the byte range exists and is a valid name + */ +dbus_bool_t +_dbus_validate_interface (const DBusString *str, + int start, + int len) +{ + const unsigned char *s; + const unsigned char *end; + const unsigned char *iface; + const unsigned char *last_dot; + + _dbus_assert (start >= 0); + _dbus_assert (len >= 0); + _dbus_assert (start <= _dbus_string_get_length (str)); + + if (len > _dbus_string_get_length (str) - start) + return FALSE; + + if (len > DBUS_MAXIMUM_NAME_LENGTH) + return FALSE; + + if (len == 0) + return FALSE; + + last_dot = NULL; + iface = _dbus_string_get_const_udata (str) + start; + end = iface + len; + s = iface; + + /* check special cases of first char so it doesn't have to be done + * in the loop. Note we know len > 0 + */ + if (_DBUS_UNLIKELY (*s == '.')) /* disallow starting with a . */ + return FALSE; + else if (_DBUS_UNLIKELY (!VALID_INITIAL_NAME_CHARACTER (*s))) + return FALSE; + else + ++s; + + while (s != end) + { + if (*s == '.') + { + if (_DBUS_UNLIKELY ((s + 1) == end)) + return FALSE; + else if (_DBUS_UNLIKELY (!VALID_INITIAL_NAME_CHARACTER (*(s + 1)))) + return FALSE; + last_dot = s; + ++s; /* we just validated the next char, so skip two */ + } + else if (_DBUS_UNLIKELY (!VALID_NAME_CHARACTER (*s))) + { + return FALSE; + } + + ++s; + } + + if (_DBUS_UNLIKELY (last_dot == NULL)) + return FALSE; + + return TRUE; +} + +/** + * Checks that the given range of the string is a valid member name + * in the D-Bus protocol. This includes a length restriction, etc., + * see the specification. + * + * @todo this is inconsistent with most of DBusString in that + * it allows a start,len range that extends past the string end. + * + * @param str the string + * @param start first byte index to check + * @param len number of bytes to check + * @returns #TRUE if the byte range exists and is a valid name + */ +dbus_bool_t +_dbus_validate_member (const DBusString *str, + int start, + int len) +{ + const unsigned char *s; + const unsigned char *end; + const unsigned char *member; + + _dbus_assert (start >= 0); + _dbus_assert (len >= 0); + _dbus_assert (start <= _dbus_string_get_length (str)); + + if (len > _dbus_string_get_length (str) - start) + return FALSE; + + if (len > DBUS_MAXIMUM_NAME_LENGTH) + return FALSE; + + if (len == 0) + return FALSE; + + member = _dbus_string_get_const_udata (str) + start; + end = member + len; + s = member; + + /* check special cases of first char so it doesn't have to be done + * in the loop. Note we know len > 0 + */ + + if (_DBUS_UNLIKELY (!VALID_INITIAL_NAME_CHARACTER (*s))) + return FALSE; + else + ++s; + + while (s != end) + { + if (_DBUS_UNLIKELY (!VALID_NAME_CHARACTER (*s))) + { + return FALSE; + } + + ++s; + } + + return TRUE; +} + +/** + * Checks that the given range of the string is a valid error name + * in the D-Bus protocol. This includes a length restriction, etc., + * see the specification. + * + * @todo this is inconsistent with most of DBusString in that + * it allows a start,len range that extends past the string end. + * + * @param str the string + * @param start first byte index to check + * @param len number of bytes to check + * @returns #TRUE if the byte range exists and is a valid name + */ +dbus_bool_t +_dbus_validate_error_name (const DBusString *str, + int start, + int len) +{ + /* Same restrictions as interface name at the moment */ + return _dbus_validate_interface (str, start, len); +} + +/** + * Determine wether the given character is valid as the first character + * in a bus name. + */ +#define VALID_INITIAL_BUS_NAME_CHARACTER(c) \ + ( ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + ((c) == '_') || ((c) == '-')) + +/** + * Determine wether the given character is valid as a second or later + * character in a bus name + */ +#define VALID_BUS_NAME_CHARACTER(c) \ + ( ((c) >= '0' && (c) <= '9') || \ + ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + ((c) == '_') || ((c) == '-')) + +static dbus_bool_t +_dbus_validate_bus_name_full (const DBusString *str, + int start, + int len, + dbus_bool_t is_namespace) +{ + const unsigned char *s; + const unsigned char *end; + const unsigned char *iface; + const unsigned char *last_dot; + + _dbus_assert (start >= 0); + _dbus_assert (len >= 0); + _dbus_assert (start <= _dbus_string_get_length (str)); + + if (len > _dbus_string_get_length (str) - start) + return FALSE; + + if (len > DBUS_MAXIMUM_NAME_LENGTH) + return FALSE; + + if (len == 0) + return FALSE; + + last_dot = NULL; + iface = _dbus_string_get_const_udata (str) + start; + end = iface + len; + s = iface; + + /* check special cases of first char so it doesn't have to be done + * in the loop. Note we know len > 0 + */ + if (*s == ':') + { + /* unique name */ + ++s; + while (s != end) + { + if (*s == '.') + { + if (_DBUS_UNLIKELY ((s + 1) == end)) + return FALSE; + if (_DBUS_UNLIKELY (!VALID_BUS_NAME_CHARACTER (*(s + 1)))) + return FALSE; + ++s; /* we just validated the next char, so skip two */ + } + else if (_DBUS_UNLIKELY (!VALID_BUS_NAME_CHARACTER (*s))) + { + return FALSE; + } + + ++s; + } + + return TRUE; + } + else if (_DBUS_UNLIKELY (*s == '.')) /* disallow starting with a . */ + return FALSE; + else if (_DBUS_UNLIKELY (!VALID_INITIAL_BUS_NAME_CHARACTER (*s))) + return FALSE; + else + ++s; + + while (s != end) + { + if (*s == '.') + { + if (_DBUS_UNLIKELY ((s + 1) == end)) + return FALSE; + else if (_DBUS_UNLIKELY (!VALID_INITIAL_BUS_NAME_CHARACTER (*(s + 1)))) + return FALSE; + last_dot = s; + ++s; /* we just validated the next char, so skip two */ + } + else if (_DBUS_UNLIKELY (!VALID_BUS_NAME_CHARACTER (*s))) + { + return FALSE; + } + + ++s; + } + + if (!is_namespace && _DBUS_UNLIKELY (last_dot == NULL)) + return FALSE; + + return TRUE; +} + +/** + * Checks that the given range of the string is a valid bus name in + * the D-Bus protocol. This includes a length restriction, etc., see + * the specification. + * + * @todo this is inconsistent with most of DBusString in that + * it allows a start,len range that extends past the string end. + * + * @param str the string + * @param start first byte index to check + * @param len number of bytes to check + * @returns #TRUE if the byte range exists and is a valid name + */ +dbus_bool_t +_dbus_validate_bus_name (const DBusString *str, + int start, + int len) +{ + return _dbus_validate_bus_name_full (str, start, len, FALSE); +} + +/** + * Checks that the given range of the string is a prefix of a valid bus name in + * the D-Bus protocol. Unlike _dbus_validate_bus_name(), this accepts strings + * with only one period-separated component. + * + * @todo this is inconsistent with most of DBusString in that + * it allows a start,len range that extends past the string end. + * + * @param str the string + * @param start first byte index to check + * @param len number of bytes to check + * @returns #TRUE if the byte range exists and is a valid name + */ +dbus_bool_t +_dbus_validate_bus_namespace (const DBusString *str, + int start, + int len) +{ + return _dbus_validate_bus_name_full (str, start, len, TRUE); +} + +/** define _dbus_check_is_valid_path() */ +DEFINE_DBUS_NAME_CHECK(path) +/** define _dbus_check_is_valid_interface() */ +DEFINE_DBUS_NAME_CHECK(interface) +/** define _dbus_check_is_valid_member() */ +DEFINE_DBUS_NAME_CHECK(member) +/** define _dbus_check_is_valid_error_name() */ +DEFINE_DBUS_NAME_CHECK(error_name) +/** define _dbus_check_is_valid_bus_name() */ +DEFINE_DBUS_NAME_CHECK(bus_name) +/** define _dbus_check_is_valid_utf8() */ +DEFINE_DBUS_NAME_CHECK(utf8) + +/** @} */ + +/* tests in dbus-marshal-validate-util.c */ diff --git a/src/3rdparty/libdbus/dbus/dbus-marshal-validate.h b/src/3rdparty/libdbus/dbus/dbus-marshal-validate.h new file mode 100644 index 00000000..94737872 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-marshal-validate.h @@ -0,0 +1,213 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-marshal-validate.h Validation routines for marshaled data + * + * Copyright (C) 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_MARSHAL_VALIDATE_H +#define DBUS_MARSHAL_VALIDATE_H + +#include <dbus/dbus-string.h> + +/** + * @addtogroup DBusMarshal + * + * @{ + */ + +/** + * This is used rather than a bool for high visibility + */ +typedef enum +{ + DBUS_VALIDATION_MODE_WE_TRUST_THIS_DATA_ABSOLUTELY, + DBUS_VALIDATION_MODE_DATA_IS_UNTRUSTED +} DBusValidationMode; + +/** + * This is primarily used in unit testing, so we can verify that each + * invalid message is invalid for the expected reasons. Thus we really + * want a distinct enum value for every codepath leaving the validator + * functions. Enum values are specified manually for ease of debugging + * (so you can see the enum value given a printf) + */ +typedef enum +{ +#define _DBUS_NEGATIVE_VALIDITY_COUNT 4 + DBUS_VALIDITY_UNKNOWN_OOM_ERROR = -4, /**< can't determine validity due to OOM */ + DBUS_INVALID_FOR_UNKNOWN_REASON = -3, + DBUS_VALID_BUT_INCOMPLETE = -2, + DBUS_VALIDITY_UNKNOWN = -1, + DBUS_VALID = 0, /**< the data is valid */ + DBUS_INVALID_UNKNOWN_TYPECODE = 1, + DBUS_INVALID_MISSING_ARRAY_ELEMENT_TYPE = 2, + DBUS_INVALID_SIGNATURE_TOO_LONG = 3, /* this one is impossible right now since + * you can't put a too-long value in a byte + */ + DBUS_INVALID_EXCEEDED_MAXIMUM_ARRAY_RECURSION = 4, + DBUS_INVALID_EXCEEDED_MAXIMUM_STRUCT_RECURSION = 5, + DBUS_INVALID_STRUCT_ENDED_BUT_NOT_STARTED = 6, + DBUS_INVALID_STRUCT_STARTED_BUT_NOT_ENDED = 7, + DBUS_INVALID_STRUCT_HAS_NO_FIELDS = 8, + DBUS_INVALID_ALIGNMENT_PADDING_NOT_NUL = 9, + DBUS_INVALID_BOOLEAN_NOT_ZERO_OR_ONE = 10, + DBUS_INVALID_NOT_ENOUGH_DATA = 11, + DBUS_INVALID_TOO_MUCH_DATA = 12, /**< trailing junk makes it invalid */ + DBUS_INVALID_BAD_BYTE_ORDER = 13, + DBUS_INVALID_BAD_PROTOCOL_VERSION = 14, + DBUS_INVALID_BAD_MESSAGE_TYPE = 15, + DBUS_INVALID_BAD_SERIAL = 16, + DBUS_INVALID_INSANE_FIELDS_ARRAY_LENGTH = 17, + DBUS_INVALID_INSANE_BODY_LENGTH = 18, + DBUS_INVALID_MESSAGE_TOO_LONG = 19, + DBUS_INVALID_HEADER_FIELD_CODE = 20, + DBUS_INVALID_HEADER_FIELD_HAS_WRONG_TYPE = 21, + DBUS_INVALID_USES_LOCAL_INTERFACE = 22, + DBUS_INVALID_USES_LOCAL_PATH = 23, + DBUS_INVALID_HEADER_FIELD_APPEARS_TWICE = 24, + DBUS_INVALID_BAD_DESTINATION = 25, + DBUS_INVALID_BAD_INTERFACE = 26, + DBUS_INVALID_BAD_MEMBER = 27, + DBUS_INVALID_BAD_ERROR_NAME = 28, + DBUS_INVALID_BAD_SENDER = 29, + DBUS_INVALID_MISSING_PATH = 30, + DBUS_INVALID_MISSING_INTERFACE = 31, + DBUS_INVALID_MISSING_MEMBER = 32, + DBUS_INVALID_MISSING_ERROR_NAME = 33, + DBUS_INVALID_MISSING_REPLY_SERIAL = 34, + DBUS_INVALID_LENGTH_OUT_OF_BOUNDS = 35, + DBUS_INVALID_ARRAY_LENGTH_EXCEEDS_MAXIMUM = 36, + DBUS_INVALID_BAD_PATH = 37, + DBUS_INVALID_SIGNATURE_LENGTH_OUT_OF_BOUNDS = 38, + DBUS_INVALID_BAD_UTF8_IN_STRING = 39, + DBUS_INVALID_ARRAY_LENGTH_INCORRECT = 40, + DBUS_INVALID_VARIANT_SIGNATURE_LENGTH_OUT_OF_BOUNDS = 41, + DBUS_INVALID_VARIANT_SIGNATURE_BAD = 42, + DBUS_INVALID_VARIANT_SIGNATURE_EMPTY = 43, + DBUS_INVALID_VARIANT_SIGNATURE_SPECIFIES_MULTIPLE_VALUES = 44, + DBUS_INVALID_VARIANT_SIGNATURE_MISSING_NUL = 45, + DBUS_INVALID_STRING_MISSING_NUL = 46, + DBUS_INVALID_SIGNATURE_MISSING_NUL = 47, + DBUS_INVALID_EXCEEDED_MAXIMUM_DICT_ENTRY_RECURSION = 48, + DBUS_INVALID_DICT_ENTRY_ENDED_BUT_NOT_STARTED = 49, + DBUS_INVALID_DICT_ENTRY_STARTED_BUT_NOT_ENDED = 50, + DBUS_INVALID_DICT_ENTRY_HAS_NO_FIELDS = 51, + DBUS_INVALID_DICT_ENTRY_HAS_ONLY_ONE_FIELD = 52, + DBUS_INVALID_DICT_ENTRY_HAS_TOO_MANY_FIELDS = 53, + DBUS_INVALID_DICT_ENTRY_NOT_INSIDE_ARRAY = 54, + DBUS_INVALID_DICT_KEY_MUST_BE_BASIC_TYPE = 55, + DBUS_INVALID_MISSING_UNIX_FDS = 56, + DBUS_INVALID_NESTED_TOO_DEEPLY = 57, + DBUS_VALIDITY_LAST +} DBusValidity; + +DBUS_PRIVATE_EXPORT +DBusValidity _dbus_validate_signature_with_reason (const DBusString *type_str, + int type_pos, + int len); +DBUS_PRIVATE_EXPORT +DBusValidity _dbus_validate_body_with_reason (const DBusString *expected_signature, + int expected_signature_start, + int byte_order, + int *bytes_remaining, + const DBusString *value_str, + int value_pos, + int len); + +const char *_dbus_validity_to_error_message (DBusValidity validity); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_validate_path (const DBusString *str, + int start, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_validate_interface (const DBusString *str, + int start, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_validate_member (const DBusString *str, + int start, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_validate_error_name (const DBusString *str, + int start, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_validate_bus_name (const DBusString *str, + int start, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_validate_bus_namespace (const DBusString *str, + int start, + int len); +/* just to have a name consistent with the above: */ +#define _dbus_validate_utf8(s,b,e) _dbus_string_validate_utf8 (s, b, e) + +#ifdef DBUS_DISABLE_CHECKS + +/* Be sure they don't exist, since we don't want to use them outside of checks + * and so we want the compile failure. + */ +#define DECLARE_DBUS_NAME_CHECK(what) +#define DEFINE_DBUS_NAME_CHECK(what) + +#else /* !DBUS_DISABLE_CHECKS */ + +/** A name check is used in _dbus_return_if_fail(), it's not suitable + * for validating untrusted data. use _dbus_validate_whatever for that. + */ +#define DECLARE_DBUS_NAME_CHECK(what) \ +dbus_bool_t _dbus_check_is_valid_##what (const char *name) + +/** Define a name check to be used in _dbus_return_if_fail() statements. + */ +#define DEFINE_DBUS_NAME_CHECK(what) \ +dbus_bool_t \ +_dbus_check_is_valid_##what (const char *name) \ +{ \ + DBusString str; \ + \ + if (name == NULL) \ + return FALSE; \ + \ + _dbus_string_init_const (&str, name); \ + return _dbus_validate_##what (&str, 0, \ + _dbus_string_get_length (&str)); \ +} +#endif /* !DBUS_DISABLE_CHECKS */ + +/** defines _dbus_check_is_valid_path() */ +DECLARE_DBUS_NAME_CHECK(path); +/** defines _dbus_check_is_valid_interface() */ +DECLARE_DBUS_NAME_CHECK(interface); +/** defines _dbus_check_is_valid_member() */ +DECLARE_DBUS_NAME_CHECK(member); +/** defines _dbus_check_is_valid_error_name() */ +DECLARE_DBUS_NAME_CHECK(error_name); +/** defines _dbus_check_is_valid_bus_name() */ +DECLARE_DBUS_NAME_CHECK(bus_name); +/** defines _dbus_check_is_valid_utf8() */ +DECLARE_DBUS_NAME_CHECK(utf8); + +/** @} */ + +#endif /* DBUS_MARSHAL_VALIDATE_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-memory.c b/src/3rdparty/libdbus/dbus/dbus-memory.c new file mode 100644 index 00000000..be7e4ef6 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-memory.c @@ -0,0 +1,952 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-memory.c D-Bus memory handling + * + * Copyright (C) 2002, 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-memory.h" +#include "dbus-internals.h" +#include "dbus-sysdeps.h" +#include "dbus-list.h" +#include "dbus-threads.h" +#include <dbus/dbus-test-tap.h> +#include <stdlib.h> + +/** + * @defgroup DBusMemory Memory Allocation + * @ingroup DBus + * @brief dbus_malloc(), dbus_free(), etc. + * + * Functions and macros related to allocating and releasing + * blocks of memory. + * + */ + +/** + * @defgroup DBusMemoryInternals Memory allocation implementation details + * @ingroup DBusInternals + * @brief internals of dbus_malloc() etc. + * + * Implementation details related to allocating and releasing blocks + * of memory. + */ + +/** + * @addtogroup DBusMemory + * + * @{ + */ + +/** + * @def dbus_new + * + * Safe macro for using dbus_malloc(). Accepts the type + * to allocate and the number of type instances to + * allocate as arguments, and returns a memory block + * cast to the desired type, instead of as a void*. + * + * @param type type name to allocate + * @param count number of instances in the allocated array + * @returns the new memory block or #NULL on failure + */ + +/** + * @def dbus_new0 + * + * Safe macro for using dbus_malloc0(). Accepts the type + * to allocate and the number of type instances to + * allocate as arguments, and returns a memory block + * cast to the desired type, instead of as a void*. + * The allocated array is initialized to all-bits-zero. + * + * @param type type name to allocate + * @param count number of instances in the allocated array + * @returns the new memory block or #NULL on failure + */ + +/** + * @typedef DBusFreeFunction + * + * The type of a function which frees a block of memory. + * + * @param memory the memory to free + */ + +/** @} */ /* end of public API docs */ + +/** + * @addtogroup DBusMemoryInternals + * + * @{ + */ + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +/* Test-only, does not need to be thread-safe */ +static dbus_bool_t debug_initialized = FALSE; +static int fail_nth = -1; +static size_t fail_size = 0; +static int fail_alloc_counter = _DBUS_INT_MAX; +static int n_failures_per_failure = 1; +static int n_failures_this_failure = 0; +static dbus_bool_t guards = FALSE; +static dbus_bool_t disable_mem_pools = FALSE; +static dbus_bool_t backtrace_on_fail_alloc = FALSE; +static dbus_bool_t malloc_cannot_fail = FALSE; +static DBusAtomic n_blocks_outstanding = {0}; + +/** value stored in guard padding for debugging buffer overrun */ +#define GUARD_VALUE 0xdeadbeef +/** size of the information about the block stored in guard mode */ +#define GUARD_INFO_SIZE 8 +/** size of the GUARD_VALUE-filled padding after the header info */ +#define GUARD_START_PAD 16 +/** size of the GUARD_VALUE-filled padding at the end of the block */ +#define GUARD_END_PAD 16 +/** size of stuff at start of block */ +#define GUARD_START_OFFSET (GUARD_START_PAD + GUARD_INFO_SIZE) +/** total extra size over the requested allocation for guard stuff */ +#define GUARD_EXTRA_SIZE (GUARD_START_OFFSET + GUARD_END_PAD) + +static void +_dbus_initialize_malloc_debug (void) +{ + if (!debug_initialized) + { + debug_initialized = TRUE; + + if (_dbus_getenv ("DBUS_MALLOC_FAIL_NTH") != NULL) + { + fail_nth = atoi (_dbus_getenv ("DBUS_MALLOC_FAIL_NTH")); + fail_alloc_counter = fail_nth; + _dbus_verbose ("Will fail dbus_malloc every %d times\n", fail_nth); + } + + if (_dbus_getenv ("DBUS_MALLOC_FAIL_GREATER_THAN") != NULL) + { + fail_size = atoi (_dbus_getenv ("DBUS_MALLOC_FAIL_GREATER_THAN")); + _dbus_verbose ("Will fail mallocs over %ld bytes\n", + (long) fail_size); + } + + if (_dbus_getenv ("DBUS_MALLOC_GUARDS") != NULL) + { + guards = TRUE; + _dbus_verbose ("Will use dbus_malloc guards\n"); + } + + if (_dbus_getenv ("DBUS_DISABLE_MEM_POOLS") != NULL) + { + disable_mem_pools = TRUE; + _dbus_verbose ("Will disable memory pools\n"); + } + + if (_dbus_getenv ("DBUS_MALLOC_BACKTRACES") != NULL) + { + backtrace_on_fail_alloc = TRUE; + _dbus_verbose ("Will backtrace on failing a dbus_malloc\n"); + } + + if (_dbus_getenv ("DBUS_MALLOC_CANNOT_FAIL") != NULL) + { + malloc_cannot_fail = TRUE; + _dbus_verbose ("Will abort if system malloc() and friends fail\n"); + } + } +} + +/** + * Whether to turn off mem pools, useful for leak checking. + * + * @returns #TRUE if mempools should not be used. + */ +dbus_bool_t +_dbus_disable_mem_pools (void) +{ + _dbus_initialize_malloc_debug (); + return disable_mem_pools; +} + +/** + * Sets the number of allocations until we simulate a failed + * allocation. If set to 0, the next allocation to run + * fails; if set to 1, one succeeds then the next fails; etc. + * Set to _DBUS_INT_MAX to not fail anything. + * + * @param until_next_fail number of successful allocs before one fails + */ +void +_dbus_set_fail_alloc_counter (int until_next_fail) +{ + _dbus_initialize_malloc_debug (); + + fail_alloc_counter = until_next_fail; + +#if 0 + _dbus_verbose ("Set fail alloc counter = %d\n", fail_alloc_counter); +#endif +} + +/** + * Gets the number of successful allocs until we'll simulate + * a failed alloc. + * + * @returns current counter value + */ +int +_dbus_get_fail_alloc_counter (void) +{ + _dbus_initialize_malloc_debug (); + + return fail_alloc_counter; +} + +/** + * Sets how many mallocs to fail when the fail alloc counter reaches + * 0. + * + * @param failures_per_failure number to fail + */ +void +_dbus_set_fail_alloc_failures (int failures_per_failure) +{ + n_failures_per_failure = failures_per_failure; +} + +/** + * Gets the number of failures we'll have when the fail malloc + * counter reaches 0. + * + * @returns number of failures planned + */ +int +_dbus_get_fail_alloc_failures (void) +{ + return n_failures_per_failure; +} + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +/** + * Called when about to alloc some memory; if + * it returns #TRUE, then the allocation should + * fail. If it returns #FALSE, then the allocation + * should not fail. + * + * @returns #TRUE if this alloc should fail + */ +dbus_bool_t +_dbus_decrement_fail_alloc_counter (void) +{ + _dbus_initialize_malloc_debug (); + + if (fail_alloc_counter <= 0) + { + if (backtrace_on_fail_alloc) + _dbus_print_backtrace (); + + _dbus_verbose ("failure %d\n", n_failures_this_failure); + + n_failures_this_failure += 1; + if (n_failures_this_failure >= n_failures_per_failure) + { + if (fail_nth >= 0) + fail_alloc_counter = fail_nth; + else + fail_alloc_counter = _DBUS_INT_MAX; + + n_failures_this_failure = 0; + + _dbus_verbose ("reset fail alloc counter to %d\n", fail_alloc_counter); + } + + return TRUE; + } + else + { + fail_alloc_counter -= 1; + return FALSE; + } +} +#endif /* DBUS_ENABLE_EMBEDDED_TESTS */ + +/** + * Get the number of outstanding malloc()'d blocks. + * + * @returns number of blocks + */ +int +_dbus_get_malloc_blocks_outstanding (void) +{ + return _dbus_atomic_get (&n_blocks_outstanding); +} + +/** + * Where the block came from. + */ +typedef enum +{ + SOURCE_UNKNOWN, + SOURCE_MALLOC, + SOURCE_REALLOC, + SOURCE_MALLOC_ZERO, + SOURCE_REALLOC_NULL +} BlockSource; + +static const char* +source_string (BlockSource source) +{ + switch (source) + { + case SOURCE_UNKNOWN: + return "unknown"; + case SOURCE_MALLOC: + return "malloc"; + case SOURCE_REALLOC: + return "realloc"; + case SOURCE_MALLOC_ZERO: + return "malloc0"; + case SOURCE_REALLOC_NULL: + return "realloc(NULL)"; + default: + _dbus_assert_not_reached ("Invalid malloc block source ID"); + return "invalid!"; + } +} + +static void +check_guards (void *free_block, + dbus_bool_t overwrite) +{ + if (free_block != NULL) + { + unsigned char *block = ((unsigned char*)free_block) - GUARD_START_OFFSET; + size_t requested_bytes = *(dbus_uint32_t *) (void *) block; + BlockSource source = *(dbus_uint32_t *) (void *) (block + 4); + unsigned int i; + dbus_bool_t failed; + + failed = FALSE; + +#if 0 + _dbus_verbose ("Checking %d bytes request from source %s\n", + requested_bytes, source_string (source)); +#endif + + i = GUARD_INFO_SIZE; + while (i < GUARD_START_OFFSET) + { + dbus_uint32_t value = *(dbus_uint32_t *) (void *) &block[i]; + if (value != GUARD_VALUE) + { + _dbus_warn ("Block of %lu bytes from %s had start guard value 0x%ux at %d expected 0x%x", + (long) requested_bytes, source_string (source), + value, i, GUARD_VALUE); + failed = TRUE; + } + + i += 4; + } + + i = GUARD_START_OFFSET + requested_bytes; + while (i < (GUARD_START_OFFSET + requested_bytes + GUARD_END_PAD)) + { + dbus_uint32_t value = *(dbus_uint32_t *) (void *) &block[i]; + if (value != GUARD_VALUE) + { + _dbus_warn ("Block of %lu bytes from %s had end guard value 0x%ux at %d expected 0x%x", + (long) requested_bytes, source_string (source), + value, i, GUARD_VALUE); + failed = TRUE; + } + + i += 4; + } + + /* set memory to anything but nul bytes */ + if (overwrite) + memset (free_block, 'g', requested_bytes); + + if (failed) + _dbus_assert_not_reached ("guard value corruption"); + } +} + +static void* +set_guards (void *real_block, + size_t requested_bytes, + BlockSource source) +{ + unsigned char *block = real_block; + unsigned int i; + + if (block == NULL) + return NULL; + + _dbus_assert (GUARD_START_OFFSET + GUARD_END_PAD == GUARD_EXTRA_SIZE); + + *((dbus_uint32_t *) (void *) block) = requested_bytes; + *((dbus_uint32_t *) (void *) (block + 4)) = source; + + i = GUARD_INFO_SIZE; + while (i < GUARD_START_OFFSET) + { + (*(dbus_uint32_t *) (void *) &block[i]) = GUARD_VALUE; + + i += 4; + } + + i = GUARD_START_OFFSET + requested_bytes; + while (i < (GUARD_START_OFFSET + requested_bytes + GUARD_END_PAD)) + { + (*(dbus_uint32_t *) (void *) &block[i]) = GUARD_VALUE; + + i += 4; + } + + check_guards (block + GUARD_START_OFFSET, FALSE); + + return block + GUARD_START_OFFSET; +} + +#endif + +/** @} */ /* End of internals docs */ + + +/** + * @addtogroup DBusMemory + * + * @{ + */ + +/** + * Allocates the given number of bytes, as with standard + * malloc(). Guaranteed to return #NULL if bytes is zero + * on all platforms. Returns #NULL if the allocation fails. + * The memory must be released with dbus_free(). + * + * dbus_malloc() memory is NOT safe to free with regular free() from + * the C library. Free it with dbus_free() only. + * + * @param bytes number of bytes to allocate + * @return allocated memory, or #NULL if the allocation fails. + */ +void* +dbus_malloc (size_t bytes) +{ +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + _dbus_initialize_malloc_debug (); + + if (_dbus_decrement_fail_alloc_counter ()) + { + _dbus_verbose (" FAILING malloc of %ld bytes\n", (long) bytes); + return NULL; + } +#endif + + if (bytes == 0) /* some system mallocs handle this, some don't */ + return NULL; +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + else if (fail_size != 0 && bytes > fail_size) + return NULL; + else if (guards) + { + void *block; + + block = malloc (bytes + GUARD_EXTRA_SIZE); + if (block) + { + _dbus_atomic_inc (&n_blocks_outstanding); + } + else if (malloc_cannot_fail) + { + _dbus_warn ("out of memory: malloc (%ld + %ld)", + (long) bytes, (long) GUARD_EXTRA_SIZE); + _dbus_abort (); + } + + return set_guards (block, bytes, SOURCE_MALLOC); + } +#endif + else + { + void *mem; + mem = malloc (bytes); + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (mem) + { + _dbus_atomic_inc (&n_blocks_outstanding); + } + else if (malloc_cannot_fail) + { + _dbus_warn ("out of memory: malloc (%ld)", (long) bytes); + _dbus_abort (); + } +#endif + + return mem; + } +} + +/** + * Allocates the given number of bytes, as with standard malloc(), but + * all bytes are initialized to zero as with calloc(). Guaranteed to + * return #NULL if bytes is zero on all platforms. Returns #NULL if the + * allocation fails. The memory must be released with dbus_free(). + * + * dbus_malloc0() memory is NOT safe to free with regular free() from + * the C library. Free it with dbus_free() only. + * + * @param bytes number of bytes to allocate + * @return allocated memory, or #NULL if the allocation fails. + */ +void* +dbus_malloc0 (size_t bytes) +{ +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + _dbus_initialize_malloc_debug (); + + if (_dbus_decrement_fail_alloc_counter ()) + { + _dbus_verbose (" FAILING malloc0 of %ld bytes\n", (long) bytes); + + return NULL; + } +#endif + + if (bytes == 0) + return NULL; +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + else if (fail_size != 0 && bytes > fail_size) + return NULL; + else if (guards) + { + void *block; + + block = calloc (bytes + GUARD_EXTRA_SIZE, 1); + + if (block) + { + _dbus_atomic_inc (&n_blocks_outstanding); + } + else if (malloc_cannot_fail) + { + _dbus_warn ("out of memory: calloc (%ld + %ld, 1)", + (long) bytes, (long) GUARD_EXTRA_SIZE); + _dbus_abort (); + } + + return set_guards (block, bytes, SOURCE_MALLOC_ZERO); + } +#endif + else + { + void *mem; + mem = calloc (bytes, 1); + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (mem) + { + _dbus_atomic_inc (&n_blocks_outstanding); + } + else if (malloc_cannot_fail) + { + _dbus_warn ("out of memory: calloc (%ld)", (long) bytes); + _dbus_abort (); + } +#endif + + return mem; + } +} + +/** + * Resizes a block of memory previously allocated by dbus_malloc() or + * dbus_malloc0(). Guaranteed to free the memory and return #NULL if bytes + * is zero on all platforms. Returns #NULL if the resize fails. + * If the resize fails, the memory is not freed. + * + * @param memory block to be resized + * @param bytes new size of the memory block + * @return allocated memory, or #NULL if the resize fails. + */ +void* +dbus_realloc (void *memory, + size_t bytes) +{ +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + _dbus_initialize_malloc_debug (); + + if (_dbus_decrement_fail_alloc_counter ()) + { + _dbus_verbose (" FAILING realloc of %ld bytes\n", (long) bytes); + + return NULL; + } +#endif + + if (bytes == 0) /* guarantee this is safe */ + { + dbus_free (memory); + return NULL; + } +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + else if (fail_size != 0 && bytes > fail_size) + return NULL; + else if (guards) + { + if (memory) + { + size_t old_bytes; + void *block; + + check_guards (memory, FALSE); + + block = realloc (((unsigned char*)memory) - GUARD_START_OFFSET, + bytes + GUARD_EXTRA_SIZE); + + if (block == NULL) + { + if (malloc_cannot_fail) + { + _dbus_warn ("out of memory: realloc (%p, %ld + %ld)", + memory, (long) bytes, (long) GUARD_EXTRA_SIZE); + _dbus_abort (); + } + + return NULL; + } + + old_bytes = *(dbus_uint32_t*)block; + if (bytes >= old_bytes) + /* old guards shouldn't have moved */ + check_guards (((unsigned char*)block) + GUARD_START_OFFSET, FALSE); + + return set_guards (block, bytes, SOURCE_REALLOC); + } + else + { + void *block; + + block = malloc (bytes + GUARD_EXTRA_SIZE); + + if (block) + { + _dbus_atomic_inc (&n_blocks_outstanding); + } + else if (malloc_cannot_fail) + { + _dbus_warn ("out of memory: malloc (%ld + %ld)", + (long) bytes, (long) GUARD_EXTRA_SIZE); + _dbus_abort (); + } + + return set_guards (block, bytes, SOURCE_REALLOC_NULL); + } + } +#endif + else + { + void *mem; + mem = realloc (memory, bytes); + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (mem == NULL && malloc_cannot_fail) + { + _dbus_warn ("out of memory: malloc (%ld)", (long) bytes); + _dbus_abort (); + } + + if (memory == NULL && mem != NULL) + _dbus_atomic_inc (&n_blocks_outstanding); +#endif + return mem; + } +} + +/** + * Frees a block of memory previously allocated by dbus_malloc() or + * dbus_malloc0(). If passed #NULL, does nothing. + * + * @param memory block to be freed + */ +void +dbus_free (void *memory) +{ +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (guards) + { + check_guards (memory, TRUE); + if (memory) + { +#ifdef DBUS_DISABLE_ASSERT + _dbus_atomic_dec (&n_blocks_outstanding); +#else + dbus_int32_t old_value; + + old_value = _dbus_atomic_dec (&n_blocks_outstanding); + _dbus_assert (old_value >= 1); +#endif + + free (((unsigned char*)memory) - GUARD_START_OFFSET); + } + + return; + } +#endif + + if (memory) /* we guarantee it's safe to free (NULL) */ + { +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +#ifdef DBUS_DISABLE_ASSERT + _dbus_atomic_dec (&n_blocks_outstanding); +#else + dbus_int32_t old_value; + + old_value = _dbus_atomic_dec (&n_blocks_outstanding); + _dbus_assert (old_value >= 1); +#endif +#endif + + free (memory); + } +} + +/** + * Frees a #NULL-terminated array of strings. + * If passed #NULL, does nothing. + * + * @param str_array the array to be freed + */ +void +dbus_free_string_array (char **str_array) +{ + if (str_array) + { + int i; + + i = 0; + while (str_array[i]) + { + dbus_free (str_array[i]); + i++; + } + + dbus_free (str_array); + } +} + +/** @} */ /* End of public API docs block */ + + +/** + * @addtogroup DBusMemoryInternals + * + * @{ + */ + +/** + * _dbus_current_generation is used to track each + * time that dbus_shutdown() is called, so we can + * reinit things after it's been called. It is simply + * incremented each time we shut down. + */ +int _dbus_current_generation = 1; + +/** + * Represents a function to be called on shutdown. + */ +typedef struct ShutdownClosure ShutdownClosure; + +/** + * This struct represents a function to be called on shutdown. + */ +struct ShutdownClosure +{ + ShutdownClosure *next; /**< Next ShutdownClosure */ + DBusShutdownFunction func; /**< Function to call */ + void *data; /**< Data for function */ +}; + +/* Protected by _DBUS_LOCK (shutdown_funcs) */ +static ShutdownClosure *registered_globals = NULL; + +/** + * Register a cleanup function to be called exactly once + * the next time dbus_shutdown() is called. + * + * @param func the function + * @param data data to pass to the function + * @returns #FALSE on not enough memory + */ +dbus_bool_t +_dbus_register_shutdown_func (DBusShutdownFunction func, + void *data) +{ + dbus_bool_t ok; + + if (!_DBUS_LOCK (shutdown_funcs)) + return FALSE; + + ok = _dbus_register_shutdown_func_unlocked (func, data); + _DBUS_UNLOCK (shutdown_funcs); + return ok; +} + +dbus_bool_t +_dbus_register_shutdown_func_unlocked (DBusShutdownFunction func, + void *data) +{ + ShutdownClosure *c; + + c = dbus_new (ShutdownClosure, 1); + + if (c == NULL) + return FALSE; + + c->func = func; + c->data = data; + + c->next = registered_globals; + registered_globals = c; + + return TRUE; +} + +/** @} */ /* End of private API docs block */ + + +/** + * @addtogroup DBusMemory + * + * @{ + */ + +/** + * Frees all memory allocated internally by libdbus and + * reverses the effects of dbus_threads_init(). libdbus keeps internal + * global variables, for example caches and thread locks, and it + * can be useful to free these internal data structures. + * + * dbus_shutdown() does NOT free memory that was returned + * to the application. It only frees libdbus-internal + * data structures. + * + * You MUST free all memory and release all reference counts + * returned to you by libdbus prior to calling dbus_shutdown(). + * + * If a shared connection is open, calling dbus_shutdown() will + * drain its queue of messages and disconnect it. In particular, + * this will result in processing of the special Disconnected + * signal, which may result in a call to _exit(), unless you + * have used dbus_connection_set_exit_on_disconnect() to disable + * that behaviour. + * + * You can't continue to use any D-Bus objects, such as connections, + * that were allocated prior to dbus_shutdown(). You can, however, + * start over; call dbus_threads_init() again, create new connections, + * and so forth. + * + * WARNING: dbus_shutdown() is NOT thread safe, it must be called + * while NO other threads are using D-Bus. (Remember, you have to free + * all D-Bus objects and memory before you call dbus_shutdown(), so no + * thread can be using libdbus.) + * + * The purpose of dbus_shutdown() is to allow applications to get + * clean output from memory leak checkers. dbus_shutdown() may also be + * useful if you want to dlopen() libdbus instead of linking to it, + * and want to be able to unload the library again. + * + * There is absolutely no requirement to call dbus_shutdown() - in fact, + * most applications won't bother and should not feel guilty. + * + * You have to know that nobody is using libdbus in your application's + * process before you can call dbus_shutdown(). One implication of this + * is that calling dbus_shutdown() from a library is almost certainly + * wrong, since you don't know what the rest of the app is up to. + * + */ +void +dbus_shutdown (void) +{ + while (registered_globals != NULL) + { + ShutdownClosure *c; + + c = registered_globals; + registered_globals = c->next; + + (* c->func) (c->data); + + dbus_free (c); + } + + /* We wrap this in the thread-initialization lock because + * dbus_threads_init() uses the current generation to tell whether + * we're initialized, so we need to make sure that un-initializing + * propagates into all threads. */ + _dbus_threads_lock_platform_specific (); + _dbus_current_generation += 1; + _dbus_threads_unlock_platform_specific (); +} + +/** @} */ /** End of public API docs block */ + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +#include "dbus-test.h" + +/** + * @ingroup DBusMemoryInternals + * Unit test for DBusMemory + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_memory_test (const char *test_data_dir _DBUS_GNUC_UNUSED) +{ + dbus_bool_t old_guards; + void *p; + size_t size; + + old_guards = guards; + guards = TRUE; + p = dbus_malloc (4); + if (p == NULL) + _dbus_test_fatal ("no memory"); + for (size = 4; size < 256; size += 4) + { + p = dbus_realloc (p, size); + if (p == NULL) + _dbus_test_fatal ("no memory"); + } + for (size = 256; size != 0; size -= 4) + { + p = dbus_realloc (p, size); + if (p == NULL) + _dbus_test_fatal ("no memory"); + } + dbus_free (p); + guards = old_guards; + return TRUE; +} + +#endif diff --git a/src/3rdparty/libdbus/dbus/dbus-memory.h b/src/3rdparty/libdbus/dbus/dbus-memory.h new file mode 100644 index 00000000..5b5a41e1 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-memory.h @@ -0,0 +1,74 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-memory.h D-Bus memory handling + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_MEMORY_H +#define DBUS_MEMORY_H + +#include <dbus/dbus-macros.h> +#include <stddef.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusMemory + * @{ + */ + +DBUS_EXPORT +DBUS_MALLOC +DBUS_ALLOC_SIZE(1) +void* dbus_malloc (size_t bytes); + +DBUS_EXPORT +DBUS_MALLOC +DBUS_ALLOC_SIZE(1) +void* dbus_malloc0 (size_t bytes); + +DBUS_EXPORT +DBUS_ALLOC_SIZE(2) +void* dbus_realloc (void *memory, + size_t bytes); +DBUS_EXPORT +void dbus_free (void *memory); + +#define dbus_new(type, count) ((type*)dbus_malloc (sizeof (type) * (count))) +#define dbus_new0(type, count) ((type*)dbus_malloc0 (sizeof (type) * (count))) + +DBUS_EXPORT +void dbus_free_string_array (char **str_array); + +typedef void (* DBusFreeFunction) (void *memory); + +DBUS_EXPORT +void dbus_shutdown (void); + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_MEMORY_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-mempool.c b/src/3rdparty/libdbus/dbus/dbus-mempool.c new file mode 100644 index 00000000..5bc4a97b --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-mempool.c @@ -0,0 +1,469 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-mempool.h Memory pools + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2011-2012 Collabora Ltd. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-mempool.h" +#include "dbus-internals.h" +#include "dbus-valgrind-internal.h" + +/** + * @defgroup DBusMemPool memory pools + * @ingroup DBusInternals + * @brief DBusMemPool object + * + * Types and functions related to DBusMemPool. A memory pool is used + * to decrease memory fragmentation/overhead and increase speed for + * blocks of small uniformly-sized objects. The main point is to avoid + * the overhead of a malloc block for each small object, speed is + * secondary. + */ + +/** + * @defgroup DBusMemPoolInternals Memory pool implementation details + * @ingroup DBusInternals + * @brief DBusMemPool implementation details + * + * The guts of DBusMemPool. + * + * @{ + */ + +/** + * typedef so DBusFreedElement struct can refer to itself. + */ +typedef struct DBusFreedElement DBusFreedElement; + +/** + * struct representing an element on the free list. + * We just cast freed elements to this so we can + * make a list out of them. + */ +struct DBusFreedElement +{ + DBusFreedElement *next; /**< next element of the free list */ +}; + +/** + * Typedef for DBusMemBlock so the struct can recursively + * point to itself. + */ +typedef struct DBusMemBlock DBusMemBlock; + +/** + * DBusMemBlock object represents a single malloc()-returned + * block that gets chunked up into objects in the memory pool. + */ +struct DBusMemBlock +{ + DBusMemBlock *next; /**< next block in the list, which is already used up; + * only saved so we can free all the blocks + * when we free the mem pool. + */ + + size_t used_so_far; /**< bytes of this block already allocated as elements. */ +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) + /* + * Ensure that elements is aligned correctly. For all supported pre-C11 + * targets, the size_t above should ensure that the elements array is + * sufficiently aligned (this is checked in the static assert below). + */ + _Alignas (dbus_max_align_t) +#endif + unsigned char elements[]; /**< the block data, actually allocated to required size */ +}; + +_DBUS_STATIC_ASSERT (_DBUS_IS_ALIGNED (sizeof (struct DBusMemBlock), + _DBUS_ALIGNOF (dbus_max_align_t))); +_DBUS_STATIC_ASSERT (_DBUS_IS_ALIGNED (offsetof (struct DBusMemBlock, + elements), + _DBUS_ALIGNOF (dbus_max_align_t))); + +/** + * Internals fields of DBusMemPool + */ +struct DBusMemPool +{ + size_t element_size; /**< size of a single object in the pool */ + size_t block_size; /**< size of most recently allocated block */ + unsigned int zero_elements : 1; /**< whether to zero-init allocated elements */ + + DBusFreedElement *free_elements; /**< a free list of elements to recycle */ + DBusMemBlock *blocks; /**< blocks of memory from malloc() */ + int allocated_elements; /**< Count of outstanding allocated elements */ +}; + +/** @} */ + +/** + * @addtogroup DBusMemPool + * + * @{ + */ + +/** + * @typedef DBusMemPool + * + * Opaque object representing a memory pool. Memory pools allow + * avoiding per-malloc-block memory overhead when allocating a lot of + * small objects that are all the same size. They are slightly + * faster than calling malloc() also. + */ + +/** + * Creates a new memory pool, or returns #NULL on failure. Objects in + * the pool must be at least sizeof(void*) bytes each, due to the way + * memory pools work. To avoid creating 64 bit problems, this means at + * least 8 bytes on all platforms, unless you are 4 bytes on 32-bit + * and 8 bytes on 64-bit. + * + * @param element_size size of an element allocated from the pool. + * @param zero_elements whether to zero-initialize elements + * @returns the new pool or #NULL + */ +DBusMemPool* +_dbus_mem_pool_new (int element_size, + dbus_bool_t zero_elements) +{ + DBusMemPool *pool; + + pool = dbus_new0 (DBusMemPool, 1); + if (pool == NULL) + return NULL; + + /* Make the element size at least 8 bytes. */ + if (element_size < 8) + element_size = 8; + if (element_size < (int) sizeof (void *)) + element_size = sizeof (void *); + + /* these assertions are equivalent but the first is more clear + * to programmers that see it fail. + */ + _dbus_assert (element_size >= (int) sizeof (void*)); + _dbus_assert (element_size >= (int) sizeof (DBusFreedElement)); + + /* align the element size to be suitable for the most-aligned type + * that we care about (in practice usually a pointer). + */ + pool->element_size = + _DBUS_ALIGN_VALUE (element_size, _DBUS_ALIGNOF (dbus_max_align_t)); + + pool->zero_elements = zero_elements != FALSE; + + pool->allocated_elements = 0; + + /* pick a size for the first block; it increases + * for each block we need to allocate. This is + * actually half the initial block size + * since _dbus_mem_pool_alloc() unconditionally + * doubles it prior to creating a new block. */ + pool->block_size = pool->element_size * 8; + + _dbus_assert ((pool->block_size % + pool->element_size) == 0); + + VALGRIND_CREATE_MEMPOOL (pool, 0, zero_elements); + + return pool; +} + +/** + * Frees a memory pool (and all elements allocated from it). + * + * @param pool the memory pool. + */ +void +_dbus_mem_pool_free (DBusMemPool *pool) +{ + DBusMemBlock *block; + + VALGRIND_DESTROY_MEMPOOL (pool); + + block = pool->blocks; + while (block != NULL) + { + DBusMemBlock *next = block->next; + + dbus_free (block); + + block = next; + } + + dbus_free (pool); +} + +/** + * Allocates an object from the memory pool. + * The object must be freed with _dbus_mem_pool_dealloc(). + * + * @param pool the memory pool + * @returns the allocated object or #NULL if no memory. + */ +void* +_dbus_mem_pool_alloc (DBusMemPool *pool) +{ +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (_dbus_disable_mem_pools ()) + { + DBusMemBlock *block; + size_t alloc_size; + + /* This is obviously really silly, but it's + * debug-mode-only code that is compiled out + * when tests are disabled (_dbus_disable_mem_pools() + * is a constant expression FALSE so this block + * should vanish) + */ + + alloc_size = sizeof (DBusMemBlock) + pool->element_size; + + if (pool->zero_elements) + block = dbus_malloc0 (alloc_size); + else + block = dbus_malloc (alloc_size); + + if (block != NULL) + { + block->next = pool->blocks; + pool->blocks = block; + pool->allocated_elements += 1; + + VALGRIND_MEMPOOL_ALLOC (pool, (void *) &block->elements[0], + pool->element_size); + _dbus_assert (_DBUS_IS_ALIGNED (&block->elements[0], + _DBUS_ALIGNOF (dbus_max_align_t))); + return (void*) &block->elements[0]; + } + else + return NULL; + } + else +#endif + { + if (_dbus_decrement_fail_alloc_counter ()) + { + _dbus_verbose (" FAILING mempool alloc\n"); + return NULL; + } + else if (pool->free_elements) + { + DBusFreedElement *element = pool->free_elements; + + pool->free_elements = pool->free_elements->next; + + VALGRIND_MEMPOOL_ALLOC (pool, element, pool->element_size); + + if (pool->zero_elements) + memset (element, '\0', pool->element_size); + + pool->allocated_elements += 1; + _dbus_assert ( + _DBUS_IS_ALIGNED (element, _DBUS_ALIGNOF (dbus_max_align_t))); + return element; + } + else + { + void *element; + + if (pool->blocks == NULL || + pool->blocks->used_so_far == pool->block_size) + { + /* Need a new block */ + DBusMemBlock *block; + size_t alloc_size; +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + int saved_counter; +#endif + + if (pool->block_size <= _DBUS_INT_MAX / 4) /* avoid overflow */ + { + /* use a larger block size for our next block */ + pool->block_size *= 2; + _dbus_assert ((pool->block_size % + pool->element_size) == 0); + } + + alloc_size = sizeof (DBusMemBlock) + pool->block_size; + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + /* We save/restore the counter, so that memory pools won't + * cause a given function to have different number of + * allocations on different invocations. i.e. when testing + * we want consistent alloc patterns. So we skip our + * malloc here for purposes of failed alloc simulation. + */ + saved_counter = _dbus_get_fail_alloc_counter (); + _dbus_set_fail_alloc_counter (_DBUS_INT_MAX); +#endif + + if (pool->zero_elements) + block = dbus_malloc0 (alloc_size); + else + block = dbus_malloc (alloc_size); + _dbus_assert ( + _DBUS_IS_ALIGNED (block, _DBUS_ALIGNOF (dbus_max_align_t))); + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + _dbus_set_fail_alloc_counter (saved_counter); + _dbus_assert (saved_counter == _dbus_get_fail_alloc_counter ()); +#endif + + if (block == NULL) + return NULL; + + block->used_so_far = 0; + block->next = pool->blocks; + pool->blocks = block; + } + + element = &pool->blocks->elements[pool->blocks->used_so_far]; + + pool->blocks->used_so_far += pool->element_size; + + pool->allocated_elements += 1; + + VALGRIND_MEMPOOL_ALLOC (pool, element, pool->element_size); + _dbus_assert ( + _DBUS_IS_ALIGNED (element, _DBUS_ALIGNOF (dbus_max_align_t))); + return element; + } + } +} + +/** + * Deallocates an object previously created with + * _dbus_mem_pool_alloc(). The previous object + * must have come from this same pool. + * @param pool the memory pool + * @param element the element earlier allocated. + * @returns #TRUE if there are no remaining allocated elements + */ +dbus_bool_t +_dbus_mem_pool_dealloc (DBusMemPool *pool, + void *element) +{ + VALGRIND_MEMPOOL_FREE (pool, element); + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (_dbus_disable_mem_pools ()) + { + DBusMemBlock *block; + DBusMemBlock *prev; + + /* mmm, fast. ;-) debug-only code, so doesn't matter. */ + + prev = NULL; + block = pool->blocks; + + while (block != NULL) + { + if (block->elements == (unsigned char*) element) + { + if (prev) + prev->next = block->next; + else + pool->blocks = block->next; + + dbus_free (block); + + _dbus_assert (pool->allocated_elements > 0); + pool->allocated_elements -= 1; + + if (pool->allocated_elements == 0) + _dbus_assert (pool->blocks == NULL); + + return pool->blocks == NULL; + } + prev = block; + block = block->next; + } + + _dbus_assert_not_reached ("freed nonexistent block"); + return FALSE; + } + else +#endif + { + DBusFreedElement *freed; + + freed = element; + /* used for internal mempool administration */ + VALGRIND_MAKE_MEM_UNDEFINED (freed, sizeof (*freed)); + + freed->next = pool->free_elements; + pool->free_elements = freed; + + _dbus_assert (pool->allocated_elements > 0); + pool->allocated_elements -= 1; + + return pool->allocated_elements == 0; + } +} + +#ifdef DBUS_ENABLE_STATS +void +_dbus_mem_pool_get_stats (DBusMemPool *pool, + dbus_uint32_t *in_use_p, + dbus_uint32_t *in_free_list_p, + dbus_uint32_t *allocated_p) +{ + DBusMemBlock *block; + DBusFreedElement *freed; + dbus_uint32_t in_use = 0; + dbus_uint32_t in_free_list = 0; + dbus_uint32_t allocated = 0; + + if (pool != NULL) + { + in_use = pool->element_size * pool->allocated_elements; + + for (freed = pool->free_elements; freed != NULL; freed = freed->next) + { + in_free_list += pool->element_size; + } + + for (block = pool->blocks; block != NULL; block = block->next) + { + if (block == pool->blocks) + allocated += pool->block_size; + else + allocated += block->used_so_far; + } + } + + if (in_use_p != NULL) + *in_use_p = in_use; + + if (in_free_list_p != NULL) + *in_free_list_p = in_free_list; + + if (allocated_p != NULL) + *allocated_p = allocated; +} +#endif /* DBUS_ENABLE_STATS */ + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-mempool.h b/src/3rdparty/libdbus/dbus/dbus-mempool.h new file mode 100644 index 00000000..fe7efd1d --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-mempool.h @@ -0,0 +1,56 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-mempool.h Memory pools + * + * Copyright (C) 2002 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_MEMPOOL_H +#define DBUS_MEMPOOL_H + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-memory.h> +#include <dbus/dbus-types.h> + +DBUS_BEGIN_DECLS + +typedef struct DBusMemPool DBusMemPool; + +DBUS_PRIVATE_EXPORT +DBusMemPool* _dbus_mem_pool_new (int element_size, + dbus_bool_t zero_elements); +DBUS_PRIVATE_EXPORT +void _dbus_mem_pool_free (DBusMemPool *pool); +DBUS_PRIVATE_EXPORT +void* _dbus_mem_pool_alloc (DBusMemPool *pool); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_mem_pool_dealloc (DBusMemPool *pool, + void *element); + +/* if DBUS_ENABLE_STATS */ +void _dbus_mem_pool_get_stats (DBusMemPool *pool, + dbus_uint32_t *in_use_p, + dbus_uint32_t *in_free_list_p, + dbus_uint32_t *allocated_p); + +DBUS_END_DECLS + +#endif /* DBUS_MEMPOOL_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-message-internal.h b/src/3rdparty/libdbus/dbus/dbus-message-internal.h new file mode 100644 index 00000000..55f473f4 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-message-internal.h @@ -0,0 +1,150 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-message-internal.h DBusMessage object internal interfaces + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_MESSAGE_INTERNAL_H +#define DBUS_MESSAGE_INTERNAL_H + +#include <dbus/dbus-marshal-validate.h> +#include <dbus/dbus-message.h> +#include <dbus/dbus-resources.h> +#include <dbus/dbus-list.h> + +DBUS_BEGIN_DECLS + +#ifdef DBUS_ENABLE_VERBOSE_MODE +void _dbus_message_trace_ref (DBusMessage *message, + int old_refcount, + int new_refcount, + const char *why); +#else +/* this bypasses any "unused" warnings for the old and new refcount */ +#define _dbus_message_trace_ref(m, o, n, w) \ + do \ + {\ + (void) (o); \ + (void) (n); \ + } while (0) +#endif + +typedef struct DBusMessageLoader DBusMessageLoader; + +void _dbus_message_get_network_data (DBusMessage *message, + const DBusString **header, + const DBusString **body); +DBUS_PRIVATE_EXPORT +void _dbus_message_get_unix_fds (DBusMessage *message, + const int **fds, + unsigned *n_fds); + +unsigned int _dbus_message_get_n_unix_fds (DBusMessage *message); +void _dbus_message_lock (DBusMessage *message); +void _dbus_message_unlock (DBusMessage *message); +dbus_bool_t _dbus_message_add_counter (DBusMessage *message, + DBusCounter *counter); +void _dbus_message_add_counter_link (DBusMessage *message, + DBusList *link); +void _dbus_message_remove_counter (DBusMessage *message, + DBusCounter *counter); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_message_remove_unknown_fields (DBusMessage *message); + +DBUS_PRIVATE_EXPORT +DBusMessageLoader* _dbus_message_loader_new (void); +DBUS_PRIVATE_EXPORT +DBusMessageLoader* _dbus_message_loader_ref (DBusMessageLoader *loader); +DBUS_PRIVATE_EXPORT +void _dbus_message_loader_unref (DBusMessageLoader *loader); + +DBUS_PRIVATE_EXPORT +void _dbus_message_loader_get_buffer (DBusMessageLoader *loader, + DBusString **buffer, + int *max_to_read, + dbus_bool_t *may_read_unix_fds); +DBUS_PRIVATE_EXPORT +void _dbus_message_loader_return_buffer (DBusMessageLoader *loader, + DBusString *buffer); + + +#ifdef HAVE_UNIX_FD_PASSING +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_message_loader_get_unix_fds (DBusMessageLoader *loader, + int **fds, + unsigned *max_n_fds); + +DBUS_PRIVATE_EXPORT +void _dbus_message_loader_return_unix_fds (DBusMessageLoader *loader, + int *fds, + unsigned n_fds); +#endif + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_message_loader_queue_messages (DBusMessageLoader *loader); +DBusMessage* _dbus_message_loader_peek_message (DBusMessageLoader *loader); +DBUS_PRIVATE_EXPORT +DBusMessage* _dbus_message_loader_pop_message (DBusMessageLoader *loader); +DBusList* _dbus_message_loader_pop_message_link (DBusMessageLoader *loader); +void _dbus_message_loader_putback_message_link (DBusMessageLoader *loader, + DBusList *link); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_message_loader_get_is_corrupted (DBusMessageLoader *loader); +DBusValidity _dbus_message_loader_get_corruption_reason (DBusMessageLoader *loader); + +void _dbus_message_loader_set_max_message_size (DBusMessageLoader *loader, + long size); +DBUS_PRIVATE_EXPORT +long _dbus_message_loader_get_max_message_size (DBusMessageLoader *loader); + +void _dbus_message_loader_set_max_message_unix_fds(DBusMessageLoader *loader, + long n); +long _dbus_message_loader_get_max_message_unix_fds(DBusMessageLoader *loader); +int _dbus_message_loader_get_pending_fds_count (DBusMessageLoader *loader); +void _dbus_message_loader_set_pending_fds_function (DBusMessageLoader *loader, + void (* callback) (void *), + void *data); + +typedef struct DBusVariant DBusVariant; +DBUS_PRIVATE_EXPORT +DBusVariant *_dbus_variant_read (DBusMessageIter *reader); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_variant_write (DBusVariant *self, + DBusMessageIter *writer); +DBUS_PRIVATE_EXPORT +void _dbus_variant_free (DBusVariant *self); +DBUS_PRIVATE_EXPORT +int _dbus_variant_get_length (DBusVariant *self); +DBUS_PRIVATE_EXPORT +const DBusString *_dbus_variant_peek (DBusVariant *self); +DBUS_PRIVATE_EXPORT +const char *_dbus_variant_get_signature (DBusVariant *self); + +static inline void +_dbus_clear_variant (DBusVariant **variant_p) +{ + _dbus_clear_pointer_impl (DBusVariant, variant_p, _dbus_variant_free); +} + +DBUS_END_DECLS + +#endif /* DBUS_MESSAGE_INTERNAL_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-message-private.h b/src/3rdparty/libdbus/dbus/dbus-message-private.h new file mode 100644 index 00000000..eb52f44f --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-message-private.h @@ -0,0 +1,148 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-message-private.h header shared between dbus-message.c and dbus-message-util.c + * + * Copyright (C) 2005 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_MESSAGE_PRIVATE_H +#define DBUS_MESSAGE_PRIVATE_H + +#include <dbus/dbus-message.h> +#include <dbus/dbus-message-internal.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-dataslot.h> +#include <dbus/dbus-marshal-header.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusMessageInternals + * @{ + */ + +/** + * @typedef DBusMessageLoader + * + * The DBusMessageLoader object encapsulates the process of converting + * a byte stream into a series of DBusMessage. It buffers the incoming + * bytes as efficiently as possible, and generates a queue of + * messages. DBusMessageLoader is typically used as part of a + * DBusTransport implementation. The DBusTransport then hands off + * the loaded messages to a DBusConnection, making the messages + * visible to the application. + * + * @todo write tests for break-loader that a) randomly delete header + * fields and b) set string fields to zero-length and other funky + * values. + * + */ + +/** + * Implementation details of DBusMessageLoader. + * All members are private. + */ +struct DBusMessageLoader +{ + int refcount; /**< Reference count. */ + + DBusString data; /**< Buffered data */ + + DBusList *messages; /**< Complete messages. */ + + long max_message_size; /**< Maximum size of a message */ + long max_message_unix_fds; /**< Maximum unix fds in a message */ + + DBusValidity corruption_reason; /**< why we were corrupted */ + + unsigned int corrupted : 1; /**< We got broken data, and are no longer working */ + + unsigned int buffer_outstanding : 1; /**< Someone is using the buffer to read */ + +#ifdef HAVE_UNIX_FD_PASSING + unsigned int unix_fds_outstanding : 1; /**< Someone is using the unix fd array to read */ + + int *unix_fds; /**< File descriptors that have been read from the transport but not yet been handed to any message. Array will be allocated at first use. */ + unsigned n_unix_fds_allocated; /**< Number of file descriptors this array has space for */ + unsigned n_unix_fds; /**< Number of valid file descriptors in array */ + void (* unix_fds_change) (void *); /**< Notify when the pending fds change */ + void *unix_fds_change_data; +#endif +}; + + +/** How many bits are in the changed_stamp used to validate iterators */ +#define CHANGED_STAMP_BITS 21 + +/** + * @brief Internals of DBusMessage + * + * Object representing a message received from or to be sent to + * another application. This is an opaque object, all members + * are private. + */ +struct DBusMessage +{ + DBusAtomic refcount; /**< Reference count */ + + DBusHeader header; /**< Header network data and associated cache */ + + DBusString body; /**< Body network data. */ + + unsigned int locked : 1; /**< Message being sent, no modifications allowed. */ + +#ifndef DBUS_DISABLE_CHECKS + unsigned int in_cache : 1; /**< Has been "freed" since it's in the cache (this is a debug feature) */ +#endif + + DBusList *counters; /**< 0-N DBusCounter used to track message size/unix fds. */ + long size_counter_delta; /**< Size we incremented the size counters by. */ + + dbus_uint32_t changed_stamp : CHANGED_STAMP_BITS; /**< Incremented when iterators are invalidated. */ + + DBusDataSlotList slot_list; /**< Data stored by allocated integer ID */ + +#ifndef DBUS_DISABLE_CHECKS + int generation; /**< _dbus_current_generation when message was created */ +#endif + +#ifdef HAVE_UNIX_FD_PASSING + int *unix_fds; + /**< Unix file descriptors associated with this message. These are + closed when the message is destroyed, hence make sure to dup() + them when adding or removing them here. */ + unsigned n_unix_fds; /**< Number of valid fds in the array */ + unsigned n_unix_fds_allocated; /**< Allocated size of the array */ + + long unix_fd_counter_delta; /**< Size we incremented the unix fd counter by */ +#endif +}; + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_message_iter_get_args_valist (DBusMessageIter *iter, + DBusError *error, + int first_arg_type, + va_list var_args); + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_MESSAGE_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-message.c b/src/3rdparty/libdbus/dbus/dbus-message.c new file mode 100644 index 00000000..b47a8638 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-message.c @@ -0,0 +1,5574 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-message.c DBusMessage object + * + * Copyright (C) 2002, 2003, 2004, 2005 Red Hat Inc. + * Copyright (C) 2002, 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-marshal-recursive.h" +#include "dbus-marshal-validate.h" +#include "dbus-marshal-byteswap.h" +#include "dbus-marshal-header.h" +#include "dbus-signature.h" +#include "dbus-message-private.h" +#include "dbus-object-tree.h" +#include "dbus-memory.h" +#include "dbus-list.h" +#include "dbus-threads-internal.h" +#ifdef HAVE_UNIX_FD_PASSING +#include "dbus-sysdeps.h" +#include "dbus-sysdeps-unix.h" +#endif + +#include <string.h> + +#define _DBUS_TYPE_IS_STRINGLIKE(type) \ + (type == DBUS_TYPE_STRING || type == DBUS_TYPE_SIGNATURE || \ + type == DBUS_TYPE_OBJECT_PATH) + +static void dbus_message_finalize (DBusMessage *message); + +/** + * @defgroup DBusMessageInternals DBusMessage implementation details + * @ingroup DBusInternals + * @brief DBusMessage private implementation details. + * + * The guts of DBusMessage and its methods. + * + * @{ + */ + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +static dbus_bool_t +_dbus_enable_message_cache (void) +{ + static int enabled = -1; + + if (enabled < 0) + { + const char *s = _dbus_getenv ("DBUS_MESSAGE_CACHE"); + + enabled = TRUE; + + if (s && *s) + { + if (*s == '0') + enabled = FALSE; + else if (*s == '1') + enabled = TRUE; + else + _dbus_warn ("DBUS_MESSAGE_CACHE should be 0 or 1 if set, not '%s'", + s); + } + } + + return enabled; +} +#else + /* constant expression, should be optimized away */ +# define _dbus_enable_message_cache() (TRUE) +#endif + +#ifndef _dbus_message_trace_ref +void +_dbus_message_trace_ref (DBusMessage *message, + int old_refcount, + int new_refcount, + const char *why) +{ + static int enabled = -1; + + _dbus_trace_ref ("DBusMessage", message, old_refcount, new_refcount, why, + "DBUS_MESSAGE_TRACE", &enabled); +} +#endif + +/* Not thread locked, but strictly const/read-only so should be OK + */ +/** An static string representing an empty signature */ +_DBUS_STRING_DEFINE_STATIC(_dbus_empty_signature_str, ""); + +/* these have wacky values to help trap uninitialized iterators; + * but has to fit in 3 bits + */ +enum { + DBUS_MESSAGE_ITER_TYPE_READER = 3, + DBUS_MESSAGE_ITER_TYPE_WRITER = 7 +}; + +/** typedef for internals of message iterator */ +typedef struct DBusMessageRealIter DBusMessageRealIter; + +/** + * @brief Internals of DBusMessageIter + * + * Object representing a position in a message. All fields are internal. + */ +struct DBusMessageRealIter +{ + DBusMessage *message; /**< Message used */ + dbus_uint32_t changed_stamp : CHANGED_STAMP_BITS; /**< stamp to detect invalid iters */ + dbus_uint32_t iter_type : 3; /**< whether this is a reader or writer iter */ + dbus_uint32_t sig_refcount : 8; /**< depth of open_signature() */ + union + { + DBusTypeWriter writer; /**< writer */ + DBusTypeReader reader; /**< reader */ + } u; /**< the type writer or reader that does all the work */ +}; + +#if DBUS_SIZEOF_VOID_P > 8 +/* + * Architectures with 128-bit pointers were not supported in DBus 1.10, so we + * do no check for DBus 1.10 structure layout compatibility for such + * architectures (e.g. Arm Morello). + */ +#define CHECK_DBUS_1_10_BINARY_COMPATIBILITY 0 +#else +#define CHECK_DBUS_1_10_BINARY_COMPATIBILITY 1 +/** + * Layout of a DBusMessageIter on the stack in dbus 1.10.0. This is no + * longer used, but for ABI compatibility we need to assert that the + * new layout is the same size. + */ +typedef struct +{ + void *dummy1; + void *dummy2; + dbus_uint32_t dummy3; + int dummy4; + int dummy5; + int dummy6; + int dummy7; + int dummy8; + int dummy9; + int dummy10; + int dummy11; + int pad1; + int pad2; + void *pad3; +} DBusMessageIter_1_10_0; +#endif + +static void +get_const_signature (DBusHeader *header, + const DBusString **type_str_p, + int *type_pos_p) +{ + if (_dbus_header_get_field_raw (header, + DBUS_HEADER_FIELD_SIGNATURE, + type_str_p, + type_pos_p)) + { + *type_pos_p += 1; /* skip the signature length which is 1 byte */ + } + else + { + *type_str_p = &_dbus_empty_signature_str; + *type_pos_p = 0; + } +} + +/** + * Swaps the message to compiler byte order if required + * + * @param message the message + */ +static void +_dbus_message_byteswap (DBusMessage *message) +{ + const DBusString *type_str; + int type_pos; + char byte_order; + + byte_order = _dbus_header_get_byte_order (&message->header); + + if (byte_order == DBUS_COMPILER_BYTE_ORDER) + return; + + _dbus_verbose ("Swapping message into compiler byte order\n"); + + get_const_signature (&message->header, &type_str, &type_pos); + + _dbus_marshal_byteswap (type_str, type_pos, + byte_order, + DBUS_COMPILER_BYTE_ORDER, + &message->body, 0); + + _dbus_header_byteswap (&message->header, DBUS_COMPILER_BYTE_ORDER); + _dbus_assert (_dbus_header_get_byte_order (&message->header) == + DBUS_COMPILER_BYTE_ORDER); +} + +/** byte-swap the message if it doesn't match our byte order. + * Called only when we need the message in our own byte order, + * normally when reading arrays of integers or doubles. + * Otherwise should not be called since it would do needless + * work. + */ +#define ensure_byte_order(message) _dbus_message_byteswap (message) + +/** + * Gets the data to be sent over the network for this message. + * The header and then the body should be written out. + * This function is guaranteed to always return the same + * data once a message is locked (with dbus_message_lock()). + * + * @param message the message. + * @param header return location for message header data. + * @param body return location for message body data. + */ +void +_dbus_message_get_network_data (DBusMessage *message, + const DBusString **header, + const DBusString **body) +{ + _dbus_assert (message->locked); + + *header = &message->header.data; + *body = &message->body; +} + +/** + * Gets the unix fds to be sent over the network for this message. + * This function is guaranteed to always return the same data once a + * message is locked (with dbus_message_lock()). + * + * @param message the message. + * @param fds return location of unix fd array + * @param n_fds return number of entries in array + */ +void _dbus_message_get_unix_fds(DBusMessage *message, + const int **fds, + unsigned *n_fds) +{ + _dbus_assert (message->locked); + +#ifdef HAVE_UNIX_FD_PASSING + *fds = message->unix_fds; + *n_fds = message->n_unix_fds; +#else + *fds = NULL; + *n_fds = 0; +#endif +} + +/** + * Remove every header field not known to this version of dbus. + * + * @param message the message + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_message_remove_unknown_fields (DBusMessage *message) +{ + return _dbus_header_remove_unknown_fields (&message->header); +} + +/** + * Sets the serial number of a message. + * This can only be done once on a message. + * + * DBusConnection will automatically set the serial to an appropriate value + * when the message is sent; this function is only needed when encapsulating + * messages in another protocol, or otherwise bypassing DBusConnection. + * + * @param message the message + * @param serial the serial + */ +void +dbus_message_set_serial (DBusMessage *message, + dbus_uint32_t serial) +{ + _dbus_return_if_fail (message != NULL); + _dbus_return_if_fail (!message->locked); + + _dbus_header_set_serial (&message->header, serial); +} + +/** + * Adds a counter to be incremented immediately with the size/unix fds + * of this message, and decremented by the size/unix fds of this + * message when this message if finalized. The link contains a + * counter with its refcount already incremented, but the counter + * itself not incremented. Ownership of link and counter refcount is + * passed to the message. + * + * This function may be called with locks held. As a result, the counter's + * notify function is not called; the caller is expected to either call + * _dbus_counter_notify() on the counter when they are no longer holding + * locks, or take the same action that would be taken by the notify function. + * + * @param message the message + * @param link link with counter as data + */ +void +_dbus_message_add_counter_link (DBusMessage *message, + DBusList *link) +{ + /* right now we don't recompute the delta when message + * size changes, and that's OK for current purposes + * I think, but could be important to change later. + * Do recompute it whenever there are no outstanding counters, + * since it's basically free. + */ + if (message->counters == NULL) + { + message->size_counter_delta = + _dbus_string_get_length (&message->header.data) + + _dbus_string_get_length (&message->body); + +#ifdef HAVE_UNIX_FD_PASSING + message->unix_fd_counter_delta = message->n_unix_fds; +#endif + +#if 0 + _dbus_verbose ("message has size %ld\n", + message->size_counter_delta); +#endif + } + + _dbus_list_append_link (&message->counters, link); + + _dbus_counter_adjust_size (link->data, message->size_counter_delta); + +#ifdef HAVE_UNIX_FD_PASSING + _dbus_counter_adjust_unix_fd (link->data, message->unix_fd_counter_delta); +#endif +} + +/** + * Adds a counter to be incremented immediately with the size/unix fds + * of this message, and decremented by the size/unix fds of this + * message when this message if finalized. + * + * This function may be called with locks held. As a result, the counter's + * notify function is not called; the caller is expected to either call + * _dbus_counter_notify() on the counter when they are no longer holding + * locks, or take the same action that would be taken by the notify function. + * + * @param message the message + * @param counter the counter + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_message_add_counter (DBusMessage *message, + DBusCounter *counter) +{ + DBusList *link; + + link = _dbus_list_alloc_link (counter); + if (link == NULL) + return FALSE; + + _dbus_counter_ref (counter); + _dbus_message_add_counter_link (message, link); + + return TRUE; +} + +/** + * Removes a counter tracking the size/unix fds of this message, and + * decrements the counter by the size/unix fds of this message. + * + * @param message the message + * @param counter the counter + */ +void +_dbus_message_remove_counter (DBusMessage *message, + DBusCounter *counter) +{ + DBusList *link; + + link = _dbus_list_find_last (&message->counters, + counter); + _dbus_assert (link != NULL); + + _dbus_list_remove_link (&message->counters, link); + + _dbus_counter_adjust_size (counter, - message->size_counter_delta); + +#ifdef HAVE_UNIX_FD_PASSING + _dbus_counter_adjust_unix_fd (counter, - message->unix_fd_counter_delta); +#endif + + _dbus_counter_notify (counter); + _dbus_counter_unref (counter); +} + +/** + * Locks a message. Allows checking that applications don't keep a + * reference to a message in the outgoing queue and change it + * underneath us. Messages are locked when they enter the outgoing + * queue (dbus_connection_send_message()), and the library complains + * if the message is modified while locked. This function may also + * called externally, for applications wrapping D-Bus in another protocol. + * + * @param message the message to lock. + */ +void +dbus_message_lock (DBusMessage *message) +{ + if (!message->locked) + { + _dbus_header_update_lengths (&message->header, + _dbus_string_get_length (&message->body)); + + /* must have a signature if you have a body */ + _dbus_assert (_dbus_string_get_length (&message->body) == 0 || + dbus_message_get_signature (message) != NULL); + + message->locked = TRUE; + } +} + +static dbus_bool_t +set_or_delete_string_field (DBusMessage *message, + int field, + int typecode, + const char *value) +{ + if (value == NULL) + return _dbus_header_delete_field (&message->header, field); + else + return _dbus_header_set_field_basic (&message->header, + field, + typecode, + &value); +} + +/* Message Cache + * + * We cache some DBusMessage to reduce the overhead of allocating + * them. In my profiling this consistently made about an 8% + * difference. It avoids the malloc for the message, the malloc for + * the slot list, the malloc for the header string and body string, + * and the associated free() calls. It does introduce another global + * lock which could be a performance issue in certain cases. + * + * For the echo client/server the round trip time goes from around + * .000077 to .000069 with the message cache on my laptop. The sysprof + * change is as follows (numbers are cumulative percentage): + * + * with message cache implemented as array as it is now (0.000069 per): + * new_empty_header 1.46 + * mutex_lock 0.56 # i.e. _DBUS_LOCK(message_cache) + * mutex_unlock 0.25 + * self 0.41 + * unref 2.24 + * self 0.68 + * list_clear 0.43 + * mutex_lock 0.33 # i.e. _DBUS_LOCK(message_cache) + * mutex_unlock 0.25 + * + * with message cache implemented as list (0.000070 per roundtrip): + * new_empty_header 2.72 + * list_pop_first 1.88 + * unref 3.3 + * list_prepend 1.63 + * + * without cache (0.000077 per roundtrip): + * new_empty_header 6.7 + * string_init_preallocated 3.43 + * dbus_malloc 2.43 + * dbus_malloc0 2.59 + * + * unref 4.02 + * string_free 1.82 + * dbus_free 1.63 + * dbus_free 0.71 + * + * If you implement the message_cache with a list, the primary reason + * it's slower is that you add another thread lock (on the DBusList + * mempool). + */ + +/** Avoid caching huge messages */ +#define MAX_MESSAGE_SIZE_TO_CACHE 10 * _DBUS_ONE_KILOBYTE + +/** Avoid caching too many messages */ +#define MAX_MESSAGE_CACHE_SIZE 5 + +/* Protected by _DBUS_LOCK (message_cache) */ +static DBusMessage *message_cache[MAX_MESSAGE_CACHE_SIZE]; +static int message_cache_count = 0; +static dbus_bool_t message_cache_shutdown_registered = FALSE; + +static void +dbus_message_cache_shutdown (void *data) +{ + int i; + + if (!_DBUS_LOCK (message_cache)) + _dbus_assert_not_reached ("we would have initialized global locks " + "before registering a shutdown function"); + + i = 0; + while (i < MAX_MESSAGE_CACHE_SIZE) + { + if (message_cache[i]) + dbus_message_finalize (message_cache[i]); + + ++i; + } + + message_cache_count = 0; + message_cache_shutdown_registered = FALSE; + + _DBUS_UNLOCK (message_cache); +} + +/** + * Tries to get a message from the message cache. The retrieved + * message will have junk in it, so it still needs to be cleared out + * in dbus_message_new_empty_header() + * + * @returns the message, or #NULL if none cached + */ +static DBusMessage* +dbus_message_get_cached (void) +{ + DBusMessage *message; + int i; + + message = NULL; + + if (!_DBUS_LOCK (message_cache)) + { + /* we'd have initialized global locks before caching anything, + * so there can't be anything in the cache */ + return NULL; + } + + _dbus_assert (message_cache_count >= 0); + + if (message_cache_count == 0) + { + _DBUS_UNLOCK (message_cache); + return NULL; + } + + /* This is not necessarily true unless count > 0, and + * message_cache is uninitialized until the shutdown is + * registered + */ + _dbus_assert (message_cache_shutdown_registered); + + i = 0; + while (i < MAX_MESSAGE_CACHE_SIZE) + { + if (message_cache[i]) + { + message = message_cache[i]; + message_cache[i] = NULL; + message_cache_count -= 1; + break; + } + ++i; + } + _dbus_assert (message_cache_count >= 0); + _dbus_assert (i < MAX_MESSAGE_CACHE_SIZE); + _dbus_assert (message != NULL); + + _dbus_assert (_dbus_atomic_get (&message->refcount) == 0); + + _dbus_assert (message->counters == NULL); + + _DBUS_UNLOCK (message_cache); + + return message; +} + +#ifdef HAVE_UNIX_FD_PASSING +static void +close_unix_fds(int *fds, unsigned *n_fds) +{ + DBusError e; + unsigned int i; + + if (*n_fds <= 0) + return; + + dbus_error_init(&e); + + for (i = 0; i < *n_fds; i++) + { + if (!_dbus_close(fds[i], &e)) + { + _dbus_warn("Failed to close file descriptor: %s", e.message); + dbus_error_free(&e); + } + } + + *n_fds = 0; + + /* We don't free the array here, in case we can recycle it later */ +} +#endif + +static void +free_counter (void *element, + void *data) +{ + DBusCounter *counter = element; + DBusMessage *message = data; + + _dbus_counter_adjust_size (counter, - message->size_counter_delta); +#ifdef HAVE_UNIX_FD_PASSING + _dbus_counter_adjust_unix_fd (counter, - message->unix_fd_counter_delta); +#endif + + _dbus_counter_notify (counter); + _dbus_counter_unref (counter); +} + +/** + * Tries to cache a message, otherwise finalize it. + * + * @param message the message + */ +static void +dbus_message_cache_or_finalize (DBusMessage *message) +{ + dbus_bool_t was_cached; + int i; + + _dbus_assert (_dbus_atomic_get (&message->refcount) == 0); + + /* This calls application code and has to be done first thing + * without holding the lock + */ + _dbus_data_slot_list_clear (&message->slot_list); + + _dbus_list_foreach (&message->counters, + free_counter, message); + _dbus_list_clear (&message->counters); + +#ifdef HAVE_UNIX_FD_PASSING + close_unix_fds(message->unix_fds, &message->n_unix_fds); +#endif + + was_cached = FALSE; + + if (!_DBUS_LOCK (message_cache)) + { + /* The only way to get a non-null message goes through + * dbus_message_get_cached() which takes the lock. */ + _dbus_assert_not_reached ("we would have initialized global locks " + "the first time we constructed a message"); + } + + if (!message_cache_shutdown_registered) + { + _dbus_assert (message_cache_count == 0); + + if (!_dbus_register_shutdown_func (dbus_message_cache_shutdown, NULL)) + goto out; + + i = 0; + while (i < MAX_MESSAGE_CACHE_SIZE) + { + message_cache[i] = NULL; + ++i; + } + + message_cache_shutdown_registered = TRUE; + } + + _dbus_assert (message_cache_count >= 0); + + if (!_dbus_enable_message_cache ()) + goto out; + + if ((_dbus_string_get_length (&message->header.data) + + _dbus_string_get_length (&message->body)) > + MAX_MESSAGE_SIZE_TO_CACHE) + goto out; + + if (message_cache_count >= MAX_MESSAGE_CACHE_SIZE) + goto out; + + /* Find empty slot */ + i = 0; + while (message_cache[i] != NULL) + ++i; + + _dbus_assert (i < MAX_MESSAGE_CACHE_SIZE); + + _dbus_assert (message_cache[i] == NULL); + message_cache[i] = message; + message_cache_count += 1; + was_cached = TRUE; +#ifndef DBUS_DISABLE_CHECKS + message->in_cache = TRUE; +#endif + + out: + _dbus_assert (_dbus_atomic_get (&message->refcount) == 0); + + _DBUS_UNLOCK (message_cache); + + if (!was_cached) + dbus_message_finalize (message); +} + +/* + * Arrange for iter to be something that _dbus_message_iter_check() would + * reject as not a valid iterator. + */ +static void +_dbus_message_real_iter_zero (DBusMessageRealIter *iter) +{ + _dbus_assert (iter != NULL); + _DBUS_ZERO (*iter); + /* NULL is not, strictly speaking, guaranteed to be all-bits-zero */ + iter->message = NULL; +} + +/** + * Initialize iter as if with #DBUS_MESSAGE_ITER_INIT_CLOSED. The only valid + * operation for such an iterator is + * dbus_message_iter_abandon_container_if_open(), which does nothing. + */ +void +dbus_message_iter_init_closed (DBusMessageIter *iter) +{ + _dbus_return_if_fail (iter != NULL); + _dbus_message_real_iter_zero ((DBusMessageRealIter *) iter); +} + +static dbus_bool_t +_dbus_message_real_iter_is_zeroed (DBusMessageRealIter *iter) +{ + return (iter != NULL && iter->message == NULL && iter->changed_stamp == 0 && + iter->iter_type == 0 && iter->sig_refcount == 0); +} + +#if defined(DBUS_ENABLE_CHECKS) || defined(DBUS_ENABLE_ASSERT) +static dbus_bool_t +_dbus_message_iter_check (DBusMessageRealIter *iter) +{ + char byte_order; + + if (iter == NULL) + { + _dbus_warn_check_failed ("dbus message iterator is NULL"); + return FALSE; + } + + if (iter->message == NULL || iter->iter_type == 0) + { + _dbus_warn_check_failed ("dbus message iterator has already been " + "closed, or is uninitialized or corrupt"); + return FALSE; + } + + byte_order = _dbus_header_get_byte_order (&iter->message->header); + + if (iter->iter_type == DBUS_MESSAGE_ITER_TYPE_READER) + { + if (iter->u.reader.byte_order != byte_order) + { + _dbus_warn_check_failed ("dbus message changed byte order since iterator was created"); + return FALSE; + } + /* because we swap the message into compiler order when you init an iter */ + _dbus_assert (iter->u.reader.byte_order == DBUS_COMPILER_BYTE_ORDER); + } + else if (iter->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER) + { + if (iter->u.writer.byte_order != byte_order) + { + _dbus_warn_check_failed ("dbus message changed byte order since append iterator was created"); + return FALSE; + } + /* because we swap the message into compiler order when you init an iter */ + _dbus_assert (iter->u.writer.byte_order == DBUS_COMPILER_BYTE_ORDER); + } + else + { + _dbus_warn_check_failed ("dbus message iterator looks uninitialized or corrupted"); + return FALSE; + } + + if (iter->changed_stamp != iter->message->changed_stamp) + { + _dbus_warn_check_failed ("dbus message iterator invalid because the message has been modified (or perhaps the iterator is just uninitialized)"); + return FALSE; + } + + return TRUE; +} +#endif /* DBUS_ENABLE_CHECKS || DBUS_ENABLE_ASSERT */ + +/** + * Implementation of the varargs arg-getting functions. + * dbus_message_get_args() is the place to go for complete + * documentation. + * + * @see dbus_message_get_args + * @param iter the message iter + * @param error error to be filled in + * @param first_arg_type type of the first argument + * @param var_args return location for first argument, followed by list of type/location pairs + * @returns #FALSE if error was set + */ +dbus_bool_t +_dbus_message_iter_get_args_valist (DBusMessageIter *iter, + DBusError *error, + int first_arg_type, + va_list var_args) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + int spec_type, msg_type, i, j; + dbus_bool_t retval; + va_list copy_args; + + _dbus_assert (_dbus_message_iter_check (real)); + + retval = FALSE; + + spec_type = first_arg_type; + i = 0; + + /* copy var_args first, then we can do another iteration over it to + * free memory and close unix fds if parse failed at some point. + */ + va_copy (copy_args, var_args); + + while (spec_type != DBUS_TYPE_INVALID) + { + msg_type = dbus_message_iter_get_arg_type (iter); + + if (msg_type != spec_type) + { + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Argument %d is specified to be of type \"%s\", but " + "is actually of type \"%s\"\n", i, + _dbus_type_to_string (spec_type), + _dbus_type_to_string (msg_type)); + + goto out; + } + + if (spec_type == DBUS_TYPE_UNIX_FD) + { +#ifdef HAVE_UNIX_FD_PASSING + DBusBasicValue idx; + int *pfd, nfd; + + pfd = va_arg (var_args, int*); + _dbus_assert(pfd); + + _dbus_type_reader_read_basic(&real->u.reader, &idx); + + if (idx.u32 >= real->message->n_unix_fds) + { + dbus_set_error (error, DBUS_ERROR_INCONSISTENT_MESSAGE, + "Message refers to file descriptor at index %i," + "but has only %i descriptors attached.\n", + idx.u32, + real->message->n_unix_fds); + goto out; + } + + if ((nfd = _dbus_dup(real->message->unix_fds[idx.u32], error)) < 0) + goto out; + + *pfd = nfd; +#else + dbus_set_error (error, DBUS_ERROR_NOT_SUPPORTED, + "Platform does not support file desciptor passing.\n"); + goto out; +#endif + } + else if (dbus_type_is_basic (spec_type)) + { + void *ptr; + + ptr = va_arg (var_args, void *); + + _dbus_assert (ptr != NULL); + + _dbus_type_reader_read_basic (&real->u.reader, + ptr); + } + else if (spec_type == DBUS_TYPE_ARRAY) + { + int element_type; + int spec_element_type; + const void **ptr; + int *n_elements_p; + DBusTypeReader array; + + spec_element_type = va_arg (var_args, int); + element_type = _dbus_type_reader_get_element_type (&real->u.reader); + + if (spec_element_type != element_type) + { + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Argument %d is specified to be an array of \"%s\", but " + "is actually an array of \"%s\"\n", + i, + _dbus_type_to_string (spec_element_type), + _dbus_type_to_string (element_type)); + + goto out; + } + + if (dbus_type_is_fixed (spec_element_type) && + element_type != DBUS_TYPE_UNIX_FD) + { + ptr = va_arg (var_args, const void **); + n_elements_p = va_arg (var_args, int*); + + _dbus_assert (ptr != NULL); + _dbus_assert (n_elements_p != NULL); + + _dbus_type_reader_recurse (&real->u.reader, &array); + + _dbus_type_reader_read_fixed_multi (&array, ptr, n_elements_p); + } + else if (_DBUS_TYPE_IS_STRINGLIKE (spec_element_type)) + { + char ***str_array_p; + int n_elements; + char **str_array; + + str_array_p = va_arg (var_args, char***); + n_elements_p = va_arg (var_args, int*); + + _dbus_assert (str_array_p != NULL); + _dbus_assert (n_elements_p != NULL); + + /* Count elements in the array */ + _dbus_type_reader_recurse (&real->u.reader, &array); + + n_elements = 0; + while (_dbus_type_reader_get_current_type (&array) != DBUS_TYPE_INVALID) + { + ++n_elements; + _dbus_type_reader_next (&array); + } + + str_array = dbus_new0 (char*, n_elements + 1); + if (str_array == NULL) + { + _DBUS_SET_OOM (error); + goto out; + } + + /* Now go through and dup each string */ + _dbus_type_reader_recurse (&real->u.reader, &array); + + j = 0; + while (j < n_elements) + { + const char *s; + _dbus_type_reader_read_basic (&array, + (void *) &s); + + str_array[j] = _dbus_strdup (s); + if (str_array[j] == NULL) + { + dbus_free_string_array (str_array); + _DBUS_SET_OOM (error); + goto out; + } + + ++j; + + if (!_dbus_type_reader_next (&array)) + _dbus_assert (j == n_elements); + } + + _dbus_assert (_dbus_type_reader_get_current_type (&array) == DBUS_TYPE_INVALID); + _dbus_assert (j == n_elements); + _dbus_assert (str_array[j] == NULL); + + *str_array_p = str_array; + *n_elements_p = n_elements; + } +#ifndef DBUS_DISABLE_CHECKS + else + { + _dbus_warn ("you can't read arrays of container types (struct, variant, array) with %s for now", + _DBUS_FUNCTION_NAME); + goto out; + } +#endif + } +#ifndef DBUS_DISABLE_CHECKS + else + { + _dbus_warn ("you can only read arrays and basic types with %s for now", + _DBUS_FUNCTION_NAME); + goto out; + } +#endif + + /* how many arguments already handled */ + i++; + + spec_type = va_arg (var_args, int); + if (!_dbus_type_reader_next (&real->u.reader) && spec_type != DBUS_TYPE_INVALID) + { + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Message has only %d arguments, but more were expected", i); + goto out; + } + } + + retval = TRUE; + + out: + /* there may memory or unix fd leak in the above iteration if parse failed. + * so we have another iteration over copy_args to free memory and close + * unix fds. + */ + if (!retval) + { + spec_type = first_arg_type; + j = 0; + + while (j < i) + { + if (spec_type == DBUS_TYPE_UNIX_FD) + { +#ifdef HAVE_UNIX_FD_PASSING + int *pfd; + + pfd = va_arg (copy_args, int *); + _dbus_assert(pfd); + if (*pfd >= 0) + { + _dbus_close (*pfd, NULL); + *pfd = -1; + } +#endif + } + else if (dbus_type_is_basic (spec_type)) + { + /* move the index forward */ + va_arg (copy_args, const void *); + } + else if (spec_type == DBUS_TYPE_ARRAY) + { + int spec_element_type; + + spec_element_type = va_arg (copy_args, int); + if (dbus_type_is_fixed (spec_element_type)) + { + /* move the index forward */ + va_arg (copy_args, const void **); + va_arg (copy_args, int *); + } + else if (_DBUS_TYPE_IS_STRINGLIKE (spec_element_type)) + { + char ***str_array_p; + + str_array_p = va_arg (copy_args, char ***); + /* move the index forward */ + va_arg (copy_args, int *); + _dbus_assert (str_array_p != NULL); + dbus_free_string_array (*str_array_p); + *str_array_p = NULL; + } + } + + spec_type = va_arg (copy_args, int); + j++; + } + } + + va_end (copy_args); + return retval; +} + +/** @} */ + +/** + * @defgroup DBusMessage DBusMessage + * @ingroup DBus + * @brief Message to be sent or received over a #DBusConnection. + * + * A DBusMessage is the most basic unit of communication over a + * DBusConnection. A DBusConnection represents a stream of messages + * received from a remote application, and a stream of messages + * sent to a remote application. + * + * A message has a message type, returned from + * dbus_message_get_type(). This indicates whether the message is a + * method call, a reply to a method call, a signal, or an error reply. + * + * A message has header fields such as the sender, destination, method + * or signal name, and so forth. DBusMessage has accessor functions for + * these, such as dbus_message_get_member(). + * + * Convenience functions dbus_message_is_method_call(), dbus_message_is_signal(), + * and dbus_message_is_error() check several header fields at once and are + * slightly more efficient than checking the header fields with individual + * accessor functions. + * + * Finally, a message has arguments. The number and types of arguments + * are in the message's signature header field (accessed with + * dbus_message_get_signature()). Simple argument values are usually + * retrieved with dbus_message_get_args() but more complex values such + * as structs may require the use of #DBusMessageIter. + * + * The D-Bus specification goes into some more detail about header fields and + * message types. + * + * @{ + */ + +/** + * @typedef DBusMessage + * + * Opaque data type representing a message received from or to be + * sent to another application. + */ + +/** + * Returns the serial of a message or 0 if none has been specified. + * The message's serial number is provided by the application sending + * the message and is used to identify replies to this message. + * + * All messages received on a connection will have a serial provided + * by the remote application. + * + * For messages you're sending, dbus_connection_send() will assign a + * serial and return it to you. + * + * @param message the message + * @returns the serial + */ +dbus_uint32_t +dbus_message_get_serial (DBusMessage *message) +{ + _dbus_return_val_if_fail (message != NULL, 0); + + return _dbus_header_get_serial (&message->header); +} + +/** + * Sets the reply serial of a message (the serial of the message this + * is a reply to). + * + * @param message the message + * @param reply_serial the serial we're replying to + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_set_reply_serial (DBusMessage *message, + dbus_uint32_t reply_serial) +{ + DBusBasicValue value; + + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (!message->locked, FALSE); + _dbus_return_val_if_fail (reply_serial != 0, FALSE); /* 0 is invalid */ + + value.u32 = reply_serial; + + return _dbus_header_set_field_basic (&message->header, + DBUS_HEADER_FIELD_REPLY_SERIAL, + DBUS_TYPE_UINT32, + &value); +} + +/** + * Returns the serial that the message is a reply to or 0 if none. + * + * @param message the message + * @returns the reply serial + */ +dbus_uint32_t +dbus_message_get_reply_serial (DBusMessage *message) +{ + dbus_uint32_t v_UINT32; + + _dbus_return_val_if_fail (message != NULL, 0); + + if (_dbus_header_get_field_basic (&message->header, + DBUS_HEADER_FIELD_REPLY_SERIAL, + DBUS_TYPE_UINT32, + &v_UINT32)) + return v_UINT32; + else + return 0; +} + +static void +dbus_message_finalize (DBusMessage *message) +{ + _dbus_assert (_dbus_atomic_get (&message->refcount) == 0); + + /* This calls application callbacks! */ + _dbus_data_slot_list_free (&message->slot_list); + + _dbus_list_foreach (&message->counters, + free_counter, message); + _dbus_list_clear (&message->counters); + + _dbus_header_free (&message->header); + _dbus_string_free (&message->body); + +#ifdef HAVE_UNIX_FD_PASSING + close_unix_fds(message->unix_fds, &message->n_unix_fds); + dbus_free(message->unix_fds); +#endif + + _dbus_assert (_dbus_atomic_get (&message->refcount) == 0); + + dbus_free (message); +} + +static DBusMessage* +dbus_message_new_empty_header (void) +{ + DBusMessage *message; + dbus_bool_t from_cache; + + message = dbus_message_get_cached (); + + if (message != NULL) + { + from_cache = TRUE; + } + else + { + from_cache = FALSE; + message = dbus_new0 (DBusMessage, 1); + if (message == NULL) + return NULL; +#ifndef DBUS_DISABLE_CHECKS + message->generation = _dbus_current_generation; +#endif + +#ifdef HAVE_UNIX_FD_PASSING + message->unix_fds = NULL; + message->n_unix_fds_allocated = 0; +#endif + } + + _dbus_atomic_inc (&message->refcount); + + _dbus_message_trace_ref (message, 0, 1, "new_empty_header"); + + message->locked = FALSE; +#ifndef DBUS_DISABLE_CHECKS + message->in_cache = FALSE; +#endif + message->counters = NULL; + message->size_counter_delta = 0; + message->changed_stamp = 0; + +#ifdef HAVE_UNIX_FD_PASSING + message->n_unix_fds = 0; + message->n_unix_fds_allocated = 0; + message->unix_fd_counter_delta = 0; +#endif + + if (!from_cache) + _dbus_data_slot_list_init (&message->slot_list); + + if (from_cache) + { + _dbus_header_reinit (&message->header); + _dbus_string_set_length (&message->body, 0); + } + else + { + if (!_dbus_header_init (&message->header)) + { + dbus_free (message); + return NULL; + } + + if (!_dbus_string_init_preallocated (&message->body, 32)) + { + _dbus_header_free (&message->header); + dbus_free (message); + return NULL; + } + } + + return message; +} + +/** + * Constructs a new message of the given message type. + * Types include #DBUS_MESSAGE_TYPE_METHOD_CALL, + * #DBUS_MESSAGE_TYPE_SIGNAL, and so forth. + * + * Usually you want to use dbus_message_new_method_call(), + * dbus_message_new_method_return(), dbus_message_new_signal(), + * or dbus_message_new_error() instead. + * + * @param message_type type of message + * @returns new message or #NULL if no memory + */ +DBusMessage* +dbus_message_new (int message_type) +{ + DBusMessage *message; + + _dbus_return_val_if_fail (message_type != DBUS_MESSAGE_TYPE_INVALID, NULL); + + message = dbus_message_new_empty_header (); + if (message == NULL) + return NULL; + + if (!_dbus_header_create (&message->header, + DBUS_COMPILER_BYTE_ORDER, + message_type, + NULL, NULL, NULL, NULL, NULL)) + { + dbus_message_unref (message); + return NULL; + } + + return message; +} + +/** + * Constructs a new message to invoke a method on a remote + * object. Returns #NULL if memory can't be allocated for the + * message. The destination may be #NULL in which case no destination + * is set; this is appropriate when using D-Bus in a peer-to-peer + * context (no message bus). The interface may be #NULL, which means + * that if multiple methods with the given name exist it is undefined + * which one will be invoked. + * + * The path and method names may not be #NULL. + * + * Destination, path, interface, and method name can't contain + * any invalid characters (see the D-Bus specification). + * + * @param destination name that the message should be sent to or #NULL + * @param path object path the message should be sent to + * @param iface interface to invoke method on, or #NULL + * @param method method to invoke + * + * @returns a new DBusMessage, free with dbus_message_unref() + */ +DBusMessage* +dbus_message_new_method_call (const char *destination, + const char *path, + const char *iface, + const char *method) +{ + DBusMessage *message; + + _dbus_return_val_if_fail (path != NULL, NULL); + _dbus_return_val_if_fail (method != NULL, NULL); + _dbus_return_val_if_fail (destination == NULL || + _dbus_check_is_valid_bus_name (destination), NULL); + _dbus_return_val_if_fail (_dbus_check_is_valid_path (path), NULL); + _dbus_return_val_if_fail (iface == NULL || + _dbus_check_is_valid_interface (iface), NULL); + _dbus_return_val_if_fail (_dbus_check_is_valid_member (method), NULL); + + message = dbus_message_new_empty_header (); + if (message == NULL) + return NULL; + + if (!_dbus_header_create (&message->header, + DBUS_COMPILER_BYTE_ORDER, + DBUS_MESSAGE_TYPE_METHOD_CALL, + destination, path, iface, method, NULL)) + { + dbus_message_unref (message); + return NULL; + } + + return message; +} + +/** + * Constructs a message that is a reply to a method call. Returns + * #NULL if memory can't be allocated for the message. + * + * @param method_call the message being replied to + * @returns a new DBusMessage, free with dbus_message_unref() + */ +DBusMessage* +dbus_message_new_method_return (DBusMessage *method_call) +{ + DBusMessage *message; + const char *sender; + + _dbus_return_val_if_fail (method_call != NULL, NULL); + + sender = dbus_message_get_sender (method_call); + + /* sender is allowed to be null here in peer-to-peer case */ + + message = dbus_message_new_empty_header (); + if (message == NULL) + return NULL; + + if (!_dbus_header_create (&message->header, + DBUS_COMPILER_BYTE_ORDER, + DBUS_MESSAGE_TYPE_METHOD_RETURN, + sender, NULL, NULL, NULL, NULL)) + { + dbus_message_unref (message); + return NULL; + } + + dbus_message_set_no_reply (message, TRUE); + + if (!dbus_message_set_reply_serial (message, + dbus_message_get_serial (method_call))) + { + dbus_message_unref (message); + return NULL; + } + + return message; +} + +/** + * Constructs a new message representing a signal emission. Returns + * #NULL if memory can't be allocated for the message. A signal is + * identified by its originating object path, interface, and the name + * of the signal. + * + * Path, interface, and signal name must all be valid (the D-Bus + * specification defines the syntax of these fields). + * + * @param path the path to the object emitting the signal + * @param iface the interface the signal is emitted from + * @param name name of the signal + * @returns a new DBusMessage, free with dbus_message_unref() + */ +DBusMessage* +dbus_message_new_signal (const char *path, + const char *iface, + const char *name) +{ + DBusMessage *message; + + _dbus_return_val_if_fail (path != NULL, NULL); + _dbus_return_val_if_fail (iface != NULL, NULL); + _dbus_return_val_if_fail (name != NULL, NULL); + _dbus_return_val_if_fail (_dbus_check_is_valid_path (path), NULL); + _dbus_return_val_if_fail (_dbus_check_is_valid_interface (iface), NULL); + _dbus_return_val_if_fail (_dbus_check_is_valid_member (name), NULL); + + message = dbus_message_new_empty_header (); + if (message == NULL) + return NULL; + + if (!_dbus_header_create (&message->header, + DBUS_COMPILER_BYTE_ORDER, + DBUS_MESSAGE_TYPE_SIGNAL, + NULL, path, iface, name, NULL)) + { + dbus_message_unref (message); + return NULL; + } + + dbus_message_set_no_reply (message, TRUE); + + return message; +} + +/** + * Creates a new message that is an error reply to another message. + * Error replies are most common in response to method calls, but + * can be returned in reply to any message. + * + * The error name must be a valid error name according to the syntax + * given in the D-Bus specification. If you don't want to make + * up an error name just use #DBUS_ERROR_FAILED. + * + * @param reply_to the message we're replying to + * @param error_name the error name + * @param error_message the error message string (or #NULL for none, but please give a message) + * @returns a new error message object, free with dbus_message_unref() + */ +DBusMessage* +dbus_message_new_error (DBusMessage *reply_to, + const char *error_name, + const char *error_message) +{ + DBusMessage *message; + const char *sender; + DBusMessageIter iter; + + _dbus_return_val_if_fail (reply_to != NULL, NULL); + _dbus_return_val_if_fail (error_name != NULL, NULL); + _dbus_return_val_if_fail (_dbus_check_is_valid_error_name (error_name), NULL); + + sender = dbus_message_get_sender (reply_to); + + /* sender may be NULL for non-message-bus case or + * when the message bus is dealing with an unregistered + * connection. + */ + message = dbus_message_new_empty_header (); + if (message == NULL) + return NULL; + + if (!_dbus_header_create (&message->header, + DBUS_COMPILER_BYTE_ORDER, + DBUS_MESSAGE_TYPE_ERROR, + sender, NULL, NULL, NULL, error_name)) + { + dbus_message_unref (message); + return NULL; + } + + dbus_message_set_no_reply (message, TRUE); + + if (!dbus_message_set_reply_serial (message, + dbus_message_get_serial (reply_to))) + { + dbus_message_unref (message); + return NULL; + } + + if (error_message != NULL) + { + dbus_message_iter_init_append (message, &iter); + if (!dbus_message_iter_append_basic (&iter, + DBUS_TYPE_STRING, + &error_message)) + { + dbus_message_unref (message); + return NULL; + } + } + + return message; +} + +/** + * Creates a new message that is an error reply to another message, allowing + * you to use printf formatting. + * + * See dbus_message_new_error() for details - this function is the same + * aside from the printf formatting. + * + * @todo add _DBUS_GNUC_PRINTF to this (requires moving _DBUS_GNUC_PRINTF to + * public header, see DBUS_DEPRECATED for an example) + * + * @param reply_to the original message + * @param error_name the error name + * @param error_format the error message format as with printf + * @param ... format string arguments + * @returns a new error message + */ +DBusMessage* +dbus_message_new_error_printf (DBusMessage *reply_to, + const char *error_name, + const char *error_format, + ...) +{ + va_list args; + DBusString str; + DBusMessage *message; + + _dbus_return_val_if_fail (reply_to != NULL, NULL); + _dbus_return_val_if_fail (error_name != NULL, NULL); + _dbus_return_val_if_fail (_dbus_check_is_valid_error_name (error_name), NULL); + + if (!_dbus_string_init (&str)) + return NULL; + + va_start (args, error_format); + + if (_dbus_string_append_printf_valist (&str, error_format, args)) + message = dbus_message_new_error (reply_to, error_name, + _dbus_string_get_const_data (&str)); + else + message = NULL; + + _dbus_string_free (&str); + + va_end (args); + + return message; +} + + +/** + * Creates a new message that is an exact replica of the message + * specified, except that its refcount is set to 1, its message serial + * is reset to 0, and if the original message was "locked" (in the + * outgoing message queue and thus not modifiable) the new message + * will not be locked. + * + * @todo This function can't be used in programs that try to recover from OOM errors. + * + * @param message the message + * @returns the new message.or #NULL if not enough memory or Unix file descriptors (in case the message to copy includes Unix file descriptors) can be allocated. + */ +DBusMessage * +dbus_message_copy (const DBusMessage *message) +{ + DBusMessage *retval; + + _dbus_return_val_if_fail (message != NULL, NULL); + + retval = dbus_new0 (DBusMessage, 1); + if (retval == NULL) + return NULL; + + _dbus_atomic_inc (&retval->refcount); + + retval->locked = FALSE; +#ifndef DBUS_DISABLE_CHECKS + retval->generation = message->generation; +#endif + + if (!_dbus_header_copy (&message->header, &retval->header)) + { + dbus_free (retval); + return NULL; + } + + if (!_dbus_string_init_preallocated (&retval->body, + _dbus_string_get_length (&message->body))) + { + _dbus_header_free (&retval->header); + dbus_free (retval); + return NULL; + } + + if (!_dbus_string_copy (&message->body, 0, + &retval->body, 0)) + goto failed_copy; + +#ifdef HAVE_UNIX_FD_PASSING + retval->unix_fds = dbus_new(int, message->n_unix_fds); + if (retval->unix_fds == NULL && message->n_unix_fds > 0) + goto failed_copy; + + retval->n_unix_fds_allocated = message->n_unix_fds; + + for (retval->n_unix_fds = 0; + retval->n_unix_fds < message->n_unix_fds; + retval->n_unix_fds++) + { + retval->unix_fds[retval->n_unix_fds] = _dbus_dup(message->unix_fds[retval->n_unix_fds], NULL); + + if (retval->unix_fds[retval->n_unix_fds] < 0) + goto failed_copy; + } + +#endif + + _dbus_message_trace_ref (retval, 0, 1, "copy"); + return retval; + + failed_copy: + _dbus_header_free (&retval->header); + _dbus_string_free (&retval->body); + +#ifdef HAVE_UNIX_FD_PASSING + close_unix_fds(retval->unix_fds, &retval->n_unix_fds); + dbus_free(retval->unix_fds); +#endif + + dbus_free (retval); + + return NULL; +} + + +/** + * Increments the reference count of a DBusMessage. + * + * @param message the message + * @returns the message + * @see dbus_message_unref + */ +DBusMessage * +dbus_message_ref (DBusMessage *message) +{ + dbus_int32_t old_refcount; + + _dbus_return_val_if_fail (message != NULL, NULL); + _dbus_return_val_if_fail (message->generation == _dbus_current_generation, NULL); + _dbus_return_val_if_fail (!message->in_cache, NULL); + + old_refcount = _dbus_atomic_inc (&message->refcount); + _dbus_assert (old_refcount >= 1); + _dbus_message_trace_ref (message, old_refcount, old_refcount + 1, "ref"); + + return message; +} + +/** + * Decrements the reference count of a DBusMessage, freeing the + * message if the count reaches 0. + * + * @param message the message + * @see dbus_message_ref + */ +void +dbus_message_unref (DBusMessage *message) +{ + dbus_int32_t old_refcount; + + _dbus_return_if_fail (message != NULL); + _dbus_return_if_fail (message->generation == _dbus_current_generation); + _dbus_return_if_fail (!message->in_cache); + + old_refcount = _dbus_atomic_dec (&message->refcount); + + _dbus_assert (old_refcount >= 1); + + _dbus_message_trace_ref (message, old_refcount, old_refcount - 1, "unref"); + + if (old_refcount == 1) + { + /* Calls application callbacks! */ + dbus_message_cache_or_finalize (message); + } +} + +/** + * Gets the type of a message. Types include + * #DBUS_MESSAGE_TYPE_METHOD_CALL, #DBUS_MESSAGE_TYPE_METHOD_RETURN, + * #DBUS_MESSAGE_TYPE_ERROR, #DBUS_MESSAGE_TYPE_SIGNAL, but other + * types are allowed and all code must silently ignore messages of + * unknown type. #DBUS_MESSAGE_TYPE_INVALID will never be returned. + * + * @param message the message + * @returns the type of the message + */ +int +dbus_message_get_type (DBusMessage *message) +{ + _dbus_return_val_if_fail (message != NULL, DBUS_MESSAGE_TYPE_INVALID); + + return _dbus_header_get_message_type (&message->header); +} + +/** + * Appends fields to a message given a variable argument list. The + * variable argument list should contain the type of each argument + * followed by the value to append. Appendable types are basic types, + * and arrays of fixed-length basic types (except arrays of Unix file + * descriptors). To append variable-length basic types, or any more + * complex value, you have to use an iterator rather than this + * function. + * + * To append a basic type, specify its type code followed by the + * address of the value. For example: + * + * @code + * + * dbus_int32_t v_INT32 = 42; + * const char *v_STRING = "Hello World"; + * dbus_message_append_args (message, + * DBUS_TYPE_INT32, &v_INT32, + * DBUS_TYPE_STRING, &v_STRING, + * DBUS_TYPE_INVALID); + * @endcode + * + * To append an array of fixed-length basic types (except Unix file + * descriptors), pass in the DBUS_TYPE_ARRAY typecode, the element + * typecode, the address of the array pointer, and a 32-bit integer + * giving the number of elements in the array. So for example: + * + * @code + * + * const dbus_int32_t array[] = { 1, 2, 3 }; + * const dbus_int32_t *v_ARRAY = array; + * dbus_message_append_args (message, + * DBUS_TYPE_ARRAY, DBUS_TYPE_INT32, &v_ARRAY, 3, + * DBUS_TYPE_INVALID); + * + * @endcode + * + * This function does not support arrays of Unix file descriptors. If + * you need those you need to manually recurse into the array. + * + * For Unix file descriptors this function will internally duplicate + * the descriptor you passed in. Hence you may close the descriptor + * immediately after this call. + * + * @warning in C, given "int array[]", "&array == array" (the + * comp.lang.c FAQ says otherwise, but gcc and the FAQ don't agree). + * So if you're using an array instead of a pointer you have to create + * a pointer variable, assign the array to it, then take the address + * of the pointer variable. For strings it works to write + * const char *array = "Hello" and then use &array though. + * + * The last argument to this function must be #DBUS_TYPE_INVALID, + * marking the end of the argument list. If you don't do this + * then libdbus won't know to stop and will read invalid memory. + * + * String/signature/path arrays should be passed in as "const char*** + * address_of_array" and "int n_elements" + * + * @todo support DBUS_TYPE_STRUCT and DBUS_TYPE_VARIANT and complex arrays + * + * @todo If this fails due to lack of memory, the message is hosed and + * you have to start over building the whole message. + * + * @param message the message + * @param first_arg_type type of the first argument + * @param ... value of first argument, list of additional type-value pairs + * @returns #TRUE on success + */ +dbus_bool_t +dbus_message_append_args (DBusMessage *message, + int first_arg_type, + ...) +{ + dbus_bool_t retval; + va_list var_args; + + _dbus_return_val_if_fail (message != NULL, FALSE); + + va_start (var_args, first_arg_type); + retval = dbus_message_append_args_valist (message, + first_arg_type, + var_args); + va_end (var_args); + + return retval; +} + +/** + * Like dbus_message_append_args() but takes a va_list for use by language bindings. + * + * @todo for now, if this function fails due to OOM it will leave + * the message half-written and you have to discard the message + * and start over. + * + * @see dbus_message_append_args. + * @param message the message + * @param first_arg_type type of first argument + * @param var_args value of first argument, then list of type/value pairs + * @returns #TRUE on success + */ +dbus_bool_t +dbus_message_append_args_valist (DBusMessage *message, + int first_arg_type, + va_list var_args) +{ + int type; + DBusMessageIter iter; + + _dbus_return_val_if_fail (message != NULL, FALSE); + + type = first_arg_type; + + dbus_message_iter_init_append (message, &iter); + + while (type != DBUS_TYPE_INVALID) + { + if (dbus_type_is_basic (type)) + { + const void *value; + value = va_arg (var_args, const void *); + + if (!dbus_message_iter_append_basic (&iter, + type, + value)) + goto failed; + } + else if (type == DBUS_TYPE_ARRAY) + { + int element_type; + DBusMessageIter array; + char buf[2]; + + element_type = va_arg (var_args, int); + + buf[0] = element_type; + buf[1] = '\0'; + if (!dbus_message_iter_open_container (&iter, + DBUS_TYPE_ARRAY, + buf, + &array)) + goto failed; + + if (dbus_type_is_fixed (element_type) && + element_type != DBUS_TYPE_UNIX_FD) + { + const void **value; + int n_elements; + + value = va_arg (var_args, const void **); + n_elements = va_arg (var_args, int); + + if (!dbus_message_iter_append_fixed_array (&array, + element_type, + value, + n_elements)) { + dbus_message_iter_abandon_container (&iter, &array); + goto failed; + } + } + else if (_DBUS_TYPE_IS_STRINGLIKE (element_type)) + { + const char ***value_p; + const char **value; + int n_elements; + int i; + + value_p = va_arg (var_args, const char***); + n_elements = va_arg (var_args, int); + + value = *value_p; + + i = 0; + while (i < n_elements) + { + if (!dbus_message_iter_append_basic (&array, + element_type, + &value[i])) { + dbus_message_iter_abandon_container (&iter, &array); + goto failed; + } + ++i; + } + } + else + { + _dbus_warn ("arrays of %s can't be appended with %s for now", + _dbus_type_to_string (element_type), + _DBUS_FUNCTION_NAME); + dbus_message_iter_abandon_container (&iter, &array); + goto failed; + } + + if (!dbus_message_iter_close_container (&iter, &array)) + goto failed; + } +#ifndef DBUS_DISABLE_CHECKS + else + { + _dbus_warn ("type %s isn't supported yet in %s", + _dbus_type_to_string (type), _DBUS_FUNCTION_NAME); + goto failed; + } +#endif + + type = va_arg (var_args, int); + } + + return TRUE; + + failed: + return FALSE; +} + +/** + * Gets arguments from a message given a variable argument list. The + * supported types include those supported by + * dbus_message_append_args(); that is, basic types and arrays of + * fixed-length basic types. The arguments are the same as they would + * be for dbus_message_iter_get_basic() or + * dbus_message_iter_get_fixed_array(). + * + * In addition to those types, arrays of string, object path, and + * signature are supported; but these are returned as allocated memory + * and must be freed with dbus_free_string_array(), while the other + * types are returned as const references. To get a string array + * pass in "char ***array_location" and "int *n_elements". + * + * Similar to dbus_message_get_fixed_array() this function does not + * support arrays of type DBUS_TYPE_UNIX_FD. If you need to parse + * messages with arrays of Unix file descriptors you need to recurse + * into the array manually. + * + * Unix file descriptors that are read with this function will have + * the FD_CLOEXEC flag set. If you need them without this flag set, + * make sure to unset it with fcntl(). + * + * The variable argument list should contain the type of the argument + * followed by a pointer to where the value should be stored. The list + * is terminated with #DBUS_TYPE_INVALID. + * + * Except for string arrays, the returned values are constant; do not + * free them. They point into the #DBusMessage. + * + * If the requested arguments are not present, or do not have the + * requested types, then an error will be set. + * + * If more arguments than requested are present, the requested + * arguments are returned and the extra arguments are ignored. + * + * @todo support DBUS_TYPE_STRUCT and DBUS_TYPE_VARIANT and complex arrays + * + * @param message the message + * @param error error to be filled in on failure + * @param first_arg_type the first argument type + * @param ... location for first argument value, then list of type-location pairs + * @returns #FALSE if the error was set + */ +dbus_bool_t +dbus_message_get_args (DBusMessage *message, + DBusError *error, + int first_arg_type, + ...) +{ + dbus_bool_t retval; + va_list var_args; + + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_error_is_set (error, FALSE); + + va_start (var_args, first_arg_type); + retval = dbus_message_get_args_valist (message, error, first_arg_type, var_args); + va_end (var_args); + + return retval; +} + +/** + * Like dbus_message_get_args but takes a va_list for use by language bindings. + * + * @see dbus_message_get_args + * @param message the message + * @param error error to be filled in + * @param first_arg_type type of the first argument + * @param var_args return location for first argument, followed by list of type/location pairs + * @returns #FALSE if error was set + */ +dbus_bool_t +dbus_message_get_args_valist (DBusMessage *message, + DBusError *error, + int first_arg_type, + va_list var_args) +{ + DBusMessageIter iter; + + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_error_is_set (error, FALSE); + + dbus_message_iter_init (message, &iter); + return _dbus_message_iter_get_args_valist (&iter, error, first_arg_type, var_args); +} + +static void +_dbus_message_iter_init_common (DBusMessage *message, + DBusMessageRealIter *real, + int iter_type) +{ + /* If these static assertions fail on your platform, report it as a bug. */ + _DBUS_STATIC_ASSERT (sizeof (DBusMessageRealIter) <= sizeof (DBusMessageIter)); + _DBUS_STATIC_ASSERT (_DBUS_ALIGNOF (DBusMessageRealIter) <= + _DBUS_ALIGNOF (DBusMessageIter)); +#if CHECK_DBUS_1_10_BINARY_COMPATIBILITY + /* A failure of these two assertions would indicate that we've broken + * ABI on this platform since 1.10.0. */ + _DBUS_STATIC_ASSERT (sizeof (DBusMessageIter_1_10_0) == + sizeof (DBusMessageIter)); + _DBUS_STATIC_ASSERT (_DBUS_ALIGNOF (DBusMessageIter_1_10_0) == + _DBUS_ALIGNOF (DBusMessageIter)); +#endif + /* If this static assertion fails, it means the DBusMessageIter struct + * is not "packed", which might result in "iter = other_iter" not copying + * every byte. */ +#if DBUS_SIZEOF_VOID_P > 8 + _DBUS_STATIC_ASSERT (sizeof (DBusMessageIter) == 16 * sizeof (void *)); +#else + _DBUS_STATIC_ASSERT (sizeof (DBusMessageIter) == + 4 * sizeof (void *) + sizeof (dbus_uint32_t) + 9 * sizeof (int)); +#endif + + /* Since the iterator will read or write who-knows-what from the + * message, we need to get in the right byte order + */ + ensure_byte_order (message); + + real->message = message; + real->changed_stamp = message->changed_stamp; + real->iter_type = iter_type; + real->sig_refcount = 0; +} + +/** + * Initializes a #DBusMessageIter for reading the arguments of the + * message passed in. + * + * When possible, dbus_message_get_args() is much more convenient. + * Some types of argument can only be read with #DBusMessageIter + * however. + * + * The easiest way to iterate is like this: + * @code + * dbus_message_iter_init (message, &iter); + * while ((current_type = dbus_message_iter_get_arg_type (&iter)) != DBUS_TYPE_INVALID) + * dbus_message_iter_next (&iter); + * @endcode + * + * #DBusMessageIter contains no allocated memory; it need not be + * freed, and can be copied by assignment or memcpy(). + * + * @param message the message + * @param iter pointer to an iterator to initialize + * @returns #FALSE if the message has no arguments + */ +dbus_bool_t +dbus_message_iter_init (DBusMessage *message, + DBusMessageIter *iter) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + const DBusString *type_str; + int type_pos; + + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (iter != NULL, FALSE); + + get_const_signature (&message->header, &type_str, &type_pos); + + _dbus_message_iter_init_common (message, real, + DBUS_MESSAGE_ITER_TYPE_READER); + + _dbus_type_reader_init (&real->u.reader, + _dbus_header_get_byte_order (&message->header), + type_str, type_pos, + &message->body, + 0); + + return _dbus_type_reader_get_current_type (&real->u.reader) != DBUS_TYPE_INVALID; +} + +/** + * Checks if an iterator has any more fields. + * + * @param iter the message iter + * @returns #TRUE if there are more fields following + */ +dbus_bool_t +dbus_message_iter_has_next (DBusMessageIter *iter) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + + _dbus_return_val_if_fail (_dbus_message_iter_check (real), FALSE); + _dbus_return_val_if_fail (real->iter_type == DBUS_MESSAGE_ITER_TYPE_READER, FALSE); + + return _dbus_type_reader_has_next (&real->u.reader); +} + +/** + * Moves the iterator to the next field, if any. If there's no next + * field, returns #FALSE. If the iterator moves forward, returns + * #TRUE. + * + * @param iter the message iter + * @returns #TRUE if the iterator was moved to the next field + */ +dbus_bool_t +dbus_message_iter_next (DBusMessageIter *iter) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + + _dbus_return_val_if_fail (_dbus_message_iter_check (real), FALSE); + _dbus_return_val_if_fail (real->iter_type == DBUS_MESSAGE_ITER_TYPE_READER, FALSE); + + return _dbus_type_reader_next (&real->u.reader); +} + +/** + * Returns the argument type of the argument that the message iterator + * points to. If the iterator is at the end of the message, returns + * #DBUS_TYPE_INVALID. You can thus write a loop as follows: + * + * @code + * dbus_message_iter_init (message, &iter); + * while ((current_type = dbus_message_iter_get_arg_type (&iter)) != DBUS_TYPE_INVALID) + * dbus_message_iter_next (&iter); + * @endcode + * + * @param iter the message iter + * @returns the argument type + */ +int +dbus_message_iter_get_arg_type (DBusMessageIter *iter) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + + _dbus_return_val_if_fail (_dbus_message_iter_check (real), DBUS_TYPE_INVALID); + _dbus_return_val_if_fail (real->iter_type == DBUS_MESSAGE_ITER_TYPE_READER, FALSE); + + return _dbus_type_reader_get_current_type (&real->u.reader); +} + +/** + * Returns the element type of the array that the message iterator + * points to. Note that you need to check that the iterator points to + * an array prior to using this function. + * + * @param iter the message iter + * @returns the array element type + */ +int +dbus_message_iter_get_element_type (DBusMessageIter *iter) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + + _dbus_return_val_if_fail (_dbus_message_iter_check (real), DBUS_TYPE_INVALID); + _dbus_return_val_if_fail (real->iter_type == DBUS_MESSAGE_ITER_TYPE_READER, DBUS_TYPE_INVALID); + _dbus_return_val_if_fail (dbus_message_iter_get_arg_type (iter) == DBUS_TYPE_ARRAY, DBUS_TYPE_INVALID); + + return _dbus_type_reader_get_element_type (&real->u.reader); +} + +/** + * Recurses into a container value when reading values from a message, + * initializing a sub-iterator to use for traversing the child values + * of the container. + * + * Note that this recurses into a value, not a type, so you can only + * recurse if the value exists. The main implication of this is that + * if you have for example an empty array of array of int32, you can + * recurse into the outermost array, but it will have no values, so + * you won't be able to recurse further. There's no array of int32 to + * recurse into. + * + * If a container is an array of fixed-length types (except Unix file + * descriptors), it is much more efficient to use + * dbus_message_iter_get_fixed_array() to get the whole array in one + * shot, rather than individually walking over the array elements. + * + * Be sure you have somehow checked that + * dbus_message_iter_get_arg_type() matches the type you are expecting + * to recurse into. Results of this function are undefined if there is + * no container to recurse into at the current iterator position. + * + * @param iter the message iterator + * @param sub the sub-iterator to initialize + */ +void +dbus_message_iter_recurse (DBusMessageIter *iter, + DBusMessageIter *sub) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + DBusMessageRealIter *real_sub = (DBusMessageRealIter *)sub; + + _dbus_return_if_fail (_dbus_message_iter_check (real)); + _dbus_return_if_fail (sub != NULL); + + *real_sub = *real; + _dbus_type_reader_recurse (&real->u.reader, &real_sub->u.reader); +} + +/** + * Returns the current signature of a message iterator. This + * is useful primarily for dealing with variants; one can + * recurse into a variant and determine the signature of + * the variant's value. + * + * The returned string must be freed with dbus_free(). + * + * @param iter the message iterator + * @returns the contained signature, or NULL if out of memory + */ +char * +dbus_message_iter_get_signature (DBusMessageIter *iter) +{ + const DBusString *sig; + DBusString retstr; + char *ret = NULL; + int start, len; + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + + _dbus_return_val_if_fail (_dbus_message_iter_check (real), NULL); + + if (!_dbus_string_init (&retstr)) + return NULL; + + _dbus_type_reader_get_signature (&real->u.reader, &sig, + &start, &len); + if (!_dbus_string_append_len (&retstr, + _dbus_string_get_const_data (sig) + start, + len)) + goto oom; + + /* This is correct whether it succeeds or fails: on success it sets `ret`, + * and on failure it leaves `ret` set to NULL. */ + _dbus_string_steal_data (&retstr, &ret); + +oom: + _dbus_string_free (&retstr); + return ret; +} + +/** + * Reads a basic-typed value from the message iterator. + * Basic types are the non-containers such as integer and string. + * + * The value argument should be the address of a location to store + * the returned value. So for int32 it should be a "dbus_int32_t*" + * and for string a "const char**". The returned value is + * by reference and should not be freed. + * + * This call duplicates Unix file descriptors when reading them. It is + * your job to close them when you don't need them anymore. + * + * Unix file descriptors that are read with this function will have + * the FD_CLOEXEC flag set. If you need them without this flag set, + * make sure to unset it with fcntl(). + * + * Be sure you have somehow checked that + * dbus_message_iter_get_arg_type() matches the type you are + * expecting, or you'll crash when you try to use an integer as a + * string or something. + * + * To read any container type (array, struct, dict) you will need to + * recurse into the container with dbus_message_iter_recurse(). If + * the container is an array of fixed-length values (except Unix file + * descriptors), you can get all the array elements at once with + * dbus_message_iter_get_fixed_array(). Otherwise, you have to iterate + * over the container's contents one value at a time. + * + * All basic-typed values are guaranteed to fit in a #DBusBasicValue, + * so in versions of libdbus that have that type, you can write code like this: + * + * @code + * DBusBasicValue value; + * int type; + * dbus_message_iter_get_basic (&read_iter, &value); + * type = dbus_message_iter_get_arg_type (&read_iter); + * dbus_message_iter_append_basic (&write_iter, type, &value); + * @endcode + * + * (All D-Bus basic types are either numeric and 8 bytes or smaller, or + * behave like a string; so in older versions of libdbus, DBusBasicValue + * can be replaced with union { char *string; unsigned char bytes[8]; }, + * for instance.) + * + * @param iter the iterator + * @param value location to store the value + */ +void +dbus_message_iter_get_basic (DBusMessageIter *iter, + void *value) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + + _dbus_return_if_fail (_dbus_message_iter_check (real)); + _dbus_return_if_fail (value != NULL); + + if (dbus_message_iter_get_arg_type (iter) == DBUS_TYPE_UNIX_FD) + { +#ifdef HAVE_UNIX_FD_PASSING + DBusBasicValue idx; + + _dbus_type_reader_read_basic(&real->u.reader, &idx); + + if (idx.u32 >= real->message->n_unix_fds) { + /* Hmm, we cannot really signal an error here, so let's make + sure to return an invalid fd. */ + *((int*) value) = -1; + return; + } + + *((int*) value) = _dbus_dup(real->message->unix_fds[idx.u32], NULL); +#else + *((int*) value) = -1; +#endif + } + else + { + _dbus_type_reader_read_basic (&real->u.reader, + value); + } +} + +/** + * Returns the number of elements in the array-typed value pointed + * to by the iterator. + * Note that this function is O(1) for arrays of fixed-size types + * but O(n) for arrays of variable-length types such as strings, + * so it may be a bad idea to use it. + * + * @param iter the iterator + * @returns the number of elements in the array + */ +int +dbus_message_iter_get_element_count (DBusMessageIter *iter) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + DBusTypeReader array; + int element_type; + int n_elements = 0; + + _dbus_return_val_if_fail (_dbus_message_iter_check (real), 0); + _dbus_return_val_if_fail (_dbus_type_reader_get_current_type (&real->u.reader) + == DBUS_TYPE_ARRAY, 0); + + element_type = _dbus_type_reader_get_element_type (&real->u.reader); + _dbus_type_reader_recurse (&real->u.reader, &array); + if (dbus_type_is_fixed (element_type)) + { + int alignment = _dbus_type_get_alignment (element_type); + int total_len = _dbus_type_reader_get_array_length (&array); + n_elements = total_len / alignment; + } + else + { + while (_dbus_type_reader_get_current_type (&array) != DBUS_TYPE_INVALID) + { + ++n_elements; + _dbus_type_reader_next (&array); + } + } + + return n_elements; +} + +/** + * Returns the number of bytes in the array as marshaled in the wire + * protocol. The iterator must currently be inside an array-typed + * value. + * + * This function is deprecated on the grounds that it is stupid. Why + * would you want to know how many bytes are in the array as marshaled + * in the wire protocol? Use dbus_message_iter_get_element_count() instead. + * + * @param iter the iterator + * @returns the number of bytes in the array + */ +int +dbus_message_iter_get_array_len (DBusMessageIter *iter) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + + _dbus_return_val_if_fail (_dbus_message_iter_check (real), 0); + + return _dbus_type_reader_get_array_length (&real->u.reader); +} + +/** + * Reads a block of fixed-length values from the message iterator. + * Fixed-length values are those basic types that are not string-like, + * such as integers, bool, double. The returned block will be from the + * current position in the array until the end of the array. + * + * There is one exception here: although DBUS_TYPE_UNIX_FD is + * considered a 'fixed' type arrays of this type may not be read with + * this function. + * + * The message iter should be "in" the array (that is, you recurse into the + * array, and then you call dbus_message_iter_get_fixed_array() on the + * "sub-iterator" created by dbus_message_iter_recurse()). + * + * The value argument should be the address of a location to store the + * returned array. So for int32 it should be a "const dbus_int32_t**" + * The returned value is by reference and should not be freed. + * + * This function should only be used if dbus_type_is_fixed() returns + * #TRUE for the element type. + * + * If an array's elements are not fixed in size, you have to recurse + * into the array with dbus_message_iter_recurse() and read the + * elements one by one. + * + * Because the array is not copied, this function runs in constant + * time and is fast; it's much preferred over walking the entire array + * with an iterator. (However, you can always use + * dbus_message_iter_recurse(), even for fixed-length types; + * dbus_message_iter_get_fixed_array() is just an optimization.) + * + * @param iter the iterator + * @param value location to store the block + * @param n_elements number of elements in the block + */ +void +dbus_message_iter_get_fixed_array (DBusMessageIter *iter, + void *value, + int *n_elements) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; +#ifndef DBUS_DISABLE_CHECKS + int subtype = _dbus_type_reader_get_current_type(&real->u.reader); + + _dbus_return_if_fail (_dbus_message_iter_check (real)); + _dbus_return_if_fail (value != NULL); + _dbus_return_if_fail ((subtype == DBUS_TYPE_INVALID) || + (dbus_type_is_fixed (subtype) && subtype != DBUS_TYPE_UNIX_FD)); +#endif + + _dbus_type_reader_read_fixed_multi (&real->u.reader, + value, n_elements); +} + +/** + * Initializes a #DBusMessageIter for appending arguments to the end + * of a message. + * + * @todo If appending any of the arguments fails due to lack of + * memory, the message is hosed and you have to start over building + * the whole message. + * + * @param message the message + * @param iter pointer to an iterator to initialize + */ +void +dbus_message_iter_init_append (DBusMessage *message, + DBusMessageIter *iter) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + + _dbus_return_if_fail (message != NULL); + _dbus_return_if_fail (iter != NULL); + + _dbus_message_iter_init_common (message, real, + DBUS_MESSAGE_ITER_TYPE_WRITER); + + /* We create the signature string and point iterators at it "on demand" + * when a value is actually appended. That means that init() never fails + * due to OOM. + */ + _dbus_type_writer_init_types_delayed (&real->u.writer, + _dbus_header_get_byte_order (&message->header), + &message->body, + _dbus_string_get_length (&message->body)); +} + +/** + * Creates a temporary signature string containing the current + * signature, stores it in the iterator, and points the iterator to + * the end of it. Used any time we write to the message. + * + * @param real an iterator without a type_str + * @returns #FALSE if no memory + */ +static dbus_bool_t +_dbus_message_iter_open_signature (DBusMessageRealIter *real) +{ + DBusString *str; + const DBusString *current_sig; + int current_sig_pos; + + _dbus_assert (real->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER); + + if (real->u.writer.type_str != NULL) + { + _dbus_assert (real->sig_refcount > 0); + real->sig_refcount += 1; + return TRUE; + } + + str = dbus_new (DBusString, 1); + if (str == NULL) + return FALSE; + + if (!_dbus_header_get_field_raw (&real->message->header, + DBUS_HEADER_FIELD_SIGNATURE, + ¤t_sig, ¤t_sig_pos)) + current_sig = NULL; + + if (current_sig) + { + int current_len; + + current_len = _dbus_string_get_byte (current_sig, current_sig_pos); + current_sig_pos += 1; /* move on to sig data */ + + if (!_dbus_string_init_preallocated (str, current_len + 4)) + { + dbus_free (str); + return FALSE; + } + + if (!_dbus_string_copy_len (current_sig, current_sig_pos, current_len, + str, 0)) + { + _dbus_string_free (str); + dbus_free (str); + return FALSE; + } + } + else + { + if (!_dbus_string_init_preallocated (str, 4)) + { + dbus_free (str); + return FALSE; + } + } + + real->sig_refcount = 1; + + /* If this assertion failed, then str would be neither stored in u.writer + * nor freed by this function, resulting in a memory leak. */ + _dbus_assert (real->u.writer.type_str == NULL); + _dbus_type_writer_add_types (&real->u.writer, + str, _dbus_string_get_length (str)); + return TRUE; +} + +/** + * Sets the new signature as the message signature, frees the + * signature string, and marks the iterator as not having a type_str + * anymore. Frees the signature even if it fails, so you can't + * really recover from failure. Kinda busted. + * + * @param real an iterator without a type_str + * @returns #FALSE if no memory + */ +static dbus_bool_t +_dbus_message_iter_close_signature (DBusMessageRealIter *real) +{ + DBusString *str; + const char *v_STRING; + dbus_bool_t retval; + + _dbus_assert (real->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER); + _dbus_assert (real->u.writer.type_str != NULL); + _dbus_assert (real->sig_refcount > 0); + + real->sig_refcount -= 1; + + if (real->sig_refcount > 0) + return TRUE; + _dbus_assert (real->sig_refcount == 0); + + retval = TRUE; + + str = real->u.writer.type_str; + + v_STRING = _dbus_string_get_const_data (str); + if (!_dbus_header_set_field_basic (&real->message->header, + DBUS_HEADER_FIELD_SIGNATURE, + DBUS_TYPE_SIGNATURE, + &v_STRING)) + retval = FALSE; + + _dbus_type_writer_remove_types (&real->u.writer); + _dbus_string_free (str); + dbus_free (str); + + return retval; +} + +/** + * Frees the signature string and marks the iterator as not having a + * type_str anymore. Since the new signature is not set, the message + * will generally be hosed after this is called. + * + * @param real an iterator without a type_str + */ +static void +_dbus_message_iter_abandon_signature (DBusMessageRealIter *real) +{ + DBusString *str; + + _dbus_assert (real->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER); + _dbus_assert (real->u.writer.type_str != NULL); + _dbus_assert (real->sig_refcount > 0); + + real->sig_refcount -= 1; + + if (real->sig_refcount > 0) + return; + _dbus_assert (real->sig_refcount == 0); + + str = real->u.writer.type_str; + + _dbus_type_writer_remove_types (&real->u.writer); + _dbus_string_free (str); + dbus_free (str); +} + +#if defined(DBUS_ENABLE_CHECKS) || defined(DBUS_ENABLE_ASSERT) +static dbus_bool_t +_dbus_message_iter_append_check (DBusMessageRealIter *iter) +{ + if (!_dbus_message_iter_check (iter)) + return FALSE; + + if (iter->message->locked) + { + _dbus_warn_check_failed ("dbus append iterator can't be used: message is locked (has already been sent)"); + return FALSE; + } + + return TRUE; +} +#endif + +#ifdef HAVE_UNIX_FD_PASSING +static int * +expand_fd_array(DBusMessage *m, + unsigned n) +{ + _dbus_assert(m); + + /* This makes space for adding n new fds to the array and returns a + pointer to the place were the first fd should be put. */ + + if (m->n_unix_fds + n > m->n_unix_fds_allocated) + { + unsigned k; + int *p; + + /* Make twice as much space as necessary */ + k = (m->n_unix_fds + n) * 2; + + /* Allocate at least four */ + if (k < 4) + k = 4; + + p = dbus_realloc(m->unix_fds, k * sizeof(int)); + if (p == NULL) + return NULL; + + m->unix_fds = p; + m->n_unix_fds_allocated = k; + } + + return m->unix_fds + m->n_unix_fds; +} +#endif + +/** + * Appends a basic-typed value to the message. The basic types are the + * non-container types such as integer and string. + * + * The "value" argument should be the address of a basic-typed value. + * So for string, const char**. For integer, dbus_int32_t*. + * + * For Unix file descriptors this function will internally duplicate + * the descriptor you passed in. Hence you may close the descriptor + * immediately after this call. + * + * @todo If this fails due to lack of memory, the message is hosed and + * you have to start over building the whole message. + * + * @param iter the append iterator + * @param type the type of the value + * @param value the address of the value + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_iter_append_basic (DBusMessageIter *iter, + int type, + const void *value) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + dbus_bool_t ret; + + _dbus_return_val_if_fail (_dbus_message_iter_append_check (real), FALSE); + _dbus_return_val_if_fail (real->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER, FALSE); + _dbus_return_val_if_fail (dbus_type_is_basic (type), FALSE); + _dbus_return_val_if_fail (value != NULL, FALSE); + +#ifndef DBUS_DISABLE_CHECKS + switch (type) + { + DBusString str; + DBusValidity signature_validity; + const char * const *string_p; + const dbus_bool_t *bool_p; + + case DBUS_TYPE_STRING: + string_p = value; + _dbus_return_val_if_fail (_dbus_check_is_valid_utf8 (*string_p), FALSE); + break; + + case DBUS_TYPE_OBJECT_PATH: + string_p = value; + _dbus_return_val_if_fail (_dbus_check_is_valid_path (*string_p), FALSE); + break; + + case DBUS_TYPE_SIGNATURE: + string_p = value; + _dbus_string_init_const (&str, *string_p); + signature_validity = _dbus_validate_signature_with_reason (&str, + 0, + _dbus_string_get_length (&str)); + + if (signature_validity == DBUS_VALIDITY_UNKNOWN_OOM_ERROR) + return FALSE; + + _dbus_return_val_if_fail (signature_validity == DBUS_VALID, FALSE); + break; + + case DBUS_TYPE_BOOLEAN: + bool_p = value; + _dbus_return_val_if_fail (*bool_p == 0 || *bool_p == 1, FALSE); + break; + + default: + { + /* nothing to check, all possible values are allowed */ + } + } +#endif + + if (!_dbus_message_iter_open_signature (real)) + return FALSE; + + if (type == DBUS_TYPE_UNIX_FD) + { +#ifdef HAVE_UNIX_FD_PASSING + int *fds; + dbus_uint32_t u; + + ret = FALSE; + + /* First step, include the fd in the fd list of this message */ + if (!(fds = expand_fd_array(real->message, 1))) + goto out; + + *fds = _dbus_dup(*(int*) value, NULL); + if (*fds < 0) + goto out; + + u = real->message->n_unix_fds; + + /* Second step, write the index to the fd */ + if (!(ret = _dbus_type_writer_write_basic (&real->u.writer, DBUS_TYPE_UNIX_FD, &u))) { + _dbus_close(*fds, NULL); + goto out; + } + + real->message->n_unix_fds += 1; + u += 1; + + /* Final step, update the header accordingly */ + ret = _dbus_header_set_field_basic (&real->message->header, + DBUS_HEADER_FIELD_UNIX_FDS, + DBUS_TYPE_UINT32, + &u); + + /* If any of these operations fail the message is + hosed. However, no memory or fds should be leaked since what + has been added to message has been added to the message, and + can hence be accounted for when the message is being + freed. */ +#else + ret = FALSE; + /* This is redundant (we could just fall through), but it avoids + * -Wunused-label in builds that don't HAVE_UNIX_FD_PASSING */ + goto out; +#endif + } + else + { + ret = _dbus_type_writer_write_basic (&real->u.writer, type, value); + } + +out: + if (!_dbus_message_iter_close_signature (real)) + ret = FALSE; + + return ret; +} + +/** + * Appends a block of fixed-length values to an array. The + * fixed-length types are all basic types that are not string-like. So + * int32, double, bool, etc. (Unix file descriptors however are not + * supported.) You must call dbus_message_iter_open_container() to + * open an array of values before calling this function. You may call + * this function multiple times (and intermixed with calls to + * dbus_message_iter_append_basic()) for the same array. + * + * The "value" argument should be the address of the array. So for + * integer, "dbus_int32_t**" is expected for example. + * + * @warning in C, given "int array[]", "&array == array" (the + * comp.lang.c FAQ says otherwise, but gcc and the FAQ don't agree). + * So if you're using an array instead of a pointer you have to create + * a pointer variable, assign the array to it, then take the address + * of the pointer variable. + * @code + * const dbus_int32_t array[] = { 1, 2, 3 }; + * const dbus_int32_t *v_ARRAY = array; + * if (!dbus_message_iter_append_fixed_array (&iter, DBUS_TYPE_INT32, &v_ARRAY, 3)) + * fprintf (stderr, "No memory!\n"); + * @endcode + * For strings it works to write const char *array = "Hello" and then + * use &array though. + * + * @todo If this fails due to lack of memory, the message is hosed and + * you have to start over building the whole message. + * + * @param iter the append iterator + * @param element_type the type of the array elements + * @param value the address of the array + * @param n_elements the number of elements to append + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_iter_append_fixed_array (DBusMessageIter *iter, + int element_type, + const void *value, + int n_elements) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + dbus_bool_t ret; + + _dbus_return_val_if_fail (_dbus_message_iter_append_check (real), FALSE); + _dbus_return_val_if_fail (real->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER, FALSE); + _dbus_return_val_if_fail (dbus_type_is_fixed (element_type) && element_type != DBUS_TYPE_UNIX_FD, FALSE); + _dbus_return_val_if_fail (real->u.writer.container_type == DBUS_TYPE_ARRAY, FALSE); + _dbus_return_val_if_fail (value != NULL, FALSE); + _dbus_return_val_if_fail (n_elements >= 0, FALSE); + _dbus_return_val_if_fail (n_elements <= + DBUS_MAXIMUM_ARRAY_LENGTH / _dbus_type_get_alignment (element_type), + FALSE); + +#ifndef DBUS_DISABLE_CHECKS + if (element_type == DBUS_TYPE_BOOLEAN) + { + const dbus_bool_t * const *bools = value; + int i; + + for (i = 0; i < n_elements; i++) + { + _dbus_return_val_if_fail ((*bools)[i] == 0 || (*bools)[i] == 1, FALSE); + } + } +#endif + + ret = _dbus_type_writer_write_fixed_multi (&real->u.writer, element_type, value, n_elements); + + return ret; +} + +/** + * Appends a container-typed value to the message. On success, you are + * required to append the contents of the container using the returned + * sub-iterator, and then call + * dbus_message_iter_close_container(). Container types are for + * example struct, variant, and array. For variants, the + * contained_signature should be the type of the single value inside + * the variant. For structs and dict entries, contained_signature + * should be #NULL; it will be set to whatever types you write into + * the struct. For arrays, contained_signature should be the type of + * the array elements. + * + * @todo If this fails due to lack of memory, the message is hosed and + * you have to start over building the whole message. + * + * If this function fails, the sub-iterator remains invalid, and must + * not be closed with dbus_message_iter_close_container() or abandoned + * with dbus_message_iter_abandon_container(). However, after this + * function has either succeeded or failed, it is valid to call + * dbus_message_iter_abandon_container_if_open(). + * + * @param iter the append iterator + * @param type the type of the value + * @param contained_signature the type of container contents + * @param sub sub-iterator to initialize + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_iter_open_container (DBusMessageIter *iter, + int type, + const char *contained_signature, + DBusMessageIter *sub) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + DBusMessageRealIter *real_sub = (DBusMessageRealIter *)sub; + DBusString contained_str; + DBusValidity contained_signature_validity; + dbus_bool_t ret; + + _dbus_return_val_if_fail (sub != NULL, FALSE); + /* Do our best to make sure the sub-iterator doesn't contain something + * valid-looking on failure */ + _dbus_message_real_iter_zero (real_sub); + + _dbus_return_val_if_fail (_dbus_message_iter_append_check (real), FALSE); + _dbus_return_val_if_fail (real->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER, FALSE); + _dbus_return_val_if_fail (dbus_type_is_container (type), FALSE); + _dbus_return_val_if_fail ((type == DBUS_TYPE_STRUCT && + contained_signature == NULL) || + (type == DBUS_TYPE_DICT_ENTRY && + contained_signature == NULL) || + (type == DBUS_TYPE_VARIANT && + contained_signature != NULL) || + (type == DBUS_TYPE_ARRAY && + contained_signature != NULL), FALSE); + + /* this would fail if the contained_signature is a dict entry, since + * dict entries are invalid signatures standalone (they must be in + * an array) + */ + if (contained_signature != NULL) + { + _dbus_string_init_const (&contained_str, contained_signature); + contained_signature_validity = _dbus_validate_signature_with_reason (&contained_str, + 0, + _dbus_string_get_length (&contained_str)); + + if (contained_signature_validity == DBUS_VALIDITY_UNKNOWN_OOM_ERROR) + return FALSE; + } + else + { + /* just some placeholder value */ + contained_signature_validity = DBUS_VALID_BUT_INCOMPLETE; + } + + _dbus_return_val_if_fail ((type == DBUS_TYPE_ARRAY && contained_signature && *contained_signature == DBUS_DICT_ENTRY_BEGIN_CHAR) || + contained_signature == NULL || + contained_signature_validity == DBUS_VALID, + FALSE); + + if (!_dbus_message_iter_open_signature (real)) + return FALSE; + + ret = FALSE; + *real_sub = *real; + + if (contained_signature != NULL) + { + _dbus_string_init_const (&contained_str, contained_signature); + + ret = _dbus_type_writer_recurse (&real->u.writer, + type, + &contained_str, 0, + &real_sub->u.writer); + } + else + { + ret = _dbus_type_writer_recurse (&real->u.writer, + type, + NULL, 0, + &real_sub->u.writer); + } + + if (!ret) + _dbus_message_iter_abandon_signature (real); + + return ret; +} + + +/** + * Closes a container-typed value appended to the message; may write + * out more information to the message known only after the entire + * container is written, and may free resources created by + * dbus_message_iter_open_container(). + * + * Even if this function fails due to lack of memory, the sub-iterator sub + * has been closed and invalidated. It must not be closed again with this + * function, or abandoned with dbus_message_iter_abandon_container(). + * However, it remains valid to call + * dbus_message_iter_abandon_container_if_open(). + * + * @todo If this fails due to lack of memory, the message is hosed and + * you have to start over building the whole message. + * + * @param iter the append iterator + * @param sub sub-iterator to close + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_iter_close_container (DBusMessageIter *iter, + DBusMessageIter *sub) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + DBusMessageRealIter *real_sub = (DBusMessageRealIter *)sub; + dbus_bool_t ret; + + _dbus_return_val_if_fail (_dbus_message_iter_append_check (real), FALSE); + _dbus_return_val_if_fail (real->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER, FALSE); + _dbus_return_val_if_fail (_dbus_message_iter_append_check (real_sub), FALSE); + _dbus_return_val_if_fail (real_sub->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER, FALSE); + + ret = _dbus_type_writer_unrecurse (&real->u.writer, + &real_sub->u.writer); + _dbus_message_real_iter_zero (real_sub); + + if (!_dbus_message_iter_close_signature (real)) + ret = FALSE; + + return ret; +} + +/** + * Abandons creation of a contained-typed value and frees resources created + * by dbus_message_iter_open_container(). Once this returns, the message + * is hosed and you have to start over building the whole message. + * + * This should only be used to abandon creation of a message when you have + * open containers. + * + * @param iter the append iterator + * @param sub sub-iterator to close + */ +void +dbus_message_iter_abandon_container (DBusMessageIter *iter, + DBusMessageIter *sub) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + DBusMessageRealIter *real_sub = (DBusMessageRealIter *)sub; + +#ifndef DBUS_DISABLE_CHECKS + _dbus_return_if_fail (_dbus_message_iter_append_check (real)); + _dbus_return_if_fail (real->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER); + _dbus_return_if_fail (_dbus_message_iter_append_check (real_sub)); + _dbus_return_if_fail (real_sub->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER); +#endif + + _dbus_message_iter_abandon_signature (real); + _dbus_message_real_iter_zero (real_sub); +} + +/** + * Abandons creation of a contained-typed value and frees resources created + * by dbus_message_iter_open_container(). Once this returns, the message + * is hosed and you have to start over building the whole message. + * + * Unlike dbus_message_iter_abandon_container(), it is valid to call this + * function on an iterator that was initialized with + * #DBUS_MESSAGE_ITER_INIT_CLOSED, or an iterator that was already closed + * or abandoned. However, it is not valid to call this function on + * uninitialized memory. This is intended to be used in error cleanup + * code paths, similar to this pattern: + * + * DBusMessageIter outer = DBUS_MESSAGE_ITER_INIT_CLOSED; + * DBusMessageIter inner = DBUS_MESSAGE_ITER_INIT_CLOSED; + * dbus_bool_t result = FALSE; + * + * if (!dbus_message_iter_open_container (iter, ..., &outer)) + * goto out; + * + * if (!dbus_message_iter_open_container (&outer, ..., &inner)) + * goto out; + * + * if (!dbus_message_iter_append_basic (&inner, ...)) + * goto out; + * + * if (!dbus_message_iter_close_container (&outer, ..., &inner)) + * goto out; + * + * if (!dbus_message_iter_close_container (iter, ..., &outer)) + * goto out; + * + * result = TRUE; + * + * out: + * dbus_message_iter_abandon_container_if_open (&outer, &inner); + * dbus_message_iter_abandon_container_if_open (iter, &outer); + * return result; + * + * @param iter the append iterator + * @param sub sub-iterator to close + */ +void +dbus_message_iter_abandon_container_if_open (DBusMessageIter *iter, + DBusMessageIter *sub) +{ + DBusMessageRealIter *real = (DBusMessageRealIter *)iter; + DBusMessageRealIter *real_sub = (DBusMessageRealIter *)sub; + + /* If both the parent and the child are zeroed out, then either we didn't + * even get as far as successfully recursing into the parent, or we already + * closed both the child and the parent. For example, in the code sample + * in the doc-comment above, this happens for + * abandon_container_if_open (&outer, &inner) if the first open_container + * call failed, or if we reached result = TRUE and fell through. */ + if (_dbus_message_real_iter_is_zeroed (real) && + _dbus_message_real_iter_is_zeroed (real_sub)) + return; + +#ifndef DBUS_DISABLE_CHECKS + /* If the child is not zeroed out, but the parent is, then something has + * gone horribly wrong (in practice that would probably mean both are + * uninitialized or corrupt, and the parent happens to have ended up + * all-bytes-zero). */ + _dbus_return_if_fail (_dbus_message_iter_append_check (real)); + _dbus_return_if_fail (real->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER); +#endif + + /* If the parent is not zeroed out, but the child is, then either we did + * not successfully open the child, or we already closed the child. This + * means we do not own a reference to the parent's signature, so it would + * be wrong to release it; so we must not call abandon_signature() here. + * In the code sample in the doc-comment above, this happens for + * abandon_container_if_open (&outer, &inner) if the second open_container + * call failed, or if the second close_container call failed. */ + if (_dbus_message_real_iter_is_zeroed (real_sub)) + return; + +#ifndef DBUS_DISABLE_CHECKS + _dbus_return_if_fail (_dbus_message_iter_append_check (real_sub)); + _dbus_return_if_fail (real_sub->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER); +#endif + + /* If neither the parent nor the child is zeroed out, then we genuinely + * have an open container; close it. In the code sample in the doc-comment, + * this happens for abandon_container_if_open (&outer, &inner) if the + * append_basic call failed. */ + _dbus_message_iter_abandon_signature (real); + _dbus_message_real_iter_zero (real_sub); +} + +/** + * Sets a flag indicating that the message does not want a reply; if + * this flag is set, the other end of the connection may (but is not + * required to) optimize by not sending method return or error + * replies. If this flag is set, there is no way to know whether the + * message successfully arrived at the remote end. Normally you know a + * message was received when you receive the reply to it. + * + * The flag is #FALSE by default, that is by default the other end is + * required to reply. + * + * On the protocol level this toggles #DBUS_HEADER_FLAG_NO_REPLY_EXPECTED + * + * @param message the message + * @param no_reply #TRUE if no reply is desired + */ +void +dbus_message_set_no_reply (DBusMessage *message, + dbus_bool_t no_reply) +{ + _dbus_return_if_fail (message != NULL); + _dbus_return_if_fail (!message->locked); + + _dbus_header_toggle_flag (&message->header, + DBUS_HEADER_FLAG_NO_REPLY_EXPECTED, + no_reply); +} + +/** + * Returns #TRUE if the message does not expect + * a reply. + * + * @param message the message + * @returns #TRUE if the message sender isn't waiting for a reply + */ +dbus_bool_t +dbus_message_get_no_reply (DBusMessage *message) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + + return _dbus_header_get_flag (&message->header, + DBUS_HEADER_FLAG_NO_REPLY_EXPECTED); +} + +/** + * Sets a flag indicating that an owner for the destination name will + * be automatically started before the message is delivered. When this + * flag is set, the message is held until a name owner finishes + * starting up, or fails to start up. In case of failure, the reply + * will be an error. + * + * The flag is set to #TRUE by default, i.e. auto starting is the default. + * + * On the protocol level this toggles #DBUS_HEADER_FLAG_NO_AUTO_START + * + * @param message the message + * @param auto_start #TRUE if auto-starting is desired + */ +void +dbus_message_set_auto_start (DBusMessage *message, + dbus_bool_t auto_start) +{ + _dbus_return_if_fail (message != NULL); + _dbus_return_if_fail (!message->locked); + + _dbus_header_toggle_flag (&message->header, + DBUS_HEADER_FLAG_NO_AUTO_START, + !auto_start); +} + +/** + * Returns #TRUE if the message will cause an owner for + * destination name to be auto-started. + * + * @param message the message + * @returns #TRUE if the message will use auto-start + */ +dbus_bool_t +dbus_message_get_auto_start (DBusMessage *message) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + + return !_dbus_header_get_flag (&message->header, + DBUS_HEADER_FLAG_NO_AUTO_START); +} + + +/** + * Sets the object path this message is being sent to (for + * DBUS_MESSAGE_TYPE_METHOD_CALL) or the one a signal is being + * emitted from (for DBUS_MESSAGE_TYPE_SIGNAL). + * + * The path must contain only valid characters as defined + * in the D-Bus specification. + * + * @param message the message + * @param object_path the path or #NULL to unset + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_set_path (DBusMessage *message, + const char *object_path) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (!message->locked, FALSE); + _dbus_return_val_if_fail (object_path == NULL || + _dbus_check_is_valid_path (object_path), + FALSE); + + return set_or_delete_string_field (message, + DBUS_HEADER_FIELD_PATH, + DBUS_TYPE_OBJECT_PATH, + object_path); +} + +/** + * Gets the object path this message is being sent to (for + * DBUS_MESSAGE_TYPE_METHOD_CALL) or being emitted from (for + * DBUS_MESSAGE_TYPE_SIGNAL). Returns #NULL if none. + * + * See also dbus_message_get_path_decomposed(). + * + * The returned string becomes invalid if the message is + * modified, since it points into the wire-marshaled message data. + * + * @param message the message + * @returns the path (should not be freed) or #NULL + */ +const char* +dbus_message_get_path (DBusMessage *message) +{ + const char *v; + + _dbus_return_val_if_fail (message != NULL, NULL); + + v = NULL; /* in case field doesn't exist */ + _dbus_header_get_field_basic (&message->header, + DBUS_HEADER_FIELD_PATH, + DBUS_TYPE_OBJECT_PATH, + (void *) &v); + return v; +} + +/** + * Checks if the message has a particular object path. The object + * path is the destination object for a method call or the emitting + * object for a signal. + * + * @param message the message + * @param path the path name + * @returns #TRUE if there is a path field in the header + */ +dbus_bool_t +dbus_message_has_path (DBusMessage *message, + const char *path) +{ + const char *msg_path; + msg_path = dbus_message_get_path (message); + + if (msg_path == NULL) + { + if (path == NULL) + return TRUE; + else + return FALSE; + } + + if (path == NULL) + return FALSE; + + if (strcmp (msg_path, path) == 0) + return TRUE; + + return FALSE; +} + +/** + * Gets the object path this message is being sent to + * (for DBUS_MESSAGE_TYPE_METHOD_CALL) or being emitted + * from (for DBUS_MESSAGE_TYPE_SIGNAL) in a decomposed + * format (one array element per path component). + * Free the returned array with dbus_free_string_array(). + * + * An empty but non-NULL path array means the path "/". + * So the path "/foo/bar" becomes { "foo", "bar", NULL } + * and the path "/" becomes { NULL }. + * + * See also dbus_message_get_path(). + * + * @todo this could be optimized by using the len from the message + * instead of calling strlen() again + * + * @param message the message + * @param path place to store allocated array of path components; #NULL set here if no path field exists + * @returns #FALSE if no memory to allocate the array + */ +dbus_bool_t +dbus_message_get_path_decomposed (DBusMessage *message, + char ***path) +{ + const char *v; + + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (path != NULL, FALSE); + + *path = NULL; + + v = dbus_message_get_path (message); + if (v != NULL) + { + if (!_dbus_decompose_path (v, strlen (v), + path, NULL)) + return FALSE; + } + return TRUE; +} + +/** + * Sets the interface this message is being sent to + * (for DBUS_MESSAGE_TYPE_METHOD_CALL) or + * the interface a signal is being emitted from + * (for DBUS_MESSAGE_TYPE_SIGNAL). + * + * The interface name must contain only valid characters as defined + * in the D-Bus specification. + * + * @param message the message + * @param iface the interface or #NULL to unset + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_set_interface (DBusMessage *message, + const char *iface) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (!message->locked, FALSE); + _dbus_return_val_if_fail (iface == NULL || + _dbus_check_is_valid_interface (iface), + FALSE); + + return set_or_delete_string_field (message, + DBUS_HEADER_FIELD_INTERFACE, + DBUS_TYPE_STRING, + iface); +} + +/** + * Gets the interface this message is being sent to + * (for DBUS_MESSAGE_TYPE_METHOD_CALL) or being emitted + * from (for DBUS_MESSAGE_TYPE_SIGNAL). + * The interface name is fully-qualified (namespaced). + * Returns #NULL if none. + * + * The returned string becomes invalid if the message is + * modified, since it points into the wire-marshaled message data. + * + * @param message the message + * @returns the message interface (should not be freed) or #NULL + */ +const char* +dbus_message_get_interface (DBusMessage *message) +{ + const char *v; + + _dbus_return_val_if_fail (message != NULL, NULL); + + v = NULL; /* in case field doesn't exist */ + _dbus_header_get_field_basic (&message->header, + DBUS_HEADER_FIELD_INTERFACE, + DBUS_TYPE_STRING, + (void *) &v); + return v; +} + +/** + * Checks if the message has an interface + * + * @param message the message + * @param iface the interface name + * @returns #TRUE if the interface field in the header matches + */ +dbus_bool_t +dbus_message_has_interface (DBusMessage *message, + const char *iface) +{ + const char *msg_interface; + msg_interface = dbus_message_get_interface (message); + + if (msg_interface == NULL) + { + if (iface == NULL) + return TRUE; + else + return FALSE; + } + + if (iface == NULL) + return FALSE; + + if (strcmp (msg_interface, iface) == 0) + return TRUE; + + return FALSE; + +} + +/** + * Sets the interface member being invoked + * (DBUS_MESSAGE_TYPE_METHOD_CALL) or emitted + * (DBUS_MESSAGE_TYPE_SIGNAL). + * + * The member name must contain only valid characters as defined + * in the D-Bus specification. + * + * @param message the message + * @param member the member or #NULL to unset + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_set_member (DBusMessage *message, + const char *member) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (!message->locked, FALSE); + _dbus_return_val_if_fail (member == NULL || + _dbus_check_is_valid_member (member), + FALSE); + + return set_or_delete_string_field (message, + DBUS_HEADER_FIELD_MEMBER, + DBUS_TYPE_STRING, + member); +} + +/** + * Gets the interface member being invoked + * (DBUS_MESSAGE_TYPE_METHOD_CALL) or emitted + * (DBUS_MESSAGE_TYPE_SIGNAL). Returns #NULL if none. + * + * The returned string becomes invalid if the message is + * modified, since it points into the wire-marshaled message data. + * + * @param message the message + * @returns the member name (should not be freed) or #NULL + */ +const char* +dbus_message_get_member (DBusMessage *message) +{ + const char *v; + + _dbus_return_val_if_fail (message != NULL, NULL); + + v = NULL; /* in case field doesn't exist */ + _dbus_header_get_field_basic (&message->header, + DBUS_HEADER_FIELD_MEMBER, + DBUS_TYPE_STRING, + (void *) &v); + return v; +} + +/** + * Checks if the message has an interface member + * + * @param message the message + * @param member the member name + * @returns #TRUE if there is a member field in the header + */ +dbus_bool_t +dbus_message_has_member (DBusMessage *message, + const char *member) +{ + const char *msg_member; + msg_member = dbus_message_get_member (message); + + if (msg_member == NULL) + { + if (member == NULL) + return TRUE; + else + return FALSE; + } + + if (member == NULL) + return FALSE; + + if (strcmp (msg_member, member) == 0) + return TRUE; + + return FALSE; + +} + +/** + * Sets the name of the error (DBUS_MESSAGE_TYPE_ERROR). + * The name is fully-qualified (namespaced). + * + * The error name must contain only valid characters as defined + * in the D-Bus specification. + * + * @param message the message + * @param error_name the name or #NULL to unset + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_set_error_name (DBusMessage *message, + const char *error_name) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (!message->locked, FALSE); + _dbus_return_val_if_fail (error_name == NULL || + _dbus_check_is_valid_error_name (error_name), + FALSE); + + return set_or_delete_string_field (message, + DBUS_HEADER_FIELD_ERROR_NAME, + DBUS_TYPE_STRING, + error_name); +} + +/** + * Gets the error name (DBUS_MESSAGE_TYPE_ERROR only) + * or #NULL if none. + * + * The returned string becomes invalid if the message is + * modified, since it points into the wire-marshaled message data. + * + * @param message the message + * @returns the error name (should not be freed) or #NULL + */ +const char* +dbus_message_get_error_name (DBusMessage *message) +{ + const char *v; + + _dbus_return_val_if_fail (message != NULL, NULL); + + v = NULL; /* in case field doesn't exist */ + _dbus_header_get_field_basic (&message->header, + DBUS_HEADER_FIELD_ERROR_NAME, + DBUS_TYPE_STRING, + (void *) &v); + return v; +} + +/** + * Sets the message's destination. The destination is the name of + * another connection on the bus and may be either the unique name + * assigned by the bus to each connection, or a well-known name + * specified in advance. + * + * The destination name must contain only valid characters as defined + * in the D-Bus specification. + * + * @param message the message + * @param destination the destination name or #NULL to unset + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_set_destination (DBusMessage *message, + const char *destination) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (!message->locked, FALSE); + _dbus_return_val_if_fail (destination == NULL || + _dbus_check_is_valid_bus_name (destination), + FALSE); + + return set_or_delete_string_field (message, + DBUS_HEADER_FIELD_DESTINATION, + DBUS_TYPE_STRING, + destination); +} + +/** + * Gets the destination of a message or #NULL if there is none set. + * + * The returned string becomes invalid if the message is + * modified, since it points into the wire-marshaled message data. + * + * @param message the message + * @returns the message destination (should not be freed) or #NULL + */ +const char* +dbus_message_get_destination (DBusMessage *message) +{ + const char *v; + + _dbus_return_val_if_fail (message != NULL, NULL); + + v = NULL; /* in case field doesn't exist */ + _dbus_header_get_field_basic (&message->header, + DBUS_HEADER_FIELD_DESTINATION, + DBUS_TYPE_STRING, + (void *) &v); + return v; +} + +/** + * Sets the message sender. + * + * The sender must be a valid bus name as defined in the D-Bus + * specification. + * + * Usually you don't want to call this. The message bus daemon will + * call it to set the origin of each message. If you aren't implementing + * a message bus daemon you shouldn't need to set the sender. + * + * @param message the message + * @param sender the sender or #NULL to unset + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_set_sender (DBusMessage *message, + const char *sender) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (!message->locked, FALSE); + _dbus_return_val_if_fail (sender == NULL || + _dbus_check_is_valid_bus_name (sender), + FALSE); + + return set_or_delete_string_field (message, + DBUS_HEADER_FIELD_SENDER, + DBUS_TYPE_STRING, + sender); +} + +/** + * Gets the unique name of the connection which originated this + * message, or #NULL if unknown or inapplicable. The sender is filled + * in by the message bus. + * + * Note, the returned sender is always the unique bus name. + * Connections may own multiple other bus names, but those + * are not found in the sender field. + * + * The returned string becomes invalid if the message is + * modified, since it points into the wire-marshaled message data. + * + * @param message the message + * @returns the unique name of the sender or #NULL + */ +const char* +dbus_message_get_sender (DBusMessage *message) +{ + const char *v; + + _dbus_return_val_if_fail (message != NULL, NULL); + + v = NULL; /* in case field doesn't exist */ + _dbus_header_get_field_basic (&message->header, + DBUS_HEADER_FIELD_SENDER, + DBUS_TYPE_STRING, + (void *) &v); + return v; +} + +/** + * Gets the type signature of the message, i.e. the arguments in the + * message payload. The signature includes only "in" arguments for + * #DBUS_MESSAGE_TYPE_METHOD_CALL and only "out" arguments for + * #DBUS_MESSAGE_TYPE_METHOD_RETURN, so is slightly different from + * what you might expect (that is, it does not include the signature of the + * entire C++-style method). + * + * The signature is a string made up of type codes such as + * #DBUS_TYPE_INT32. The string is terminated with nul (nul is also + * the value of #DBUS_TYPE_INVALID). + * + * The returned string becomes invalid if the message is + * modified, since it points into the wire-marshaled message data. + * + * @param message the message + * @returns the type signature + */ +const char* +dbus_message_get_signature (DBusMessage *message) +{ + const DBusString *type_str; + int type_pos; + + _dbus_return_val_if_fail (message != NULL, NULL); + + get_const_signature (&message->header, &type_str, &type_pos); + + return _dbus_string_get_const_data_len (type_str, type_pos, 0); +} + +static dbus_bool_t +_dbus_message_has_type_interface_member (DBusMessage *message, + int type, + const char *iface, + const char *member) +{ + const char *n; + + _dbus_assert (message != NULL); + _dbus_assert (iface != NULL); + _dbus_assert (member != NULL); + + if (dbus_message_get_type (message) != type) + return FALSE; + + /* Optimize by checking the short member name first + * instead of the longer interface name + */ + + n = dbus_message_get_member (message); + + if (n && strcmp (n, member) == 0) + { + n = dbus_message_get_interface (message); + + if (n == NULL || strcmp (n, iface) == 0) + return TRUE; + } + + return FALSE; +} + +/** + * Checks whether the message is a method call with the given + * interface and member fields. If the message is not + * #DBUS_MESSAGE_TYPE_METHOD_CALL, or has a different interface or + * member field, returns #FALSE. If the interface field is missing, + * then it will be assumed equal to the provided interface. The D-Bus + * protocol allows method callers to leave out the interface name. + * + * @param message the message + * @param iface the name to check (must not be #NULL) + * @param method the name to check (must not be #NULL) + * + * @returns #TRUE if the message is the specified method call + */ +dbus_bool_t +dbus_message_is_method_call (DBusMessage *message, + const char *iface, + const char *method) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (iface != NULL, FALSE); + _dbus_return_val_if_fail (method != NULL, FALSE); + /* don't check that interface/method are valid since it would be + * expensive, and not catch many common errors + */ + + return _dbus_message_has_type_interface_member (message, + DBUS_MESSAGE_TYPE_METHOD_CALL, + iface, method); +} + +/** + * Checks whether the message is a signal with the given interface and + * member fields. If the message is not #DBUS_MESSAGE_TYPE_SIGNAL, or + * has a different interface or member field, returns #FALSE. + * + * @param message the message + * @param iface the name to check (must not be #NULL) + * @param signal_name the name to check (must not be #NULL) + * + * @returns #TRUE if the message is the specified signal + */ +dbus_bool_t +dbus_message_is_signal (DBusMessage *message, + const char *iface, + const char *signal_name) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (iface != NULL, FALSE); + _dbus_return_val_if_fail (signal_name != NULL, FALSE); + /* don't check that interface/name are valid since it would be + * expensive, and not catch many common errors + */ + + return _dbus_message_has_type_interface_member (message, + DBUS_MESSAGE_TYPE_SIGNAL, + iface, signal_name); +} + +/** + * Checks whether the message is an error reply with the given error + * name. If the message is not #DBUS_MESSAGE_TYPE_ERROR, or has a + * different name, returns #FALSE. + * + * @param message the message + * @param error_name the name to check (must not be #NULL) + * + * @returns #TRUE if the message is the specified error + */ +dbus_bool_t +dbus_message_is_error (DBusMessage *message, + const char *error_name) +{ + const char *n; + + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (error_name != NULL, FALSE); + /* don't check that error_name is valid since it would be expensive, + * and not catch many common errors + */ + + if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_ERROR) + return FALSE; + + n = dbus_message_get_error_name (message); + + if (n && strcmp (n, error_name) == 0) + return TRUE; + else + return FALSE; +} + +/** + * Checks whether the message was sent to the given name. If the + * message has no destination specified or has a different + * destination, returns #FALSE. + * + * @param message the message + * @param name the name to check (must not be #NULL) + * + * @returns #TRUE if the message has the given destination name + */ +dbus_bool_t +dbus_message_has_destination (DBusMessage *message, + const char *name) +{ + const char *s; + + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (name != NULL, FALSE); + /* don't check that name is valid since it would be expensive, and + * not catch many common errors + */ + + s = dbus_message_get_destination (message); + + if (s && strcmp (s, name) == 0) + return TRUE; + else + return FALSE; +} + +/** + * Checks whether the message has the given unique name as its sender. + * If the message has no sender specified or has a different sender, + * returns #FALSE. Note that a peer application will always have the + * unique name of the connection as the sender. So you can't use this + * function to see whether a sender owned a well-known name. + * + * Messages from the bus itself will have #DBUS_SERVICE_DBUS + * as the sender. + * + * @param message the message + * @param name the name to check (must not be #NULL) + * + * @returns #TRUE if the message has the given sender + */ +dbus_bool_t +dbus_message_has_sender (DBusMessage *message, + const char *name) +{ + const char *s; + + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (name != NULL, FALSE); + /* don't check that name is valid since it would be expensive, and + * not catch many common errors + */ + + s = dbus_message_get_sender (message); + + if (s && strcmp (s, name) == 0) + return TRUE; + else + return FALSE; +} + +/** + * Checks whether the message has the given signature; see + * dbus_message_get_signature() for more details on what the signature + * looks like. + * + * @param message the message + * @param signature typecode array + * @returns #TRUE if message has the given signature +*/ +dbus_bool_t +dbus_message_has_signature (DBusMessage *message, + const char *signature) +{ + const char *s; + + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (signature != NULL, FALSE); + /* don't check that signature is valid since it would be expensive, + * and not catch many common errors + */ + + s = dbus_message_get_signature (message); + + if (s && strcmp (s, signature) == 0) + return TRUE; + else + return FALSE; +} + +/** + * Sets a #DBusError based on the contents of the given + * message. The error is only set if the message + * is an error message, as in #DBUS_MESSAGE_TYPE_ERROR. + * The name of the error is set to the name of the message, + * and the error message is set to the first argument + * if the argument exists and is a string. + * + * The return value indicates whether the error was set (the error is + * set if and only if the message is an error message). So you can + * check for an error reply and convert it to DBusError in one go: + * @code + * if (dbus_set_error_from_message (error, reply)) + * return error; + * else + * process reply; + * @endcode + * + * @param error the error to set + * @param message the message to set it from + * @returns #TRUE if the message had type #DBUS_MESSAGE_TYPE_ERROR + */ +dbus_bool_t +dbus_set_error_from_message (DBusError *error, + DBusMessage *message) +{ + const char *str; + + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_error_is_set (error, FALSE); + + if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_ERROR) + return FALSE; + + str = NULL; + dbus_message_get_args (message, NULL, + DBUS_TYPE_STRING, &str, + DBUS_TYPE_INVALID); + + dbus_set_error (error, dbus_message_get_error_name (message), + str ? "%s" : NULL, str); + + return TRUE; +} + +/** + * Checks whether a message contains unix fds + * + * @param message the message + * @returns #TRUE if the message contains unix fds + */ +dbus_bool_t +dbus_message_contains_unix_fds(DBusMessage *message) +{ +#ifdef HAVE_UNIX_FD_PASSING + _dbus_assert(message); + + return message->n_unix_fds > 0; +#else + return FALSE; +#endif +} + +/** + * Sets the container instance this message was sent from. + * + * The path must contain only valid characters for an object path + * as defined in the D-Bus specification. + * + * @param message the message + * @param object_path the path or #NULL to unset + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_message_set_container_instance (DBusMessage *message, + const char *object_path) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (!message->locked, FALSE); + _dbus_return_val_if_fail (object_path == NULL || + _dbus_check_is_valid_path (object_path), + FALSE); + + return set_or_delete_string_field (message, + DBUS_HEADER_FIELD_CONTAINER_INSTANCE, + DBUS_TYPE_OBJECT_PATH, + object_path); +} + +/** + * Gets the container instance this message was sent from, or #NULL + * if none. + * + * The returned string becomes invalid if the message is + * modified, since it points into the wire-marshaled message data. + * + * @param message the message + * @returns the path (should not be freed) or #NULL + */ +const char * +dbus_message_get_container_instance (DBusMessage *message) +{ + const char *v; + + _dbus_return_val_if_fail (message != NULL, NULL); + + v = NULL; /* in case field doesn't exist */ + _dbus_header_get_field_basic (&message->header, + DBUS_HEADER_FIELD_CONTAINER_INSTANCE, + DBUS_TYPE_OBJECT_PATH, + (void *) &v); + return v; +} + +/** @} */ + +/** + * @addtogroup DBusMessageInternals + * + * @{ + */ + +/** + * The initial buffer size of the message loader. + * + * @todo this should be based on min header size plus some average + * body size, or something. Or rather, the min header size only, if we + * want to try to read only the header, store that in a DBusMessage, + * then read only the body and store that, etc., depends on + * how we optimize _dbus_message_loader_get_buffer() and what + * the exact message format is. + */ +#define INITIAL_LOADER_DATA_LEN 32 + +/** + * Creates a new message loader. Returns #NULL if memory can't + * be allocated. + * + * @returns new loader, or #NULL. + */ +DBusMessageLoader* +_dbus_message_loader_new (void) +{ + DBusMessageLoader *loader; + + loader = dbus_new0 (DBusMessageLoader, 1); + if (loader == NULL) + return NULL; + + loader->refcount = 1; + + loader->corrupted = FALSE; + loader->corruption_reason = DBUS_VALID; + + /* this can be configured by the app, but defaults to the protocol max */ + loader->max_message_size = DBUS_MAXIMUM_MESSAGE_LENGTH; + + /* We set a very relatively conservative default here since due to how + SCM_RIGHTS works we need to preallocate an fd array of the maximum + number of unix fds we want to receive in advance. A + try-and-reallocate loop is not possible. */ + loader->max_message_unix_fds = DBUS_DEFAULT_MESSAGE_UNIX_FDS; + + if (!_dbus_string_init (&loader->data)) + { + dbus_free (loader); + return NULL; + } + + /* preallocate the buffer for speed, ignore failure */ + _dbus_string_set_length (&loader->data, INITIAL_LOADER_DATA_LEN); + _dbus_string_set_length (&loader->data, 0); + +#ifdef HAVE_UNIX_FD_PASSING + loader->unix_fds = NULL; + loader->n_unix_fds = loader->n_unix_fds_allocated = 0; + loader->unix_fds_outstanding = FALSE; +#endif + + return loader; +} + +/** + * Increments the reference count of the loader. + * + * @param loader the loader. + * @returns the loader + */ +DBusMessageLoader * +_dbus_message_loader_ref (DBusMessageLoader *loader) +{ + loader->refcount += 1; + + return loader; +} + +/** + * Decrements the reference count of the loader and finalizes the + * loader when the count reaches zero. + * + * @param loader the loader. + */ +void +_dbus_message_loader_unref (DBusMessageLoader *loader) +{ + loader->refcount -= 1; + if (loader->refcount == 0) + { +#ifdef HAVE_UNIX_FD_PASSING + close_unix_fds(loader->unix_fds, &loader->n_unix_fds); + dbus_free(loader->unix_fds); +#endif + _dbus_list_clear_full (&loader->messages, + (DBusFreeFunction) dbus_message_unref); + _dbus_string_free (&loader->data); + dbus_free (loader); + } +} + +/** + * Gets the buffer to use for reading data from the network. Network + * data is read directly into an allocated buffer, which is then used + * in the DBusMessage, to avoid as many extra memcpy's as possible. + * The buffer must always be returned immediately using + * _dbus_message_loader_return_buffer(), even if no bytes are + * successfully read. + * + * @todo this function can be a lot more clever. For example + * it can probably always return a buffer size to read exactly + * the body of the next message, thus avoiding any memory wastage + * or reallocs. + * + * @todo we need to enforce a max length on strings in header fields. + * + * @param loader the message loader. + * @param buffer the buffer + */ +void +_dbus_message_loader_get_buffer (DBusMessageLoader *loader, + DBusString **buffer, + int *max_to_read, + dbus_bool_t *may_read_fds) +{ + _dbus_assert (!loader->buffer_outstanding); + + *buffer = &loader->data; + + loader->buffer_outstanding = TRUE; + + if (max_to_read != NULL) + { +#ifdef HAVE_UNIX_FD_PASSING + int offset = 0; + int remain; + int byte_order; + int fields_array_len; + int header_len; + int body_len; +#endif + + *max_to_read = DBUS_MAXIMUM_MESSAGE_LENGTH; + *may_read_fds = TRUE; + +#ifdef HAVE_UNIX_FD_PASSING + /* If we aren't holding onto any fds, we can read as much as we want + * (fast path). */ + if (loader->n_unix_fds == 0) + return; + + /* Slow path: we have a message with some fds in it. We don't want + * to start on the next message until this one is out of the way; + * otherwise a legitimate sender can keep us processing messages + * containing fds, until we disconnect it for having had fds pending + * for too long, a limit that is in place to stop malicious senders + * from setting up recursive fd-passing that takes up our quota and + * will never go away. */ + + remain = _dbus_string_get_length (&loader->data); + + while (remain > 0) + { + DBusValidity validity = DBUS_VALIDITY_UNKNOWN; + int needed; + + /* If 0 < remain < DBUS_MINIMUM_HEADER_SIZE, then we've had at + * least the first byte of a message, but we don't know how + * much more to read. Only read the rest of the + * DBUS_MINIMUM_HEADER_SIZE for now; then we'll know. */ + if (remain < DBUS_MINIMUM_HEADER_SIZE) + { + *max_to_read = DBUS_MINIMUM_HEADER_SIZE - remain; + *may_read_fds = FALSE; + return; + } + + if (!_dbus_header_have_message_untrusted (loader->max_message_size, + &validity, + &byte_order, + &fields_array_len, + &header_len, + &body_len, + &loader->data, + offset, + remain)) + { + /* If a message in the buffer is invalid, we're going to + * disconnect the sender anyway, so reading an arbitrary amount + * is fine. */ + if (validity != DBUS_VALID) + return; + + /* We have a partial message, with the + * DBUS_MINIMUM_HEADER_SIZE-byte fixed part of the header (which + * lets us work out how much more we need), but no more. Read + * the rest of the message. */ + needed = header_len + body_len; + _dbus_assert (needed > remain); + *max_to_read = needed - remain; + *may_read_fds = FALSE; + return; + } + + /* Skip over entire messages until we have less than a message + * remaining. */ + needed = header_len + body_len; + _dbus_assert (needed > DBUS_MINIMUM_HEADER_SIZE); + _dbus_assert (remain >= needed); + remain -= needed; + offset += needed; + } +#endif + } +} + +/** + * Returns a buffer obtained from _dbus_message_loader_get_buffer(), + * indicating to the loader how many bytes of the buffer were filled + * in. This function must always be called, even if no bytes were + * successfully read. + * + * @param loader the loader. + * @param buffer the buffer. + */ +void +_dbus_message_loader_return_buffer (DBusMessageLoader *loader, + DBusString *buffer) +{ + _dbus_assert (loader->buffer_outstanding); + _dbus_assert (buffer == &loader->data); + + loader->buffer_outstanding = FALSE; +} + +#ifdef HAVE_UNIX_FD_PASSING +/** + * Gets the buffer to use for reading unix fds from the network. + * + * This works similar to _dbus_message_loader_get_buffer() + * + * @param loader the message loader. + * @param fds the array to read fds into + * @param max_n_fds how many fds to read at most + * @return TRUE on success, FALSE on OOM + */ +dbus_bool_t +_dbus_message_loader_get_unix_fds(DBusMessageLoader *loader, + int **fds, + unsigned *max_n_fds) +{ + _dbus_assert (!loader->unix_fds_outstanding); + + /* Allocate space where we can put the fds we read. We allocate + space for max_message_unix_fds since this is an + upper limit how many fds can be received within a single + message. Since SCM_RIGHTS doesn't allow a reallocate+retry logic + we are allocating the maximum possible array size right from the + beginning. This sucks a bit, however unless SCM_RIGHTS is fixed + there is no better way. */ + + if (loader->n_unix_fds_allocated < loader->max_message_unix_fds) + { + int *a = dbus_realloc(loader->unix_fds, + loader->max_message_unix_fds * sizeof(loader->unix_fds[0])); + + if (!a) + return FALSE; + + loader->unix_fds = a; + loader->n_unix_fds_allocated = loader->max_message_unix_fds; + } + + *fds = loader->unix_fds + loader->n_unix_fds; + *max_n_fds = loader->n_unix_fds_allocated - loader->n_unix_fds; + + loader->unix_fds_outstanding = TRUE; + return TRUE; +} + +/** + * Returns a buffer obtained from _dbus_message_loader_get_unix_fds(). + * + * This works similar to _dbus_message_loader_return_buffer() + * + * @param loader the message loader. + * @param fds the array fds were read into + * @param n_fds how many fds were read + */ + +void +_dbus_message_loader_return_unix_fds(DBusMessageLoader *loader, + int *fds, + unsigned n_fds) +{ + _dbus_assert(loader->unix_fds_outstanding); + _dbus_assert(loader->unix_fds + loader->n_unix_fds == fds); + _dbus_assert(loader->n_unix_fds + n_fds <= loader->n_unix_fds_allocated); + + loader->n_unix_fds += n_fds; + loader->unix_fds_outstanding = FALSE; + + if (n_fds && loader->unix_fds_change) + loader->unix_fds_change (loader->unix_fds_change_data); +} +#endif + +/* + * FIXME when we move the header out of the buffer, that memmoves all + * buffered messages. Kind of crappy. + * + * Also we copy the header and body, which is kind of crappy. To + * avoid this, we have to allow header and body to be in a single + * memory block, which is good for messages we read and bad for + * messages we are creating. But we could move_len() the buffer into + * this single memory block, and move_len() will just swap the buffers + * if you're moving the entire buffer replacing the dest string. + * + * We could also have the message loader tell the transport how many + * bytes to read; so it would first ask for some arbitrary number like + * 256, then if the message was incomplete it would use the + * header/body len to ask for exactly the size of the message (or + * blocks the size of a typical kernel buffer for the socket). That + * way we don't get trailing bytes in the buffer that have to be + * memmoved. Though I suppose we also don't have a chance of reading a + * bunch of small messages at once, so the optimization may be stupid. + * + * Another approach would be to keep a "start" index into + * loader->data and only delete it occasionally, instead of after + * each message is loaded. + * + * load_message() returns FALSE if not enough memory OR the loader was corrupted + */ +static dbus_bool_t +load_message (DBusMessageLoader *loader, + DBusMessage *message, + int byte_order, + int fields_array_len, + int header_len, + int body_len) +{ + dbus_bool_t oom; + DBusValidity validity; + const DBusString *type_str; + int type_pos; + DBusValidationMode mode; + dbus_uint32_t n_unix_fds = 0; + + mode = DBUS_VALIDATION_MODE_DATA_IS_UNTRUSTED; + + oom = FALSE; + +#if 0 + _dbus_verbose_bytes_of_string (&loader->data, 0, header_len /* + body_len */); +#endif + + /* 1. VALIDATE AND COPY OVER HEADER */ + _dbus_assert (_dbus_string_get_length (&message->header.data) == 0); + _dbus_assert ((header_len + body_len) <= _dbus_string_get_length (&loader->data)); + + if (!_dbus_header_load (&message->header, + mode, + &validity, + byte_order, + fields_array_len, + header_len, + body_len, + &loader->data)) + { + _dbus_verbose ("Failed to load header for new message code %d\n", validity); + + /* assert here so we can catch any code that still uses DBUS_VALID to indicate + oom errors. They should use DBUS_VALIDITY_UNKNOWN_OOM_ERROR instead */ + _dbus_assert (validity != DBUS_VALID); + + if (validity == DBUS_VALIDITY_UNKNOWN_OOM_ERROR) + oom = TRUE; + else + { + loader->corrupted = TRUE; + loader->corruption_reason = validity; + } + goto failed; + } + + _dbus_assert (validity == DBUS_VALID); + + /* 2. VALIDATE BODY */ + if (mode != DBUS_VALIDATION_MODE_WE_TRUST_THIS_DATA_ABSOLUTELY) + { + get_const_signature (&message->header, &type_str, &type_pos); + + /* Because the bytes_remaining arg is NULL, this validates that the + * body is the right length + */ + validity = _dbus_validate_body_with_reason (type_str, + type_pos, + byte_order, + NULL, + &loader->data, + header_len, + body_len); + if (validity != DBUS_VALID) + { + _dbus_verbose ("Failed to validate message body code %d\n", validity); + + loader->corrupted = TRUE; + loader->corruption_reason = validity; + + goto failed; + } + } + + /* 3. COPY OVER UNIX FDS */ + _dbus_header_get_field_basic(&message->header, + DBUS_HEADER_FIELD_UNIX_FDS, + DBUS_TYPE_UINT32, + &n_unix_fds); + +#ifdef HAVE_UNIX_FD_PASSING + + if (n_unix_fds > loader->n_unix_fds) + { + _dbus_verbose("Message contains references to more unix fds than were sent %u != %u\n", + n_unix_fds, loader->n_unix_fds); + + loader->corrupted = TRUE; + loader->corruption_reason = DBUS_INVALID_MISSING_UNIX_FDS; + goto failed; + } + + /* If this was a recycled message there might still be + some memory allocated for the fds */ + dbus_free(message->unix_fds); + + if (n_unix_fds > 0) + { + message->unix_fds = _dbus_memdup(loader->unix_fds, n_unix_fds * sizeof(message->unix_fds[0])); + if (message->unix_fds == NULL) + { + _dbus_verbose ("Failed to allocate file descriptor array\n"); + oom = TRUE; + goto failed; + } + + message->n_unix_fds_allocated = message->n_unix_fds = n_unix_fds; + loader->n_unix_fds -= n_unix_fds; + memmove (loader->unix_fds, loader->unix_fds + n_unix_fds, loader->n_unix_fds * sizeof (loader->unix_fds[0])); + + if (loader->unix_fds_change) + loader->unix_fds_change (loader->unix_fds_change_data); + } + else + message->unix_fds = NULL; + +#else + + if (n_unix_fds > 0) + { + _dbus_verbose ("Hmm, message claims to come with file descriptors " + "but that's not supported on our platform, disconnecting.\n"); + + loader->corrupted = TRUE; + loader->corruption_reason = DBUS_INVALID_MISSING_UNIX_FDS; + goto failed; + } + +#endif + + /* 3. COPY OVER BODY AND QUEUE MESSAGE */ + + if (!_dbus_list_append (&loader->messages, message)) + { + _dbus_verbose ("Failed to append new message to loader queue\n"); + oom = TRUE; + goto failed; + } + + _dbus_assert (_dbus_string_get_length (&message->body) == 0); + _dbus_assert (_dbus_string_get_length (&loader->data) >= + (header_len + body_len)); + + if (!_dbus_string_copy_len (&loader->data, header_len, body_len, &message->body, 0)) + { + _dbus_verbose ("Failed to move body into new message\n"); + oom = TRUE; + goto failed; + } + + _dbus_string_delete (&loader->data, 0, header_len + body_len); + + /* don't waste more than 2k of memory */ + _dbus_string_compact (&loader->data, 2048); + + _dbus_assert (_dbus_string_get_length (&message->header.data) == header_len); + _dbus_assert (_dbus_string_get_length (&message->body) == body_len); + + _dbus_verbose ("Loaded message %p\n", message); + + _dbus_assert (!oom); + _dbus_assert (!loader->corrupted); + _dbus_assert (loader->messages != NULL); + _dbus_assert (_dbus_list_find_last (&loader->messages, message) != NULL); + + return TRUE; + + failed: + + /* Clean up */ + + /* does nothing if the message isn't in the list */ + _dbus_list_remove_last (&loader->messages, message); + + if (oom) + _dbus_assert (!loader->corrupted); + else + _dbus_assert (loader->corrupted); + + _dbus_verbose_bytes_of_string (&loader->data, 0, _dbus_string_get_length (&loader->data)); + + return FALSE; +} + +/** + * Converts buffered data into messages, if we have enough data. If + * we don't have enough data, does nothing. + * + * @todo we need to check that the proper named header fields exist + * for each message type. + * + * @todo If a message has unknown type, we should probably eat it + * right here rather than passing it out to applications. However + * it's not an error to see messages of unknown type. + * + * @param loader the loader. + * @returns #TRUE if we had enough memory to finish. + */ +dbus_bool_t +_dbus_message_loader_queue_messages (DBusMessageLoader *loader) +{ + while (!loader->corrupted && + _dbus_string_get_length (&loader->data) >= DBUS_MINIMUM_HEADER_SIZE) + { + DBusValidity validity; + int byte_order, fields_array_len, header_len, body_len; + + if (_dbus_header_have_message_untrusted (loader->max_message_size, + &validity, + &byte_order, + &fields_array_len, + &header_len, + &body_len, + &loader->data, 0, + _dbus_string_get_length (&loader->data))) + { + DBusMessage *message; + + _dbus_assert (validity == DBUS_VALID); + + message = dbus_message_new_empty_header (); + if (message == NULL) + return FALSE; + + if (!load_message (loader, message, + byte_order, fields_array_len, + header_len, body_len)) + { + dbus_message_unref (message); + /* load_message() returns false if corrupted or OOM; if + * corrupted then return TRUE for not OOM + */ + return loader->corrupted; + } + + _dbus_assert (loader->messages != NULL); + _dbus_assert (_dbus_list_find_last (&loader->messages, message) != NULL); + } + else + { + _dbus_verbose ("Initial peek at header says we don't have a whole message yet, or data broken with invalid code %d\n", + validity); + if (validity != DBUS_VALID) + { + loader->corrupted = TRUE; + loader->corruption_reason = validity; + } + return TRUE; + } + } + + return TRUE; +} + +/** + * Peeks at first loaded message, returns #NULL if no messages have + * been queued. + * + * @param loader the loader. + * @returns the next message, or #NULL if none. + */ +DBusMessage* +_dbus_message_loader_peek_message (DBusMessageLoader *loader) +{ + if (loader->messages) + return loader->messages->data; + else + return NULL; +} + +/** + * Pops a loaded message (passing ownership of the message + * to the caller). Returns #NULL if no messages have been + * queued. + * + * @param loader the loader. + * @returns the next message, or #NULL if none. + */ +DBusMessage* +_dbus_message_loader_pop_message (DBusMessageLoader *loader) +{ + return _dbus_list_pop_first (&loader->messages); +} + +/** + * Pops a loaded message inside a list link (passing ownership of the + * message and link to the caller). Returns #NULL if no messages have + * been loaded. + * + * @param loader the loader. + * @returns the next message link, or #NULL if none. + */ +DBusList* +_dbus_message_loader_pop_message_link (DBusMessageLoader *loader) +{ + return _dbus_list_pop_first_link (&loader->messages); +} + +/** + * Returns a popped message link, used to undo a pop. + * + * @param loader the loader + * @param link the link with a message in it + */ +void +_dbus_message_loader_putback_message_link (DBusMessageLoader *loader, + DBusList *link) +{ + _dbus_list_prepend_link (&loader->messages, link); +} + +/** + * Checks whether the loader is confused due to bad data. + * If messages are received that are invalid, the + * loader gets confused and gives up permanently. + * This state is called "corrupted." + * + * @param loader the loader + * @returns #TRUE if the loader is hosed. + */ +dbus_bool_t +_dbus_message_loader_get_is_corrupted (DBusMessageLoader *loader) +{ + _dbus_assert ((loader->corrupted && loader->corruption_reason != DBUS_VALID) || + (!loader->corrupted && loader->corruption_reason == DBUS_VALID)); + return loader->corrupted; +} + +/** + * Checks what kind of bad data confused the loader. + * + * @param loader the loader + * @returns why the loader is hosed, or DBUS_VALID if it isn't. + */ +DBusValidity +_dbus_message_loader_get_corruption_reason (DBusMessageLoader *loader) +{ + _dbus_assert ((loader->corrupted && loader->corruption_reason != DBUS_VALID) || + (!loader->corrupted && loader->corruption_reason == DBUS_VALID)); + + return loader->corruption_reason; +} + +/** + * Sets the maximum size message we allow. + * + * @param loader the loader + * @param size the max message size in bytes + */ +void +_dbus_message_loader_set_max_message_size (DBusMessageLoader *loader, + long size) +{ + if (size > DBUS_MAXIMUM_MESSAGE_LENGTH) + { + _dbus_verbose ("clamping requested max message size %ld to %d\n", + size, DBUS_MAXIMUM_MESSAGE_LENGTH); + size = DBUS_MAXIMUM_MESSAGE_LENGTH; + } + loader->max_message_size = size; +} + +/** + * Gets the maximum allowed message size in bytes. + * + * @param loader the loader + * @returns max size in bytes + */ +long +_dbus_message_loader_get_max_message_size (DBusMessageLoader *loader) +{ + return loader->max_message_size; +} + +/** + * Sets the maximum unix fds per message we allow. + * + * @param loader the loader + * @param n the max number of unix fds in a message + */ +void +_dbus_message_loader_set_max_message_unix_fds (DBusMessageLoader *loader, + long n) +{ + if (n > DBUS_MAXIMUM_MESSAGE_UNIX_FDS) + { + _dbus_verbose ("clamping requested max message unix_fds %ld to %d\n", + n, DBUS_MAXIMUM_MESSAGE_UNIX_FDS); + n = DBUS_MAXIMUM_MESSAGE_UNIX_FDS; + } + loader->max_message_unix_fds = n; +} + +/** + * Gets the maximum allowed number of unix fds per message + * + * @param loader the loader + * @returns max unix fds + */ +long +_dbus_message_loader_get_max_message_unix_fds (DBusMessageLoader *loader) +{ + return loader->max_message_unix_fds; +} + +/** + * Return how many file descriptors are pending in the loader + * + * @param loader the loader + */ +int +_dbus_message_loader_get_pending_fds_count (DBusMessageLoader *loader) +{ +#ifdef HAVE_UNIX_FD_PASSING + return loader->n_unix_fds; +#else + return 0; +#endif +} + +/** + * Register a function to be called whenever the number of pending file + * descriptors in the loader change. + * + * @param loader the loader + * @param callback the callback + * @param data the data for the callback + */ +void +_dbus_message_loader_set_pending_fds_function (DBusMessageLoader *loader, + void (* callback) (void *), + void *data) +{ +#ifdef HAVE_UNIX_FD_PASSING + loader->unix_fds_change = callback; + loader->unix_fds_change_data = data; +#endif +} + +static DBusDataSlotAllocator slot_allocator = + _DBUS_DATA_SLOT_ALLOCATOR_INIT (_DBUS_LOCK_NAME (message_slots)); + +/** + * Allocates an integer ID to be used for storing application-specific + * data on any DBusMessage. The allocated ID may then be used + * with dbus_message_set_data() and dbus_message_get_data(). + * The passed-in slot must be initialized to -1, and is filled in + * with the slot ID. If the passed-in slot is not -1, it's assumed + * to be already allocated, and its refcount is incremented. + * + * The allocated slot is global, i.e. all DBusMessage objects will + * have a slot with the given integer ID reserved. + * + * @param slot_p address of a global variable storing the slot + * @returns #FALSE on failure (no memory) + */ +dbus_bool_t +dbus_message_allocate_data_slot (dbus_int32_t *slot_p) +{ + return _dbus_data_slot_allocator_alloc (&slot_allocator, + slot_p); +} + +/** + * Deallocates a global ID for message data slots. + * dbus_message_get_data() and dbus_message_set_data() may no + * longer be used with this slot. Existing data stored on existing + * DBusMessage objects will be freed when the message is + * finalized, but may not be retrieved (and may only be replaced if + * someone else reallocates the slot). When the refcount on the + * passed-in slot reaches 0, it is set to -1. + * + * @param slot_p address storing the slot to deallocate + */ +void +dbus_message_free_data_slot (dbus_int32_t *slot_p) +{ + _dbus_return_if_fail (*slot_p >= 0); + + _dbus_data_slot_allocator_free (&slot_allocator, slot_p); +} + +/** + * Stores a pointer on a DBusMessage, along + * with an optional function to be used for freeing + * the data when the data is set again, or when + * the message is finalized. The slot number + * must have been allocated with dbus_message_allocate_data_slot(). + * + * @param message the message + * @param slot the slot number + * @param data the data to store + * @param free_data_func finalizer function for the data + * @returns #TRUE if there was enough memory to store the data + */ +dbus_bool_t +dbus_message_set_data (DBusMessage *message, + dbus_int32_t slot, + void *data, + DBusFreeFunction free_data_func) +{ + DBusFreeFunction old_free_func; + void *old_data; + dbus_bool_t retval; + + _dbus_return_val_if_fail (message != NULL, FALSE); + _dbus_return_val_if_fail (slot >= 0, FALSE); + + retval = _dbus_data_slot_list_set (&slot_allocator, + &message->slot_list, + slot, data, free_data_func, + &old_free_func, &old_data); + + if (retval) + { + /* Do the actual free outside the message lock */ + if (old_free_func) + (* old_free_func) (old_data); + } + + return retval; +} + +/** + * Retrieves data previously set with dbus_message_set_data(). + * The slot must still be allocated (must not have been freed). + * + * @param message the message + * @param slot the slot to get data from + * @returns the data, or #NULL if not found + */ +void* +dbus_message_get_data (DBusMessage *message, + dbus_int32_t slot) +{ + void *res; + + _dbus_return_val_if_fail (message != NULL, NULL); + + res = _dbus_data_slot_list_get (&slot_allocator, + &message->slot_list, + slot); + + return res; +} + +/** + * Utility function to convert a machine-readable (not translated) + * string into a D-Bus message type. + * + * @code + * "method_call" -> DBUS_MESSAGE_TYPE_METHOD_CALL + * "method_return" -> DBUS_MESSAGE_TYPE_METHOD_RETURN + * "signal" -> DBUS_MESSAGE_TYPE_SIGNAL + * "error" -> DBUS_MESSAGE_TYPE_ERROR + * anything else -> DBUS_MESSAGE_TYPE_INVALID + * @endcode + * + */ +int +dbus_message_type_from_string (const char *type_str) +{ + if (strcmp (type_str, "method_call") == 0) + return DBUS_MESSAGE_TYPE_METHOD_CALL; + if (strcmp (type_str, "method_return") == 0) + return DBUS_MESSAGE_TYPE_METHOD_RETURN; + else if (strcmp (type_str, "signal") == 0) + return DBUS_MESSAGE_TYPE_SIGNAL; + else if (strcmp (type_str, "error") == 0) + return DBUS_MESSAGE_TYPE_ERROR; + else + return DBUS_MESSAGE_TYPE_INVALID; +} + +/** + * Utility function to convert a D-Bus message type into a + * machine-readable string (not translated). + * + * @code + * DBUS_MESSAGE_TYPE_METHOD_CALL -> "method_call" + * DBUS_MESSAGE_TYPE_METHOD_RETURN -> "method_return" + * DBUS_MESSAGE_TYPE_SIGNAL -> "signal" + * DBUS_MESSAGE_TYPE_ERROR -> "error" + * DBUS_MESSAGE_TYPE_INVALID -> "invalid" + * @endcode + * + */ +const char * +dbus_message_type_to_string (int type) +{ + switch (type) + { + case DBUS_MESSAGE_TYPE_METHOD_CALL: + return "method_call"; + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + return "method_return"; + case DBUS_MESSAGE_TYPE_SIGNAL: + return "signal"; + case DBUS_MESSAGE_TYPE_ERROR: + return "error"; + default: + return "invalid"; + } +} + +/** + * Turn a DBusMessage into the marshalled form as described in the D-Bus + * specification. + * + * Generally, this function is only useful for encapsulating D-Bus messages in + * a different protocol. + * + * @param msg the DBusMessage + * @param marshalled_data_p the location to save the marshalled form to + * @param len_p the location to save the length of the marshalled form to + * @returns #FALSE if there was not enough memory + */ +dbus_bool_t +dbus_message_marshal (DBusMessage *msg, + char **marshalled_data_p, + int *len_p) +{ + DBusString tmp; + dbus_bool_t was_locked; + + _dbus_return_val_if_fail (msg != NULL, FALSE); + _dbus_return_val_if_fail (marshalled_data_p != NULL, FALSE); + _dbus_return_val_if_fail (len_p != NULL, FALSE); + + if (!_dbus_string_init (&tmp)) + return FALSE; + + /* Ensure the message is locked, to ensure the length header is filled in. */ + was_locked = msg->locked; + + if (!was_locked) + dbus_message_lock (msg); + + if (!_dbus_string_copy (&(msg->header.data), 0, &tmp, 0)) + goto fail; + + *len_p = _dbus_string_get_length (&tmp); + + if (!_dbus_string_copy (&(msg->body), 0, &tmp, *len_p)) + goto fail; + + *len_p = _dbus_string_get_length (&tmp); + + if (!_dbus_string_steal_data (&tmp, marshalled_data_p)) + goto fail; + + _dbus_string_free (&tmp); + + if (!was_locked) + msg->locked = FALSE; + + return TRUE; + + fail: + _dbus_string_free (&tmp); + + if (!was_locked) + msg->locked = FALSE; + + return FALSE; +} + +/** + * Demarshal a D-Bus message from the format described in the D-Bus + * specification. + * + * Generally, this function is only useful for encapsulating D-Bus messages in + * a different protocol. + * + * @param str the marshalled DBusMessage + * @param len the length of str + * @param error the location to save errors to + * @returns #NULL if there was an error + */ +DBusMessage * +dbus_message_demarshal (const char *str, + int len, + DBusError *error) +{ + DBusMessageLoader *loader = NULL; + DBusString *buffer; + DBusMessage *msg; + + _dbus_return_val_if_fail (str != NULL, NULL); + + loader = _dbus_message_loader_new (); + + if (loader == NULL) + goto fail_oom; + + _dbus_message_loader_get_buffer (loader, &buffer, NULL, NULL); + + if (!_dbus_string_append_len (buffer, str, len)) + goto fail_oom; + + _dbus_message_loader_return_buffer (loader, buffer); + + if (!_dbus_message_loader_queue_messages (loader)) + goto fail_oom; + + if (_dbus_message_loader_get_is_corrupted (loader)) + goto fail_corrupt; + + msg = _dbus_message_loader_pop_message (loader); + + if (!msg) + goto fail_oom; + + _dbus_message_loader_unref (loader); + return msg; + + fail_corrupt: + if (loader->corruption_reason == DBUS_VALIDITY_UNKNOWN_OOM_ERROR) + goto fail_oom; + + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, "Message is corrupted (%s)", + _dbus_validity_to_error_message (loader->corruption_reason)); + _dbus_message_loader_unref (loader); + return NULL; + + fail_oom: + _DBUS_SET_OOM (error); + + if (loader != NULL) + _dbus_message_loader_unref (loader); + + return NULL; +} + +/** + * Returns the number of bytes required to be in the buffer to demarshal a + * D-Bus message. + * + * Generally, this function is only useful for encapsulating D-Bus messages in + * a different protocol. + * + * @param buf data to be marshalled + * @param len the length of @p buf + * @returns -1 if there was no valid data to be demarshalled, 0 if there wasn't enough data to determine how much should be demarshalled. Otherwise returns the number of bytes to be demarshalled + * + */ +int +dbus_message_demarshal_bytes_needed(const char *buf, + int len) +{ + DBusString str; + int byte_order, fields_array_len, header_len, body_len; + DBusValidity validity = DBUS_VALID; + int have_message; + + if (!buf || len < DBUS_MINIMUM_HEADER_SIZE) + return 0; + + if (len > DBUS_MAXIMUM_MESSAGE_LENGTH) + len = DBUS_MAXIMUM_MESSAGE_LENGTH; + _dbus_string_init_const_len (&str, buf, len); + + validity = DBUS_VALID; + have_message + = _dbus_header_have_message_untrusted(DBUS_MAXIMUM_MESSAGE_LENGTH, + &validity, &byte_order, + &fields_array_len, + &header_len, + &body_len, + &str, 0, + len); + _dbus_string_free (&str); + + if (validity == DBUS_VALID) + { + _dbus_assert (have_message || (header_len + body_len) > len); + (void) have_message; /* unused unless asserting */ + return header_len + body_len; + } + else + { + return -1; /* broken! */ + } +} + +/** + * Sets a flag indicating that the caller of the method is prepared + * to wait for interactive authorization to take place (for instance + * via Polkit) before the actual method is processed. + * + * The flag is #FALSE by default; that is, by default the other end is + * expected to make any authorization decisions non-interactively + * and promptly. It may use the error + * #DBUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED to signal that + * authorization failed, but could have succeeded if this flag had + * been used. + * + * For messages whose type is not #DBUS_MESSAGE_TYPE_METHOD_CALL, + * this flag is meaningless and should not be set. + * + * On the protocol level this toggles + * #DBUS_HEADER_FLAG_ALLOW_INTERACTIVE_AUTHORIZATION. + * + * @param message the message + * @param allow #TRUE if interactive authorization is acceptable + */ +void +dbus_message_set_allow_interactive_authorization (DBusMessage *message, + dbus_bool_t allow) +{ + _dbus_return_if_fail (message != NULL); + _dbus_return_if_fail (!message->locked); + + _dbus_header_toggle_flag (&message->header, + DBUS_HEADER_FLAG_ALLOW_INTERACTIVE_AUTHORIZATION, + allow); +} + +/** + * Returns whether the flag controlled by + * dbus_message_set_allow_interactive_authorization() has been set. + * + * @param message the message + */ +dbus_bool_t +dbus_message_get_allow_interactive_authorization (DBusMessage *message) +{ + _dbus_return_val_if_fail (message != NULL, FALSE); + + return _dbus_header_get_flag (&message->header, + DBUS_HEADER_FLAG_ALLOW_INTERACTIVE_AUTHORIZATION); +} + +/** + * An opaque data structure containing the serialized form of any single + * D-Bus message item, whose signature is a single complete type. + * + * (Implementation detail: It's serialized as a single variant.) + */ +struct DBusVariant +{ + DBusString data; +}; + +/** + * Copy a single D-Bus message item from reader into a + * newly-allocated #DBusVariant. + * + * For example, if a message contains three string arguments, and reader points + * to the second string, the resulting DBusVariant will have signature + * #DBUS_TYPE_STRING_AS_STRING and contain only that second string. + * + * @param reader An iterator over message items, pointing to one item to copy + * @returns The variant, or #NULL if out of memory + */ +DBusVariant * +_dbus_variant_read (DBusMessageIter *reader) +{ + DBusVariant *self = NULL; + /* Points to the single item we will read from the reader */ + DBusMessageRealIter *real_reader = (DBusMessageRealIter *) reader; + /* The position in self at which we will write a single variant + * (it is position 0) */ + DBusTypeWriter items_writer; + /* The position in self at which we will write a copy of reader + * (it is inside the variant) */ + DBusTypeWriter variant_writer; + /* 'v' */ + DBusString variant_signature; + /* Whatever is the signature of the item we will copy from the reader */ + DBusString contained_signature; + /* TRUE if self->data needs to be freed */ + dbus_bool_t data_inited = FALSE; + /* The type of the item we will read from the reader */ + int type; + /* The string, start position within that string, and length of the signature + * of the single complete type of the item reader points to */ + const DBusString *sig; + int start, len; + + _dbus_assert (_dbus_message_iter_check (real_reader)); + _dbus_assert (real_reader->iter_type == DBUS_MESSAGE_ITER_TYPE_READER); + _dbus_string_init_const (&variant_signature, DBUS_TYPE_VARIANT_AS_STRING); + type = dbus_message_iter_get_arg_type (reader); + _dbus_type_reader_get_signature (&real_reader->u.reader, &sig, &start, &len); + + if (!_dbus_string_init (&contained_signature)) + return NULL; + + if (!_dbus_string_copy_len (sig, start, len, &contained_signature, 0)) + goto oom; + + self = dbus_new0 (DBusVariant, 1); + + if (self == NULL) + goto oom; + + if (!_dbus_string_init (&self->data)) + goto oom; + + data_inited = TRUE; + + _dbus_type_writer_init_values_only (&items_writer, DBUS_COMPILER_BYTE_ORDER, + &variant_signature, 0, &self->data, 0); + + if (!_dbus_type_writer_recurse (&items_writer, DBUS_TYPE_VARIANT, + &contained_signature, 0, &variant_writer)) + goto oom; + + if (type == DBUS_TYPE_ARRAY) + { + /* Points to each item in turn inside the array we are copying */ + DBusMessageIter array_reader; + /* Same as array_reader */ + DBusMessageRealIter *real_array_reader = (DBusMessageRealIter *) &array_reader; + /* The position inside the copied array at which we will write + * the copy of array_reader */ + DBusTypeWriter array_writer; + + dbus_message_iter_recurse (reader, &array_reader); + + if (!_dbus_type_writer_recurse (&variant_writer, type, + &contained_signature, 1, &array_writer)) + goto oom; + + if (!_dbus_type_writer_write_reader (&array_writer, + &real_array_reader->u.reader)) + goto oom; + + if (!_dbus_type_writer_unrecurse (&variant_writer, &array_writer)) + goto oom; + } + else if (type == DBUS_TYPE_DICT_ENTRY || type == DBUS_TYPE_VARIANT || + type == DBUS_TYPE_STRUCT) + { + /* Points to each item in turn inside the container we are copying */ + DBusMessageIter inner_reader; + /* Same as inner_reader */ + DBusMessageRealIter *real_inner_reader = (DBusMessageRealIter *) &inner_reader; + /* The position inside the copied container at which we will write the + * copy of inner_reader */ + DBusTypeWriter inner_writer; + + dbus_message_iter_recurse (reader, &inner_reader); + + if (!_dbus_type_writer_recurse (&variant_writer, type, NULL, 0, + &inner_writer)) + goto oom; + + if (!_dbus_type_writer_write_reader (&inner_writer, + &real_inner_reader->u.reader)) + goto oom; + + if (!_dbus_type_writer_unrecurse (&variant_writer, &inner_writer)) + goto oom; + } + else + { + DBusBasicValue value; + + /* We eliminated all the container types above */ + _dbus_assert (dbus_type_is_basic (type)); + + dbus_message_iter_get_basic (reader, &value); + + if (!_dbus_type_writer_write_basic (&variant_writer, type, &value)) + goto oom; + } + + _dbus_string_free (&contained_signature); + return self; + +oom: + if (self != NULL) + { + if (data_inited) + _dbus_string_free (&self->data); + + dbus_free (self); + } + + _dbus_string_free (&contained_signature); + return NULL; +} + +/** + * Return the signature of the item stored in self. It is a single complete + * type. + * + * @param self the variant + */ +const char * +_dbus_variant_get_signature (DBusVariant *self) +{ + const char *ret; +#ifndef DBUS_DISABLE_ASSERT + unsigned char len; +#endif + + _dbus_assert (self != NULL); + +#ifndef DBUS_DISABLE_ASSERT + /* Here we make use of the fact that the serialization of a variant starts + * with the 1-byte length, then that many bytes of signature, then \0. */ + len = _dbus_string_get_byte (&self->data, 0); +#endif + ret = _dbus_string_get_const_data_len (&self->data, 1, len); + _dbus_assert (strlen (ret) == len); + return ret; +} + +/** + * Copy the single D-Bus message item from self into writer. + * + * For example, if writer points into the body of an empty message and self has + * signature #DBUS_TYPE_STRING_AS_STRING, then the message will + * have signature #DBUS_TYPE_STRING_AS_STRING after this function returns + * + * @param self the variant + * @param writer the place to write the contents of the variant + * @returns #TRUE on success, #FALSE if out of memory + */ +dbus_bool_t +_dbus_variant_write (DBusVariant *self, + DBusMessageIter *writer) +{ + /* 'v' */ + DBusString variant_signature; + /* Points to the single item in self */ + DBusTypeReader variant_reader; + /* Points to the single item (of whatever type) inside the variant */ + DBusTypeReader reader; + /* The position at which we will copy reader */ + DBusMessageRealIter *real_writer = (DBusMessageRealIter *) writer; + dbus_bool_t ret; + + _dbus_assert (self != NULL); + _dbus_assert (_dbus_message_iter_append_check (real_writer)); + _dbus_assert (real_writer->iter_type == DBUS_MESSAGE_ITER_TYPE_WRITER); + + _dbus_string_init_const (&variant_signature, DBUS_TYPE_VARIANT_AS_STRING); + _dbus_type_reader_init (&reader, DBUS_COMPILER_BYTE_ORDER, + &variant_signature, 0, &self->data, 0); + _dbus_type_reader_recurse (&reader, &variant_reader); + + if (!_dbus_message_iter_open_signature (real_writer)) + return FALSE; + + ret = _dbus_type_writer_write_reader (&real_writer->u.writer, + &variant_reader); + + if (!_dbus_message_iter_close_signature (real_writer)) + return FALSE; + + return ret; +} + +int +_dbus_variant_get_length (DBusVariant *self) +{ + _dbus_assert (self != NULL); + return _dbus_string_get_length (&self->data); +} + +const DBusString * +_dbus_variant_peek (DBusVariant *self) +{ + _dbus_assert (self != NULL); + return &self->data; +} + +void +_dbus_variant_free (DBusVariant *self) +{ + _dbus_assert (self != NULL); + _dbus_string_free (&self->data); + dbus_free (self); +} + +/** @} */ + +/* tests in dbus-message-util.c */ diff --git a/src/3rdparty/libdbus/dbus/dbus-message.h b/src/3rdparty/libdbus/dbus/dbus-message.h new file mode 100644 index 00000000..0bbad3d1 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-message.h @@ -0,0 +1,401 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-message.h DBusMessage object + * + * Copyright (C) 2002, 2003, 2005 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_MESSAGE_H +#define DBUS_MESSAGE_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-arch-deps.h> +#include <dbus/dbus-memory.h> +#include <dbus/dbus-errors.h> +#include <stdarg.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusMessage + * @{ + */ + +typedef struct DBusMessage DBusMessage; +/** + * Opaque type representing a message iterator. Can be copied by value and + * allocated on the stack. + * + * A DBusMessageIter usually contains no allocated memory. However, there + * is one special case: after a successful call to + * dbus_message_iter_open_container(), the caller is responsible for calling + * either dbus_message_iter_close_container() or + * dbus_message_iter_abandon_container() exactly once, with the same pair + * of iterators. + */ +typedef struct DBusMessageIter DBusMessageIter; + +/** + * DBusMessageIter struct; contains no public fields. + */ +struct DBusMessageIter +{ +#if DBUS_SIZEOF_VOID_P > 8 + void *dummy[16]; /**< Don't use this */ +#else + void *dummy1; /**< Don't use this */ + void *dummy2; /**< Don't use this */ + dbus_uint32_t dummy3; /**< Don't use this */ + int dummy4; /**< Don't use this */ + int dummy5; /**< Don't use this */ + int dummy6; /**< Don't use this */ + int dummy7; /**< Don't use this */ + int dummy8; /**< Don't use this */ + int dummy9; /**< Don't use this */ + int dummy10; /**< Don't use this */ + int dummy11; /**< Don't use this */ + int pad1; /**< Don't use this */ + void *pad2; /**< Don't use this */ + void *pad3; /**< Don't use this */ +#endif +}; + +/** + * A message iterator for which dbus_message_iter_abandon_container_if_open() + * is the only valid operation. + */ +#if DBUS_SIZEOF_VOID_P > 8 +#define DBUS_MESSAGE_ITER_INIT_CLOSED \ +{ \ + { \ + NULL, NULL, NULL, NULL, \ + NULL, NULL, NULL, NULL, \ + NULL, NULL, NULL, NULL, \ + NULL, NULL, NULL, NULL \ + } \ +} +#else +#define DBUS_MESSAGE_ITER_INIT_CLOSED \ +{ \ + NULL, /* dummy1 */ \ + NULL, /* dummy2 */ \ + 0, /* dummy3 */ \ + 0, /* dummy4 */ \ + 0, /* dummy5 */ \ + 0, /* dummy6 */ \ + 0, /* dummy7 */ \ + 0, /* dummy8 */ \ + 0, /* dummy9 */ \ + 0, /* dummy10 */ \ + 0, /* dummy11 */ \ + 0, /* pad1 */ \ + NULL, /* pad2 */ \ + NULL /* pad3 */ \ +} +#endif + +DBUS_EXPORT +DBusMessage* dbus_message_new (int message_type); +DBUS_EXPORT +DBusMessage* dbus_message_new_method_call (const char *bus_name, + const char *path, + const char *iface, + const char *method); +DBUS_EXPORT +DBusMessage* dbus_message_new_method_return (DBusMessage *method_call); +DBUS_EXPORT +DBusMessage* dbus_message_new_signal (const char *path, + const char *iface, + const char *name); +DBUS_EXPORT +DBusMessage* dbus_message_new_error (DBusMessage *reply_to, + const char *error_name, + const char *error_message); +DBUS_EXPORT +DBusMessage* dbus_message_new_error_printf (DBusMessage *reply_to, + const char *error_name, + const char *error_format, + ...) _DBUS_GNUC_PRINTF (3, 4); + +DBUS_EXPORT +DBusMessage* dbus_message_copy (const DBusMessage *message); + +DBUS_EXPORT +DBusMessage* dbus_message_ref (DBusMessage *message); +DBUS_EXPORT +void dbus_message_unref (DBusMessage *message); +DBUS_EXPORT +int dbus_message_get_type (DBusMessage *message); +DBUS_EXPORT +dbus_bool_t dbus_message_set_path (DBusMessage *message, + const char *object_path); +DBUS_EXPORT +const char* dbus_message_get_path (DBusMessage *message); +DBUS_EXPORT +dbus_bool_t dbus_message_has_path (DBusMessage *message, + const char *object_path); +DBUS_EXPORT +dbus_bool_t dbus_message_set_interface (DBusMessage *message, + const char *iface); +DBUS_EXPORT +const char* dbus_message_get_interface (DBusMessage *message); +DBUS_EXPORT +dbus_bool_t dbus_message_has_interface (DBusMessage *message, + const char *iface); +DBUS_EXPORT +dbus_bool_t dbus_message_set_member (DBusMessage *message, + const char *member); +DBUS_EXPORT +const char* dbus_message_get_member (DBusMessage *message); +DBUS_EXPORT +dbus_bool_t dbus_message_has_member (DBusMessage *message, + const char *member); +DBUS_EXPORT +dbus_bool_t dbus_message_set_error_name (DBusMessage *message, + const char *name); +DBUS_EXPORT +const char* dbus_message_get_error_name (DBusMessage *message); +DBUS_EXPORT +dbus_bool_t dbus_message_set_destination (DBusMessage *message, + const char *destination); +DBUS_EXPORT +const char* dbus_message_get_destination (DBusMessage *message); +DBUS_EXPORT +dbus_bool_t dbus_message_set_sender (DBusMessage *message, + const char *sender); +DBUS_EXPORT +const char* dbus_message_get_sender (DBusMessage *message); +DBUS_EXPORT +const char* dbus_message_get_signature (DBusMessage *message); +DBUS_EXPORT +void dbus_message_set_no_reply (DBusMessage *message, + dbus_bool_t no_reply); +DBUS_EXPORT +dbus_bool_t dbus_message_get_no_reply (DBusMessage *message); +DBUS_EXPORT +dbus_bool_t dbus_message_is_method_call (DBusMessage *message, + const char *iface, + const char *method); +DBUS_EXPORT +dbus_bool_t dbus_message_is_signal (DBusMessage *message, + const char *iface, + const char *signal_name); +DBUS_EXPORT +dbus_bool_t dbus_message_is_error (DBusMessage *message, + const char *error_name); +DBUS_EXPORT +dbus_bool_t dbus_message_has_destination (DBusMessage *message, + const char *bus_name); +DBUS_EXPORT +dbus_bool_t dbus_message_has_sender (DBusMessage *message, + const char *unique_bus_name); +DBUS_EXPORT +dbus_bool_t dbus_message_has_signature (DBusMessage *message, + const char *signature); +DBUS_EXPORT +dbus_uint32_t dbus_message_get_serial (DBusMessage *message); +DBUS_EXPORT +void dbus_message_set_serial (DBusMessage *message, + dbus_uint32_t serial); +DBUS_EXPORT +dbus_bool_t dbus_message_set_reply_serial (DBusMessage *message, + dbus_uint32_t reply_serial); +DBUS_EXPORT +dbus_uint32_t dbus_message_get_reply_serial (DBusMessage *message); + +DBUS_EXPORT +void dbus_message_set_auto_start (DBusMessage *message, + dbus_bool_t auto_start); +DBUS_EXPORT +dbus_bool_t dbus_message_get_auto_start (DBusMessage *message); + +DBUS_EXPORT +dbus_bool_t dbus_message_get_path_decomposed (DBusMessage *message, + char ***path); + +DBUS_EXPORT +const char *dbus_message_get_container_instance (DBusMessage *message); +DBUS_EXPORT +dbus_bool_t dbus_message_set_container_instance (DBusMessage *message, + const char *object_path); + +DBUS_EXPORT +dbus_bool_t dbus_message_append_args (DBusMessage *message, + int first_arg_type, + ...); +DBUS_EXPORT +dbus_bool_t dbus_message_append_args_valist (DBusMessage *message, + int first_arg_type, + va_list var_args); +DBUS_EXPORT +dbus_bool_t dbus_message_get_args (DBusMessage *message, + DBusError *error, + int first_arg_type, + ...); +DBUS_EXPORT +dbus_bool_t dbus_message_get_args_valist (DBusMessage *message, + DBusError *error, + int first_arg_type, + va_list var_args); + +DBUS_EXPORT +dbus_bool_t dbus_message_contains_unix_fds (DBusMessage *message); + +DBUS_EXPORT +void dbus_message_iter_init_closed (DBusMessageIter *iter); +DBUS_EXPORT +dbus_bool_t dbus_message_iter_init (DBusMessage *message, + DBusMessageIter *iter); +DBUS_EXPORT +dbus_bool_t dbus_message_iter_has_next (DBusMessageIter *iter); +DBUS_EXPORT +dbus_bool_t dbus_message_iter_next (DBusMessageIter *iter); +DBUS_EXPORT +char* dbus_message_iter_get_signature (DBusMessageIter *iter); +DBUS_EXPORT +int dbus_message_iter_get_arg_type (DBusMessageIter *iter); +DBUS_EXPORT +int dbus_message_iter_get_element_type (DBusMessageIter *iter); +DBUS_EXPORT +void dbus_message_iter_recurse (DBusMessageIter *iter, + DBusMessageIter *sub); +DBUS_EXPORT +void dbus_message_iter_get_basic (DBusMessageIter *iter, + void *value); +DBUS_EXPORT +int dbus_message_iter_get_element_count(DBusMessageIter *iter); + +#ifndef DBUS_DISABLE_DEPRECATED +/* This function returns the wire protocol size of the array in bytes, + * you do not want to know that probably + */ +DBUS_EXPORT +DBUS_DEPRECATED int dbus_message_iter_get_array_len (DBusMessageIter *iter); +#endif +DBUS_EXPORT +void dbus_message_iter_get_fixed_array (DBusMessageIter *iter, + void *value, + int *n_elements); + + +DBUS_EXPORT +void dbus_message_iter_init_append (DBusMessage *message, + DBusMessageIter *iter); +DBUS_EXPORT +dbus_bool_t dbus_message_iter_append_basic (DBusMessageIter *iter, + int type, + const void *value); +DBUS_EXPORT +dbus_bool_t dbus_message_iter_append_fixed_array (DBusMessageIter *iter, + int element_type, + const void *value, + int n_elements); +DBUS_EXPORT +dbus_bool_t dbus_message_iter_open_container (DBusMessageIter *iter, + int type, + const char *contained_signature, + DBusMessageIter *sub); +DBUS_EXPORT +dbus_bool_t dbus_message_iter_close_container (DBusMessageIter *iter, + DBusMessageIter *sub); +DBUS_EXPORT +void dbus_message_iter_abandon_container (DBusMessageIter *iter, + DBusMessageIter *sub); + +DBUS_EXPORT +void dbus_message_iter_abandon_container_if_open (DBusMessageIter *iter, + DBusMessageIter *sub); + +DBUS_EXPORT +void dbus_message_lock (DBusMessage *message); + +DBUS_EXPORT +dbus_bool_t dbus_set_error_from_message (DBusError *error, + DBusMessage *message); + + +DBUS_EXPORT +dbus_bool_t dbus_message_allocate_data_slot (dbus_int32_t *slot_p); +DBUS_EXPORT +void dbus_message_free_data_slot (dbus_int32_t *slot_p); +DBUS_EXPORT +dbus_bool_t dbus_message_set_data (DBusMessage *message, + dbus_int32_t slot, + void *data, + DBusFreeFunction free_data_func); +DBUS_EXPORT +void* dbus_message_get_data (DBusMessage *message, + dbus_int32_t slot); + +DBUS_EXPORT +int dbus_message_type_from_string (const char *type_str); +DBUS_EXPORT +const char* dbus_message_type_to_string (int type); + +DBUS_EXPORT +dbus_bool_t dbus_message_marshal (DBusMessage *msg, + char **marshalled_data_p, + int *len_p); +DBUS_EXPORT +DBusMessage* dbus_message_demarshal (const char *str, + int len, + DBusError *error); + +DBUS_EXPORT +int dbus_message_demarshal_bytes_needed (const char *str, + int len); + +DBUS_EXPORT +void dbus_message_set_allow_interactive_authorization (DBusMessage *message, + dbus_bool_t allow); + +DBUS_EXPORT +dbus_bool_t dbus_message_get_allow_interactive_authorization ( + DBusMessage *message); + +/** + * Clear a variable or struct member that contains a #DBusMessage. + * If it does not contain #NULL, the message that was previously + * there is unreferenced with dbus_message_unref(). + * + * This is very similar to dbus_clear_connection(): see that function + * for more details. + * + * @param pointer_to_message A pointer to a variable or struct member. + * pointer_to_message must not be #NULL, but *pointer_to_message + * may be #NULL. + */ +static inline void +dbus_clear_message (DBusMessage **pointer_to_message) +{ + _dbus_clear_pointer_impl (DBusMessage, pointer_to_message, + dbus_message_unref); +} + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_MESSAGE_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-misc.c b/src/3rdparty/libdbus/dbus/dbus-misc.c new file mode 100644 index 00000000..0f6c2e6d --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-misc.c @@ -0,0 +1,226 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-misc.c A few assorted public functions that don't fit elsewhere + * + * Copyright (C) 2006 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-misc.h" +#include "dbus-internals.h" +#include "dbus-string.h" +#include <dbus/dbus-test-tap.h> + +/** + * @defgroup DBusMisc Miscellaneous + * @ingroup DBus + * @brief Miscellaneous API that doesn't cleanly fit anywhere else + * + * @{ + */ + +/** + * Obtains the machine UUID of the machine this process is running on. + * + * The returned string must be freed with dbus_free(). + * + * This UUID is guaranteed to remain the same until the next reboot + * (unless the sysadmin foolishly changes it and screws themselves). + * It will usually remain the same across reboots also, but hardware + * configuration changes or rebuilding the machine could break that. + * + * The idea is that two processes with the same machine ID should be + * able to use shared memory, UNIX domain sockets, process IDs, and other + * features of the OS that require both processes to be running + * on the same OS kernel instance. + * + * The machine ID can also be used to create unique per-machine + * instances. For example, you could use it in bus names or + * X selection names. + * + * The machine ID is preferred over the machine hostname, because + * the hostname is frequently set to "localhost.localdomain" and + * may also change at runtime. + * + * You can get the machine ID of a remote application by invoking the + * method GetMachineId from interface org.freedesktop.DBus.Peer. + * + * If the remote application has the same machine ID as the one + * returned by this function, then the remote application is on the + * same machine as your application. + * + * The UUID is not a UUID in the sense of RFC4122; the details + * are explained in the D-Bus specification. + * + * @returns a 32-byte-long hex-encoded UUID string, or #NULL on failure + */ +char * +dbus_try_get_local_machine_id (DBusError *error) +{ + DBusString uuid; + char *s; + + s = NULL; + + if (!_dbus_string_init (&uuid)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + if (!_dbus_get_local_machine_uuid_encoded (&uuid, error)) + { + _dbus_string_free (&uuid); + return NULL; + } + + if (!_dbus_string_steal_data (&uuid, &s)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&uuid); + return NULL; + } + else + { + _dbus_string_free (&uuid); + return s; + } + +} + +/** + * Obtains the machine UUID of the machine this process is running on. + * + * The returned string must be freed with dbus_free(). + * + * This function returns #NULL if there was not enough memory to read + * the UUID, or if the UUID could not be read because the D-Bus + * library was installed incorrectly. In the latter case, a warning + * is logged. + * + * Other than its deficient error reporting, this function is the same as + * dbus_try_get_local_machine_id(). + * + * @returns a 32-byte-long hex-encoded UUID string, or #NULL on failure + */ +char * +dbus_get_local_machine_id (void) +{ + DBusError error = DBUS_ERROR_INIT; + char *s; + + s = dbus_try_get_local_machine_id (&error); + + /* The documentation says dbus_get_local_machine_id() only fails on OOM; + * this can actually also fail if the D-Bus installation is faulty + * (no UUID), but we have no good way to report that. Historically, + * _dbus_get_local_machine_uuid_encoded was responsible for issuing the + * warning; now we do that here. */ + if (s == NULL) + { + if (!dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + _dbus_warn_check_failed ("%s", error.message); + + dbus_error_free (&error); + return NULL; + } + + return s; +} + +/** + * @def DBUS_MAJOR_VERSION + * + * The COMPILE TIME major version of libdbus, that is, the "X" in "X.Y.Z", + * as an integer literal. Consider carefully whether to use this or the + * runtime version from dbus_get_version(). + */ + +/** + * @def DBUS_MINOR_VERSION + * + * The COMPILE TIME minor version of libdbus, that is, the "Y" in "X.Y.Z", + * as an integer literal. Consider carefully whether to use this or the + * runtime version from dbus_get_version(). + */ + +/** + * @def DBUS_MICRO_VERSION + * + * The COMPILE TIME micro version of libdbus, that is, the "Z" in "X.Y.Z", + * as an integer literal. Consider carefully whether to use this or the + * runtime version from dbus_get_version(). + */ + +/** + * @def DBUS_VERSION + * + * The COMPILE TIME version of libdbus, as a single integer that has 0 in the most + * significant byte, the major version in the next most significant byte, + * the minor version in the third most significant, and the micro version in the + * least significant byte. This means two DBUS_VERSION can be compared to see + * which is higher. + * + * Consider carefully whether to use this or the runtime version from + * dbus_get_version(). + */ + +/** + * @def DBUS_VERSION_STRING + * + * The COMPILE TIME version of libdbus, as a string "X.Y.Z". + * + * Consider carefully whether to use this or the runtime version from + * dbus_get_version(). + */ + +/** + * Gets the DYNAMICALLY LINKED version of libdbus. Alternatively, there + * are macros #DBUS_MAJOR_VERSION, #DBUS_MINOR_VERSION, #DBUS_MICRO_VERSION, + * and #DBUS_VERSION which allow you to test the VERSION YOU ARE COMPILED AGAINST. + * In other words, you can get either the runtime or the compile-time version. + * Think carefully about which of these you want in a given case. + * + * The libdbus full version number is "MAJOR.MINOR.MICRO" where the + * MINOR changes if API is added, and the MICRO changes with each + * release of a MAJOR.MINOR series. The MINOR is an odd number for + * development releases and an even number for stable releases. + * + * @param major_version_p pointer to return the major version, or #NULL + * @param minor_version_p pointer to return the minor version, or #NULL + * @param micro_version_p pointer to return the micro version, or #NULL + * + */ +void +dbus_get_version (int *major_version_p, + int *minor_version_p, + int *micro_version_p) +{ + if (major_version_p) + *major_version_p = DBUS_MAJOR_VERSION; + if (minor_version_p) + *minor_version_p = DBUS_MINOR_VERSION; + if (micro_version_p) + *micro_version_p = DBUS_MICRO_VERSION; +} + + +/** @} */ /* End of public API */ diff --git a/src/3rdparty/libdbus/dbus/dbus-misc.h b/src/3rdparty/libdbus/dbus/dbus-misc.h new file mode 100644 index 00000000..3442ab78 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-misc.h @@ -0,0 +1,60 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-misc.h A few assorted public functions that don't fit elsewhere + * + * Copyright (C) 2006 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_MISC_H +#define DBUS_MISC_H + +#include <dbus/dbus-types.h> +#include <dbus/dbus-errors.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusMisc + * @{ + */ +DBUS_EXPORT +char* dbus_get_local_machine_id (void); + +DBUS_EXPORT +void dbus_get_version (int *major_version_p, + int *minor_version_p, + int *micro_version_p); + +DBUS_EXPORT +dbus_bool_t dbus_setenv (const char *variable, + const char *value); + +DBUS_EXPORT +char *dbus_try_get_local_machine_id (DBusError *error); + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_MISC_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-nonce.c b/src/3rdparty/libdbus/dbus/dbus-nonce.c new file mode 100644 index 00000000..5ac9a013 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-nonce.c @@ -0,0 +1,531 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-nonce.c Nonce handling functions used by nonce-tcp (internal to D-Bus implementation) + * + * Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <config.h> +// major sections of this file are modified code from libassuan, (C) FSF +#include "dbus-nonce.h" +#include "dbus-internals.h" +#include "dbus-protocol.h" +#include "dbus-sysdeps.h" + +#include <stdio.h> + +struct DBusNonceFile +{ + DBusString path; + DBusString dir; +}; + +static dbus_bool_t +do_check_nonce (DBusSocket fd, const DBusString *nonce, DBusError *error) +{ + DBusString buffer; + DBusString p; + size_t nleft; + dbus_bool_t result; + int n; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + nleft = 16; + + /* This is a trick to make it safe to call _dbus_string_free on these + * strings during error unwinding, even if allocating memory for them + * fails. A constant DBusString is considered to be valid to "free", + * even though there is nothing to free (of course the free operation + * is trivial, because it does not own its own buffer); but + * unlike a mutable DBusString, initializing a constant DBusString + * cannot fail. + * + * We must successfully re-initialize the strings to be mutable before + * writing to them, of course. + */ + _dbus_string_init_const (&buffer, ""); + _dbus_string_init_const (&p, ""); + + if ( !_dbus_string_init (&buffer) + || !_dbus_string_init (&p) ) { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&p); + _dbus_string_free (&buffer); + return FALSE; + } + + while (nleft) + { + int saved_errno; + + n = _dbus_read_socket (fd, &p, nleft); + saved_errno = _dbus_save_socket_errno (); + + if (n == -1 && _dbus_get_is_errno_eintr (saved_errno)) + ; + else if (n == -1 && _dbus_get_is_errno_eagain_or_ewouldblock (saved_errno)) + _dbus_sleep_milliseconds (100); + else if (n==-1) + { + dbus_set_error (error, DBUS_ERROR_IO_ERROR, "Could not read nonce from socket (fd=%" DBUS_SOCKET_FORMAT ")", _dbus_socket_printable (fd)); + _dbus_string_free (&p); + _dbus_string_free (&buffer); + return FALSE; + } + else if (!n) + { + _dbus_string_free (&p); + _dbus_string_free (&buffer); + dbus_set_error (error, DBUS_ERROR_IO_ERROR, "Could not read nonce from socket (fd=%" DBUS_SOCKET_FORMAT ")", _dbus_socket_printable (fd)); + return FALSE; + } + else + { + if (!_dbus_string_append_len (&buffer, _dbus_string_get_const_data (&p), n)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&p); + _dbus_string_free (&buffer); + return FALSE; + } + nleft -= n; + } + } + + result = _dbus_string_equal_len (&buffer, nonce, 16); + if (!result) + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, "Nonces do not match, access denied (fd=%" DBUS_SOCKET_FORMAT ")", _dbus_socket_printable (fd)); + + _dbus_string_free (&p); + _dbus_string_free (&buffer); + + return result; +} + +/** + * reads the nonce from the nonce file and stores it in a string + * + * @param fname the file to read the nonce from + * @param nonce returns the nonce. Must be an initialized string, the nonce will be appended. + * @param error error object to report possible errors + * @return FALSE iff reading the nonce fails (error is set then) + */ +dbus_bool_t +_dbus_read_nonce (const DBusString *fname, DBusString *nonce, DBusError* error) +{ + FILE *fp; + char buffer[17]; + size_t nread; + + buffer[sizeof buffer - 1] = '\0'; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_verbose ("reading nonce from file: %s\n", _dbus_string_get_const_data (fname)); + + + fp = fopen (_dbus_string_get_const_data (fname), "rb"); + if (!fp) + { + dbus_set_error (error, + _dbus_error_from_system_errno (), + "Failed to open %s for read: %s", + _dbus_string_get_const_data (fname), + _dbus_strerror_from_errno ()); + return FALSE; + } + + nread = fread (buffer, 1, sizeof buffer - 1, fp); + fclose (fp); + if (!nread) + { + dbus_set_error (error, DBUS_ERROR_FILE_NOT_FOUND, "Could not read nonce from file %s", _dbus_string_get_const_data (fname)); + return FALSE; + } + + if (!_dbus_string_append_len (nonce, buffer, sizeof buffer - 1 )) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return FALSE; + } + return TRUE; +} + +DBusSocket +_dbus_accept_with_noncefile (DBusSocket listen_fd, const DBusNonceFile *noncefile) +{ + DBusSocket fd = _dbus_socket_get_invalid (); + DBusString nonce; + + _dbus_assert (noncefile != NULL); + + /* Make it valid to "free" this even if _dbus_string_init() runs + * out of memory: see comment in do_check_nonce() */ + _dbus_string_init_const (&nonce, ""); + + if (!_dbus_string_init (&nonce)) + goto out; + + //PENDING(kdab): set better errors + if (_dbus_read_nonce (_dbus_noncefile_get_path(noncefile), &nonce, NULL) != TRUE) + goto out; + + fd = _dbus_accept (listen_fd); + + if (!_dbus_socket_is_valid (fd)) + goto out; + + if (do_check_nonce(fd, &nonce, NULL) != TRUE) { + _dbus_verbose ("nonce check failed. Closing socket.\n"); + _dbus_close_socket (&fd, NULL); + goto out; + } + +out: + _dbus_string_free (&nonce); + return fd; +} + +static dbus_bool_t +generate_and_write_nonce (const DBusString *filename, DBusError *error) +{ + DBusString nonce; + dbus_bool_t ret; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&nonce)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return FALSE; + } + + if (!_dbus_generate_random_bytes (&nonce, 16, error)) + { + _dbus_string_free (&nonce); + return FALSE; + } + + ret = _dbus_string_save_to_file (&nonce, filename, FALSE, error); + + _dbus_string_free (&nonce); + + return ret; +} + +/** + * sends the nonce over a given socket. Blocks while doing so. + * + * @param fd the file descriptor to write the nonce data to (usually a socket) + * @param noncefile the noncefile location to read the nonce from + * @param error contains error details if FALSE is returned + * @return TRUE iff the nonce was successfully sent. Note that this does not + * indicate whether the server accepted the nonce. + */ +dbus_bool_t +_dbus_send_nonce (DBusSocket fd, + const DBusString *noncefile, + DBusError *error) +{ + dbus_bool_t read_result; + int send_result; + DBusString nonce; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (_dbus_string_get_length (noncefile) == 0) + return FALSE; + + if (!_dbus_string_init (&nonce)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return FALSE; + } + + read_result = _dbus_read_nonce (noncefile, &nonce, error); + if (!read_result) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_string_free (&nonce); + return FALSE; + } + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + send_result = _dbus_write_socket (fd, &nonce, 0, _dbus_string_get_length (&nonce)); + + _dbus_string_free (&nonce); + + if (send_result == -1) + { + dbus_set_error (error, + _dbus_error_from_system_errno (), + "Failed to send nonce (fd=%" DBUS_SOCKET_FORMAT "): %s", + _dbus_socket_printable (fd), + _dbus_strerror_from_errno ()); + return FALSE; + } + + return TRUE; +} + +static dbus_bool_t +do_noncefile_create (DBusNonceFile **noncefile_out, + DBusError *error, + dbus_bool_t use_subdir) +{ + DBusNonceFile *noncefile = NULL; + DBusString randomStr; + const char *tmp; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_assert (noncefile_out != NULL); + _dbus_assert (*noncefile_out == NULL); + + noncefile = dbus_new0 (DBusNonceFile, 1); + if (noncefile == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return FALSE; + } + + /* Make it valid to "free" these even if _dbus_string_init() runs + * out of memory: see comment in do_check_nonce() */ + _dbus_string_init_const (&randomStr, ""); + _dbus_string_init_const (&noncefile->dir, ""); + _dbus_string_init_const (&noncefile->path, ""); + + if (!_dbus_string_init (&randomStr)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto on_error; + } + + if (!_dbus_generate_random_ascii (&randomStr, 8, error)) + { + goto on_error; + } + + tmp = _dbus_get_tmpdir (); + + if (!_dbus_string_init (&noncefile->dir) + || tmp == NULL + || !_dbus_string_append (&noncefile->dir, tmp)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto on_error; + } + if (use_subdir) + { + if (!_dbus_string_append (&noncefile->dir, "/dbus_nonce-") + || !_dbus_string_append (&noncefile->dir, _dbus_string_get_const_data (&randomStr)) ) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto on_error; + } + if (!_dbus_string_init (&noncefile->path) + || !_dbus_string_copy (&noncefile->dir, 0, &noncefile->path, 0) + || !_dbus_string_append (&noncefile->path, "/nonce")) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto on_error; + } + if (!_dbus_create_directory (&noncefile->dir, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto on_error; + } + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + } + else + { + if (!_dbus_string_init (&noncefile->path) + || !_dbus_string_copy (&noncefile->dir, 0, &noncefile->path, 0) + || !_dbus_string_append (&noncefile->path, "/dbus_nonce-") + || !_dbus_string_append (&noncefile->path, _dbus_string_get_const_data (&randomStr))) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto on_error; + } + + } + + if (!generate_and_write_nonce (&noncefile->path, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + if (use_subdir) + _dbus_delete_directory (&noncefile->dir, NULL); //we ignore possible errors deleting the dir and return the write error instead + goto on_error; + } + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + *noncefile_out = noncefile; + _dbus_string_free (&randomStr); + + return TRUE; + on_error: + if (use_subdir && _dbus_string_get_length (&noncefile->dir) != 0) + _dbus_delete_directory (&noncefile->dir, NULL); + _dbus_string_free (&noncefile->dir); + _dbus_string_free (&noncefile->path); + dbus_free (noncefile); + _dbus_string_free (&randomStr); + return FALSE; +} + +#ifdef DBUS_WIN +/** + * creates a nonce file in a user-readable location and writes a generated nonce to it + * + * @param noncefile_out returns the nonce file location + * @param error error details if creating the nonce file fails + * @return TRUE iff the nonce file was successfully created + */ +dbus_bool_t +_dbus_noncefile_create (DBusNonceFile **noncefile_out, + DBusError *error) +{ + return do_noncefile_create (noncefile_out, error, /*use_subdir=*/FALSE); +} + +/** + * deletes the noncefile and frees the DBusNonceFile object. + * + * If noncefile_location points to #NULL, nothing is freed or deleted, + * similar to dbus_error_free(). + * + * @param noncefile_location the nonce file to delete. Contents will be freed and cleared to #NULL. + * @param error error details if the nonce file could not be deleted + * @return TRUE + */ +dbus_bool_t +_dbus_noncefile_delete (DBusNonceFile **noncefile_location, + DBusError *error) +{ + DBusNonceFile *noncefile; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + _dbus_assert (noncefile_location != NULL); + + noncefile = *noncefile_location; + *noncefile_location = NULL; + + if (noncefile == NULL) + { + /* Nothing to do */ + return TRUE; + } + + _dbus_delete_file (&noncefile->path, error); + _dbus_string_free (&noncefile->dir); + _dbus_string_free (&noncefile->path); + dbus_free (noncefile); + return TRUE; +} + +#else +/** + * creates a nonce file in a user-readable location and writes a generated nonce to it. + * Initializes the noncefile object. + * + * @param noncefile_out returns the nonce file location + * @param error error details if creating the nonce file fails + * @return TRUE iff the nonce file was successfully created + */ +dbus_bool_t +_dbus_noncefile_create (DBusNonceFile **noncefile_out, + DBusError *error) +{ + return do_noncefile_create (noncefile_out, error, /*use_subdir=*/TRUE); +} + +/** + * deletes the noncefile and frees the DBusNonceFile object. + * + * If noncefile_location points to #NULL, nothing is freed or deleted, + * similar to dbus_error_free(). + * + * @param noncefile_location the nonce file to delete. Contents will be freed and cleared to #NULL. + * @param error error details if the nonce file could not be deleted + * @return TRUE + */ +dbus_bool_t +_dbus_noncefile_delete (DBusNonceFile **noncefile_location, + DBusError *error) +{ + DBusNonceFile *noncefile; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + _dbus_assert (noncefile_location != NULL); + + noncefile = *noncefile_location; + *noncefile_location = NULL; + + if (noncefile == NULL) + { + /* Nothing to do */ + return TRUE; + } + + _dbus_delete_directory (&noncefile->dir, error); + _dbus_string_free (&noncefile->dir); + _dbus_string_free (&noncefile->path); + dbus_free (noncefile); + return TRUE; +} +#endif + + +/** + * returns the absolute file path of the nonce file + * + * @param noncefile an initialized noncefile object + * @return the absolute path of the nonce file + */ +const DBusString* +_dbus_noncefile_get_path (const DBusNonceFile *noncefile) +{ + _dbus_assert (noncefile); + return &noncefile->path; +} + +/** + * reads data from a file descriptor and checks if the received data matches + * the data in the given noncefile. + * + * @param fd the file descriptor to read the nonce from + * @param noncefile the nonce file to check the received data against + * @param error error details on fail + * @return TRUE iff a nonce could be successfully read from the file descriptor + * and matches the nonce from the given nonce file + */ +dbus_bool_t +_dbus_noncefile_check_nonce (DBusSocket fd, + const DBusNonceFile *noncefile, + DBusError* error) +{ + return do_check_nonce (fd, _dbus_noncefile_get_path (noncefile), error); +} + + +/** @} end of nonce */ diff --git a/src/3rdparty/libdbus/dbus/dbus-nonce.h b/src/3rdparty/libdbus/dbus/dbus-nonce.h new file mode 100644 index 00000000..d26dc397 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-nonce.h @@ -0,0 +1,69 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-nonce.h Nonce handling functions used by nonce-tcp (internal to D-Bus implementation) + * + * Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef DBUS_NONCE_H +#define DBUS_NONCE_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-errors.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-sysdeps.h> + +DBUS_BEGIN_DECLS + +typedef struct DBusNonceFile DBusNonceFile; + +// server + +dbus_bool_t _dbus_noncefile_create (DBusNonceFile **noncefile_out, + DBusError *error); + +dbus_bool_t _dbus_noncefile_delete (DBusNonceFile **noncefile_location, + DBusError *error); + +dbus_bool_t _dbus_noncefile_check_nonce (DBusSocket fd, + const DBusNonceFile *noncefile, + DBusError *error); + +const DBusString* _dbus_noncefile_get_path (const DBusNonceFile *noncefile); + +DBusSocket _dbus_accept_with_noncefile(DBusSocket listen_fd, + const DBusNonceFile *noncefile); + +// shared + +dbus_bool_t _dbus_read_nonce (const DBusString *fname, + DBusString *nonce, + DBusError *error); + +// client + +dbus_bool_t _dbus_send_nonce (DBusSocket fd, + const DBusString *noncefile, + DBusError *error); + +DBUS_END_DECLS + +#endif /* DBUS_NONCE_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-object-tree.c b/src/3rdparty/libdbus/dbus/dbus-object-tree.c new file mode 100644 index 00000000..bb7d5479 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-object-tree.c @@ -0,0 +1,2333 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-object-tree.c DBusObjectTree (internals of DBusConnection) + * + * Copyright (C) 2003, 2005 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-object-tree.h" +#include "dbus-connection-internal.h" +#include "dbus-internals.h" +#include "dbus-hash.h" +#include "dbus-protocol.h" +#include "dbus-string.h" +#include <dbus/dbus-test-tap.h> +#include <string.h> +#include <stdlib.h> + +/** + * @defgroup DBusObjectTree A hierarchy of objects with container-contained relationship + * @ingroup DBusInternals + * @brief DBusObjectTree is used by DBusConnection to track the object tree + * + * Types and functions related to DBusObjectTree. These + * are all library-internal. + * + * @{ + */ + +/** Subnode of the object hierarchy */ +typedef struct DBusObjectSubtree DBusObjectSubtree; + +static DBusObjectSubtree* _dbus_object_subtree_new (const char *name, + const DBusObjectPathVTable *vtable, + void *user_data); +static DBusObjectSubtree* _dbus_object_subtree_ref (DBusObjectSubtree *subtree); +static void _dbus_object_subtree_unref (DBusObjectSubtree *subtree); + +/** + * Internals of DBusObjectTree + */ +struct DBusObjectTree +{ + int refcount; /**< Reference count */ + DBusConnection *connection; /**< Connection this tree belongs to */ + + DBusObjectSubtree *root; /**< Root of the tree ("/" node) */ +}; + +/** + * Struct representing a single registered subtree handler, or node + * that's a parent of a registered subtree handler. If + * message_function != NULL there's actually a handler at this node. + */ +struct DBusObjectSubtree +{ + DBusAtomic refcount; /**< Reference count */ + DBusObjectSubtree *parent; /**< Parent node */ + DBusObjectPathUnregisterFunction unregister_function; /**< Function to call on unregister */ + DBusObjectPathMessageFunction message_function; /**< Function to handle messages */ + void *user_data; /**< Data for functions */ + DBusObjectSubtree **subtrees; /**< Child nodes */ + int n_subtrees; /**< Number of child nodes */ + int max_subtrees; /**< Number of allocated entries in subtrees */ + unsigned int invoke_as_fallback : 1; /**< Whether to invoke message_function when child nodes don't handle the message */ + char name[1]; /**< Allocated as large as necessary */ +}; + +/** + * Creates a new object tree, representing a mapping from paths + * to handler vtables. + * + * @param connection the connection this tree belongs to + * @returns the new tree or #NULL if no memory + */ +DBusObjectTree* +_dbus_object_tree_new (DBusConnection *connection) +{ + DBusObjectTree *tree; + + /* the connection passed in here isn't fully constructed, + * so don't do anything more than store a pointer to + * it + */ + + tree = dbus_new0 (DBusObjectTree, 1); + if (tree == NULL) + goto oom; + + tree->refcount = 1; + tree->connection = connection; + tree->root = _dbus_object_subtree_new ("/", NULL, NULL); + if (tree->root == NULL) + goto oom; + tree->root->invoke_as_fallback = TRUE; + + return tree; + + oom: + if (tree) + { + dbus_free (tree); + } + + return NULL; +} + +/** + * Increment the reference count + * @param tree the object tree + * @returns the object tree + */ +DBusObjectTree * +_dbus_object_tree_ref (DBusObjectTree *tree) +{ + _dbus_assert (tree->refcount > 0); + + tree->refcount += 1; + + return tree; +} + +/** + * Decrement the reference count + * @param tree the object tree + */ +void +_dbus_object_tree_unref (DBusObjectTree *tree) +{ + _dbus_assert (tree->refcount > 0); + + tree->refcount -= 1; + + if (tree->refcount == 0) + { + _dbus_object_tree_free_all_unlocked (tree); + + dbus_free (tree); + } +} + +/** Set to 1 to get a bunch of debug spew about finding the + * subtree nodes + */ +#define VERBOSE_FIND 0 + +static DBusObjectSubtree* +find_subtree_recurse (DBusObjectSubtree *subtree, + const char **path, + dbus_bool_t create_if_not_found, + int *index_in_parent, + dbus_bool_t *exact_match) +{ + int i, j; + dbus_bool_t return_deepest_match; + + return_deepest_match = exact_match != NULL; + + _dbus_assert (!(return_deepest_match && create_if_not_found)); + + if (path[0] == NULL) + { +#if VERBOSE_FIND + _dbus_verbose (" path exhausted, returning %s\n", + subtree->name); +#endif + if (exact_match != NULL) + *exact_match = TRUE; + return subtree; + } + +#if VERBOSE_FIND + _dbus_verbose (" searching children of %s for %s\n", + subtree->name, path[0]); +#endif + + i = 0; + j = subtree->n_subtrees; + while (i < j) + { + int k, v; + + k = (i + j) / 2; + v = strcmp (path[0], subtree->subtrees[k]->name); + +#if VERBOSE_FIND + _dbus_verbose (" %s cmp %s = %d\n", + path[0], subtree->subtrees[k]->name, + v); +#endif + + if (v == 0) + { + if (index_in_parent) + { +#if VERBOSE_FIND + _dbus_verbose (" storing parent index %d\n", k); +#endif + *index_in_parent = k; + } + + if (return_deepest_match) + { + DBusObjectSubtree *next; + + next = find_subtree_recurse (subtree->subtrees[k], + &path[1], create_if_not_found, + index_in_parent, exact_match); + if (next == NULL && + subtree->invoke_as_fallback) + { +#if VERBOSE_FIND + _dbus_verbose (" no deeper match found, returning %s\n", + subtree->name); +#endif + if (exact_match != NULL) + *exact_match = FALSE; + return subtree; + } + else + return next; + } + else + return find_subtree_recurse (subtree->subtrees[k], + &path[1], create_if_not_found, + index_in_parent, exact_match); + } + else if (v < 0) + { + j = k; + } + else + { + i = k + 1; + } + } + +#if VERBOSE_FIND + _dbus_verbose (" no match found, current tree %s, create_if_not_found = %d\n", + subtree->name, create_if_not_found); +#endif + + if (create_if_not_found) + { + DBusObjectSubtree* child; + int child_pos, new_n_subtrees; + +#if VERBOSE_FIND + _dbus_verbose (" creating subtree %s\n", + path[0]); +#endif + + child = _dbus_object_subtree_new (path[0], + NULL, NULL); + if (child == NULL) + return NULL; + + new_n_subtrees = subtree->n_subtrees + 1; + if (new_n_subtrees > subtree->max_subtrees) + { + int new_max_subtrees; + DBusObjectSubtree **new_subtrees; + + new_max_subtrees = subtree->max_subtrees == 0 ? 1 : 2 * subtree->max_subtrees; + new_subtrees = dbus_realloc (subtree->subtrees, + new_max_subtrees * sizeof (DBusObjectSubtree*)); + if (new_subtrees == NULL) + { + _dbus_object_subtree_unref (child); + return NULL; + } + subtree->subtrees = new_subtrees; + subtree->max_subtrees = new_max_subtrees; + } + + /* The binary search failed, so i == j points to the + place the child should be inserted. */ + child_pos = i; + _dbus_assert (child_pos < new_n_subtrees && + new_n_subtrees <= subtree->max_subtrees); + if (child_pos + 1 < new_n_subtrees) + { + memmove (&subtree->subtrees[child_pos+1], + &subtree->subtrees[child_pos], + (new_n_subtrees - child_pos - 1) * + sizeof subtree->subtrees[0]); + } + subtree->subtrees[child_pos] = child; + + if (index_in_parent) + *index_in_parent = child_pos; + subtree->n_subtrees = new_n_subtrees; + child->parent = subtree; + + return find_subtree_recurse (child, + &path[1], create_if_not_found, + index_in_parent, exact_match); + } + else + { + if (exact_match != NULL) + *exact_match = FALSE; + return (return_deepest_match && subtree->invoke_as_fallback) ? subtree : NULL; + } +} + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +static DBusObjectSubtree* +find_subtree (DBusObjectTree *tree, + const char **path, + int *index_in_parent) +{ + DBusObjectSubtree *subtree; + +#if VERBOSE_FIND + _dbus_verbose ("Looking for exact registered subtree\n"); +#endif + + subtree = find_subtree_recurse (tree->root, path, FALSE, index_in_parent, NULL); + + if (subtree && subtree->message_function == NULL) + return NULL; + else + return subtree; +} +#endif + +static DBusObjectSubtree* +lookup_subtree (DBusObjectTree *tree, + const char **path) +{ +#if VERBOSE_FIND + _dbus_verbose ("Looking for subtree\n"); +#endif + return find_subtree_recurse (tree->root, path, FALSE, NULL, NULL); +} + +static DBusObjectSubtree* +find_handler (DBusObjectTree *tree, + const char **path, + dbus_bool_t *exact_match) +{ +#if VERBOSE_FIND + _dbus_verbose ("Looking for deepest handler\n"); +#endif + _dbus_assert (exact_match != NULL); + + *exact_match = FALSE; /* ensure always initialized */ + + return find_subtree_recurse (tree->root, path, FALSE, NULL, exact_match); +} + +static DBusObjectSubtree* +ensure_subtree (DBusObjectTree *tree, + const char **path) +{ +#if VERBOSE_FIND + _dbus_verbose ("Ensuring subtree\n"); +#endif + return find_subtree_recurse (tree->root, path, TRUE, NULL, NULL); +} + +static char *flatten_path (const char **path); + +/** + * Registers a new subtree in the global object tree. + * + * @param tree the global object tree + * @param fallback #TRUE to handle messages to children of this path + * @param path NULL-terminated array of path elements giving path to subtree + * @param vtable the vtable used to traverse this subtree + * @param user_data user data to pass to methods in the vtable + * @param error address where an error can be returned + * @returns #FALSE if an error (#DBUS_ERROR_NO_MEMORY or + * #DBUS_ERROR_OBJECT_PATH_IN_USE) is reported + */ +dbus_bool_t +_dbus_object_tree_register (DBusObjectTree *tree, + dbus_bool_t fallback, + const char **path, + const DBusObjectPathVTable *vtable, + void *user_data, + DBusError *error) +{ + DBusObjectSubtree *subtree; + + _dbus_assert (tree != NULL); + _dbus_assert (vtable->message_function != NULL); + _dbus_assert (path != NULL); + + subtree = ensure_subtree (tree, path); + if (subtree == NULL) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (subtree->message_function != NULL) + { + if (error != NULL) + { + char *complete_path = flatten_path (path); + + dbus_set_error (error, DBUS_ERROR_OBJECT_PATH_IN_USE, + "A handler is already registered for %s", + complete_path ? complete_path + : "(cannot represent path: out of memory!)"); + + dbus_free (complete_path); + } + + return FALSE; + } + + subtree->message_function = vtable->message_function; + subtree->unregister_function = vtable->unregister_function; + subtree->user_data = user_data; + subtree->invoke_as_fallback = fallback != FALSE; + + return TRUE; +} + +/** + * Attempts to unregister the given subtree. If the subtree is registered, + * stores its unregister function and user data for later use and returns + * #TRUE. If subtree is not registered, simply returns #FALSE. Does not free + * subtree or remove it from the object tree. + * + * @param subtree the subtree to unregister + * @param unregister_function_out stores subtree's unregister_function + * @param user_data_out stores subtree's user_data + * @return #FALSE if the subtree was not registered, #TRUE on success + */ +static dbus_bool_t +unregister_subtree (DBusObjectSubtree *subtree, + DBusObjectPathUnregisterFunction *unregister_function_out, + void **user_data_out) +{ + _dbus_assert (subtree != NULL); + _dbus_assert (unregister_function_out != NULL); + _dbus_assert (user_data_out != NULL); + + /* Confirm subtree is registered */ + if (subtree->message_function != NULL) + { + subtree->message_function = NULL; + + *unregister_function_out = subtree->unregister_function; + *user_data_out = subtree->user_data; + + subtree->unregister_function = NULL; + subtree->user_data = NULL; + + return TRUE; + } + else + { + /* Assert that this unregistered subtree is either the root node or has + children, otherwise we have a dangling path which should never + happen */ + _dbus_assert (subtree->parent == NULL || subtree->n_subtrees > 0); + + /* The subtree is not registered */ + return FALSE; + } +} + +/** + * Attempts to remove a child subtree from its parent. If removal is + * successful, also frees the child. Returns #TRUE on success, #FALSE + * otherwise. A #FALSE return value tells unregister_and_free_path_recurse to + * stop attempting to remove ancestors, i.e., that no ancestors of the + * specified child are eligible for removal. + * + * @param parent parent from which to remove child + * @param child_index parent->subtrees index of child to remove + * @return #TRUE if removal and free succeed, #FALSE otherwise + */ +static dbus_bool_t +attempt_child_removal (DBusObjectSubtree *parent, + int child_index) +{ + /* Candidate for removal */ + DBusObjectSubtree* candidate; + + _dbus_assert (parent != NULL); + _dbus_assert (child_index >= 0 && child_index < parent->n_subtrees); + + candidate = parent->subtrees[child_index]; + _dbus_assert (candidate != NULL); + + if (candidate->n_subtrees == 0 && candidate->message_function == NULL) + { + /* The candidate node is childless and is not a registered + path, so... */ + + /* ... remove it from its parent... */ + /* Assumes a 0-byte memmove is OK */ + memmove (&parent->subtrees[child_index], + &parent->subtrees[child_index + 1], + (parent->n_subtrees - child_index - 1) + * sizeof (parent->subtrees[0])); + parent->n_subtrees -= 1; + + /* ... and free it */ + candidate->parent = NULL; + _dbus_object_subtree_unref (candidate); + + return TRUE; + } + return FALSE; +} + +/** + * Searches the object tree for a registered subtree node at the given path. + * If a registered node is found, it is removed from the tree and freed, and + * TRUE is returned. If a registered subtree node is not found at the given + * path, the tree is not modified and FALSE is returned. + * + * The found node's unregister_function and user_data are returned in the + * corresponding _out arguments. The caller should define these variables and + * pass their addresses as arguments. + * + * Likewise, the caller should define and set to TRUE a boolean variable, then + * pass its address as the continue_removal_attempts argument. + * + * Once a matching registered node is found, removed and freed, the recursive + * return path is traversed. Along the way, eligible ancestor nodes are + * removed and freed. An ancestor node is eligible for removal if and only if + * 1) it has no children, i.e., it has become childless and 2) it is not itself + * a registered handler. + * + * For example, suppose /A/B and /A/C are registered paths, and that these are + * the only paths in the tree. If B is removed and freed, C is still reachable + * through A, so A cannot be removed and freed. If C is subsequently removed + * and freed, then A becomes a childless node and it becomes eligible for + * removal, and will be removed and freed. + * + * Similarly, suppose /A is a registered path, and /A/B is also a registered + * path, and that these are the only paths in the tree. If B is removed and + * freed, then even though A has become childless, it can't be freed because it + * refers to a path that is still registered. + * + * @param subtree subtree from which to start the search, root for initial call + * @param path path to subtree (same as _dbus_object_tree_unregister_and_unlock) + * @param continue_removal_attempts pointer to a bool, #TRUE for initial call + * @param unregister_function_out returns the found node's unregister_function + * @param user_data_out returns the found node's user_data + * @returns #TRUE if a registered node was found at path, #FALSE otherwise + */ +static dbus_bool_t +unregister_and_free_path_recurse +(DBusObjectSubtree *subtree, + const char **path, + dbus_bool_t *continue_removal_attempts, + DBusObjectPathUnregisterFunction *unregister_function_out, + void **user_data_out) +{ + int i, j; + + _dbus_assert (continue_removal_attempts != NULL); + _dbus_assert (*continue_removal_attempts); + _dbus_assert (unregister_function_out != NULL); + _dbus_assert (user_data_out != NULL); + + if (path[0] == NULL) + return unregister_subtree (subtree, unregister_function_out, user_data_out); + + i = 0; + j = subtree->n_subtrees; + while (i < j) + { + int k, v; + + k = (i + j) / 2; + v = strcmp (path[0], subtree->subtrees[k]->name); + + if (v == 0) + { + dbus_bool_t freed; + freed = unregister_and_free_path_recurse (subtree->subtrees[k], + &path[1], + continue_removal_attempts, + unregister_function_out, + user_data_out); + if (freed && *continue_removal_attempts) + *continue_removal_attempts = attempt_child_removal (subtree, k); + return freed; + } + else if (v < 0) + { + j = k; + } + else + { + i = k + 1; + } + } + return FALSE; +} + +/** + * Unregisters an object subtree that was registered with the + * same path. + * + * @param tree the global object tree + * @param path path to the subtree (same as the one passed to _dbus_object_tree_register()) + */ +void +_dbus_object_tree_unregister_and_unlock (DBusObjectTree *tree, + const char **path) +{ + dbus_bool_t found_subtree; + dbus_bool_t continue_removal_attempts; + DBusObjectPathUnregisterFunction unregister_function; + void *user_data; + DBusConnection *connection; + + _dbus_assert (tree != NULL); + _dbus_assert (path != NULL); + + continue_removal_attempts = TRUE; + unregister_function = NULL; + user_data = NULL; + + found_subtree = unregister_and_free_path_recurse (tree->root, + path, + &continue_removal_attempts, + &unregister_function, + &user_data); + +#ifndef DBUS_DISABLE_CHECKS + if (found_subtree == FALSE) + { + _dbus_warn ("Attempted to unregister path (path[0] = %s path[1] = %s) which isn't registered", + path[0] ? path[0] : "null", + (path[0] && path[1]) ? path[1] : "null"); + goto unlock; + } +#else + _dbus_assert (found_subtree == TRUE); +#endif + +unlock: + connection = tree->connection; + + /* Unlock and call application code */ +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (connection) +#endif + { + _dbus_connection_ref_unlocked (connection); + _dbus_verbose ("unlock\n"); + _dbus_connection_unlock (connection); + } + + if (unregister_function) + (* unregister_function) (connection, user_data); + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (connection) +#endif + dbus_connection_unref (connection); +} + +static void +free_subtree_recurse (DBusConnection *connection, + DBusObjectSubtree *subtree) +{ + /* Delete them from the end, for slightly + * more robustness against odd reentrancy. + */ + while (subtree->n_subtrees > 0) + { + DBusObjectSubtree *child; + + child = subtree->subtrees[subtree->n_subtrees - 1]; + subtree->subtrees[subtree->n_subtrees - 1] = NULL; + subtree->n_subtrees -= 1; + child->parent = NULL; + + free_subtree_recurse (connection, child); + } + + /* Call application code */ + if (subtree->unregister_function) + (* subtree->unregister_function) (connection, + subtree->user_data); + + subtree->message_function = NULL; + subtree->unregister_function = NULL; + subtree->user_data = NULL; + + /* Now free ourselves */ + _dbus_object_subtree_unref (subtree); +} + +/** + * Free all the handlers in the tree. Lock on tree's connection + * must not be held. + * + * @param tree the object tree + */ +void +_dbus_object_tree_free_all_unlocked (DBusObjectTree *tree) +{ + if (tree->root) + free_subtree_recurse (tree->connection, + tree->root); + tree->root = NULL; +} + +static dbus_bool_t +_dbus_object_tree_list_registered_unlocked (DBusObjectTree *tree, + const char **parent_path, + char ***child_entries) +{ + DBusObjectSubtree *subtree; + char **retval; + + _dbus_assert (parent_path != NULL); + _dbus_assert (child_entries != NULL); + + *child_entries = NULL; + + subtree = lookup_subtree (tree, parent_path); + if (subtree == NULL) + { + retval = dbus_new0 (char *, 1); + } + else + { + int i; + retval = dbus_new0 (char*, subtree->n_subtrees + 1); + if (retval == NULL) + goto out; + i = 0; + while (i < subtree->n_subtrees) + { + retval[i] = _dbus_strdup (subtree->subtrees[i]->name); + if (retval[i] == NULL) + { + dbus_free_string_array (retval); + retval = NULL; + goto out; + } + ++i; + } + } + + out: + + *child_entries = retval; + return retval != NULL; +} + +static DBusHandlerResult +handle_default_introspect_and_unlock (DBusObjectTree *tree, + DBusMessage *message, + const char **path) +{ + DBusString xml; + DBusHandlerResult result; + char **children; + int i; + DBusMessage *reply; + DBusMessageIter iter; + const char *v_STRING; + dbus_bool_t already_unlocked; + + /* We have the connection lock here */ + + already_unlocked = FALSE; + + _dbus_verbose (" considering default Introspect() handler...\n"); + + reply = NULL; + + if (!dbus_message_is_method_call (message, + DBUS_INTERFACE_INTROSPECTABLE, + "Introspect")) + { +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (tree->connection) +#endif + { + _dbus_verbose ("unlock\n"); + _dbus_connection_unlock (tree->connection); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + _dbus_verbose (" using default Introspect() handler!\n"); + + if (!_dbus_string_init (&xml)) + { +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (tree->connection) +#endif + { + _dbus_verbose ("unlock\n"); + _dbus_connection_unlock (tree->connection); + } + + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + result = DBUS_HANDLER_RESULT_NEED_MEMORY; + + children = NULL; + if (!_dbus_object_tree_list_registered_unlocked (tree, path, &children)) + goto out; + + if (!_dbus_string_append (&xml, DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE)) + goto out; + + if (!_dbus_string_append (&xml, "<node>\n")) + goto out; + + i = 0; + while (children[i] != NULL) + { + if (!_dbus_string_append_printf (&xml, " <node name=\"%s\"/>\n", + children[i])) + goto out; + + ++i; + } + + if (!_dbus_string_append (&xml, "</node>\n")) + goto out; + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto out; + + dbus_message_iter_init_append (reply, &iter); + v_STRING = _dbus_string_get_const_data (&xml); + if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &v_STRING)) + goto out; + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (tree->connection) +#endif + { + already_unlocked = TRUE; + + if (!_dbus_connection_send_and_unlock (tree->connection, reply, NULL)) + goto out; + } + + result = DBUS_HANDLER_RESULT_HANDLED; + + out: +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (tree->connection) +#endif + { + if (!already_unlocked) + { + _dbus_verbose ("unlock\n"); + _dbus_connection_unlock (tree->connection); + } + } + + _dbus_string_free (&xml); + dbus_free_string_array (children); + if (reply) + dbus_message_unref (reply); + + return result; +} + +/** + * Tries to dispatch a message by directing it to handler for the + * object path listed in the message header, if any. Messages are + * dispatched first to the registered handler that matches the largest + * number of path elements; that is, message to /foo/bar/baz would go + * to the handler for /foo/bar before the one for /foo. + * + * @todo thread problems + * + * @param tree the global object tree + * @param message the message to dispatch + * @param found_object return location for the object + * @returns whether message was handled successfully + */ +DBusHandlerResult +_dbus_object_tree_dispatch_and_unlock (DBusObjectTree *tree, + DBusMessage *message, + dbus_bool_t *found_object) +{ + char **path; + dbus_bool_t exact_match; + DBusList *list; + DBusList *link; + DBusHandlerResult result; + DBusObjectSubtree *subtree; + +#if 0 + _dbus_verbose ("Dispatch of message by object path\n"); +#endif + + path = NULL; + if (!dbus_message_get_path_decomposed (message, &path)) + { +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (tree->connection) +#endif + { + _dbus_verbose ("unlock\n"); + _dbus_connection_unlock (tree->connection); + } + + _dbus_verbose ("No memory to get decomposed path\n"); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + if (path == NULL) + { +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (tree->connection) +#endif + { + _dbus_verbose ("unlock\n"); + _dbus_connection_unlock (tree->connection); + } + + _dbus_verbose ("No path field in message\n"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + /* Find the deepest path that covers the path in the message */ + subtree = find_handler (tree, (const char**) path, &exact_match); + + if (found_object) + *found_object = !!subtree; + + /* Build a list of all paths that cover the path in the message */ + + list = NULL; + + while (subtree != NULL) + { + if (subtree->message_function != NULL && (exact_match || subtree->invoke_as_fallback)) + { + _dbus_object_subtree_ref (subtree); + + /* run deepest paths first */ + if (!_dbus_list_append (&list, subtree)) + { + result = DBUS_HANDLER_RESULT_NEED_MEMORY; + _dbus_object_subtree_unref (subtree); + goto free_and_return; + } + } + + exact_match = FALSE; + subtree = subtree->parent; + } + + _dbus_verbose ("%d handlers in the path tree for this message\n", + _dbus_list_get_length (&list)); + + /* Invoke each handler in the list */ + + result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + link = _dbus_list_get_first_link (&list); + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (&list, link); + subtree = link->data; + + /* message_function is NULL if we're unregistered + * due to reentrancy + */ + if (subtree->message_function) + { + DBusObjectPathMessageFunction message_function; + void *user_data; + + message_function = subtree->message_function; + user_data = subtree->user_data; + +#if 0 + _dbus_verbose (" (invoking a handler)\n"); +#endif + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (tree->connection) +#endif + { + _dbus_verbose ("unlock\n"); + _dbus_connection_unlock (tree->connection); + } + + /* FIXME you could unregister the subtree in another thread + * before we invoke the callback, and I can't figure out a + * good way to solve this. + */ + + result = (* message_function) (tree->connection, + message, + user_data); + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (tree->connection) +#endif + _dbus_connection_lock (tree->connection); + + if (result != DBUS_HANDLER_RESULT_NOT_YET_HANDLED) + goto free_and_return; + } + + link = next; + } + + free_and_return: + + if (result == DBUS_HANDLER_RESULT_NOT_YET_HANDLED) + { + /* This hardcoded default handler does a minimal Introspect() + */ + result = handle_default_introspect_and_unlock (tree, message, + (const char**) path); + } + else + { +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (tree->connection) +#endif + { + _dbus_verbose ("unlock\n"); + _dbus_connection_unlock (tree->connection); + } + } + + while (list != NULL) + { + link = _dbus_list_get_first_link (&list); + _dbus_object_subtree_unref (link->data); + _dbus_list_remove_link (&list, link); + } + + dbus_free_string_array (path); + + return result; +} + +/** + * Looks up the data passed to _dbus_object_tree_register() for a + * handler at the given path. + * + * @param tree the global object tree + * @param path NULL-terminated array of path elements giving path to subtree + * @returns the object's user_data or #NULL if none found + */ +void* +_dbus_object_tree_get_user_data_unlocked (DBusObjectTree *tree, + const char **path) +{ + dbus_bool_t exact_match; + DBusObjectSubtree *subtree; + + _dbus_assert (tree != NULL); + _dbus_assert (path != NULL); + + /* Find the deepest path that covers the path in the message */ + subtree = find_handler (tree, (const char**) path, &exact_match); + + if ((subtree == NULL) || !exact_match) + { + _dbus_verbose ("No object at specified path found\n"); + return NULL; + } + + return subtree->user_data; +} + +/** + * Allocates a subtree object. + * + * @param name name to duplicate. + * @returns newly-allocated subtree + */ +static DBusObjectSubtree* +allocate_subtree_object (const char *name) +{ + int len; + DBusObjectSubtree *subtree; + const size_t front_padding = _DBUS_STRUCT_OFFSET (DBusObjectSubtree, name); + + _dbus_assert (name != NULL); + + len = strlen (name); + + subtree = dbus_malloc0 (MAX (front_padding + (len + 1), sizeof (DBusObjectSubtree))); + + if (subtree == NULL) + return NULL; + + memcpy (subtree->name, name, len + 1); + + return subtree; +} + +static DBusObjectSubtree* +_dbus_object_subtree_new (const char *name, + const DBusObjectPathVTable *vtable, + void *user_data) +{ + DBusObjectSubtree *subtree; + + subtree = allocate_subtree_object (name); + if (subtree == NULL) + goto oom; + + _dbus_assert (name != NULL); + + subtree->parent = NULL; + + if (vtable) + { + subtree->message_function = vtable->message_function; + subtree->unregister_function = vtable->unregister_function; + } + else + { + subtree->message_function = NULL; + subtree->unregister_function = NULL; + } + + subtree->user_data = user_data; + _dbus_atomic_inc (&subtree->refcount); + subtree->subtrees = NULL; + subtree->n_subtrees = 0; + subtree->max_subtrees = 0; + subtree->invoke_as_fallback = FALSE; + + return subtree; + + oom: + return NULL; +} + +static DBusObjectSubtree * +_dbus_object_subtree_ref (DBusObjectSubtree *subtree) +{ +#ifdef DBUS_DISABLE_ASSERT + _dbus_atomic_inc (&subtree->refcount); +#else + dbus_int32_t old_value; + + old_value = _dbus_atomic_inc (&subtree->refcount); + _dbus_assert (old_value > 0); +#endif + + return subtree; +} + +static void +_dbus_object_subtree_unref (DBusObjectSubtree *subtree) +{ + dbus_int32_t old_value; + + old_value = _dbus_atomic_dec (&subtree->refcount); + _dbus_assert (old_value > 0); + + if (old_value == 1) + { + _dbus_assert (subtree->unregister_function == NULL); + _dbus_assert (subtree->message_function == NULL); + + dbus_free (subtree->subtrees); + dbus_free (subtree); + } +} + +/** + * Lists the registered fallback handlers and object path handlers at + * the given parent_path. The returned array should be freed with + * dbus_free_string_array(). + * + * @param tree the object tree + * @param parent_path the path to list the child handlers of + * @param child_entries returns #NULL-terminated array of children + * @returns #FALSE if no memory to allocate the child entries + */ +dbus_bool_t +_dbus_object_tree_list_registered_and_unlock (DBusObjectTree *tree, + const char **parent_path, + char ***child_entries) +{ + dbus_bool_t result; + + result = _dbus_object_tree_list_registered_unlocked (tree, + parent_path, + child_entries); + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + if (tree->connection) +#endif + { + _dbus_verbose ("unlock\n"); + _dbus_connection_unlock (tree->connection); + } + + return result; +} + + +/** Set to 1 to get a bunch of spew about disassembling the path string */ +#define VERBOSE_DECOMPOSE 0 + +/** + * Decompose an object path. A path of just "/" is + * represented as an empty vector of strings. + * The path need not be nul terminated. + * + * @param data the path data + * @param len the length of the path string + * @param path address to store new object path + * @param path_len length of stored path + */ +dbus_bool_t +_dbus_decompose_path (const char* data, + int len, + char ***path, + int *path_len) +{ + char **retval; + int n_components; + int i, j, comp; + + _dbus_assert (data != NULL); + _dbus_assert (path != NULL); + +#if VERBOSE_DECOMPOSE + _dbus_verbose ("Decomposing path \"%s\"\n", + data); +#endif + + n_components = 0; + if (len > 1) /* if path is not just "/" */ + { + i = 0; + while (i < len) + { + _dbus_assert (data[i] != '\0'); + if (data[i] == '/') + n_components += 1; + ++i; + } + } + + retval = dbus_new0 (char*, n_components + 1); + + if (retval == NULL) + return FALSE; + + comp = 0; + if (n_components == 0) + i = 1; + else + i = 0; + while (comp < n_components) + { + _dbus_assert (i < len); + + if (data[i] == '/') + ++i; + j = i; + + while (j < len && data[j] != '/') + ++j; + + /* Now [i, j) is the path component */ + _dbus_assert (i < j); + _dbus_assert (data[i] != '/'); + _dbus_assert (j == len || data[j] == '/'); + +#if VERBOSE_DECOMPOSE + _dbus_verbose (" (component in [%d,%d))\n", + i, j); +#endif + + retval[comp] = _dbus_memdup (&data[i], j - i + 1); + if (retval[comp] == NULL) + { + dbus_free_string_array (retval); + return FALSE; + } + retval[comp][j-i] = '\0'; +#if VERBOSE_DECOMPOSE + _dbus_verbose (" (component %d = \"%s\")\n", + comp, retval[comp]); +#endif + + ++comp; + i = j; + } + _dbus_assert (i == len); + + *path = retval; + if (path_len) + *path_len = n_components; + + return TRUE; +} + +/** @} */ + +static char* +flatten_path (const char **path) +{ + DBusString str; + char *s; + + if (!_dbus_string_init (&str)) + return NULL; + + if (path[0] == NULL) + { + if (!_dbus_string_append_byte (&str, '/')) + goto nomem; + } + else + { + int i; + + i = 0; + while (path[i]) + { + if (!_dbus_string_append_byte (&str, '/')) + goto nomem; + + if (!_dbus_string_append (&str, path[i])) + goto nomem; + + ++i; + } + } + + if (!_dbus_string_steal_data (&str, &s)) + goto nomem; + + _dbus_string_free (&str); + + return s; + + nomem: + _dbus_string_free (&str); + return NULL; +} + + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +#include "dbus-test.h" +#include <stdio.h> + +typedef enum +{ + STR_EQUAL, + STR_PREFIX, + STR_DIFFERENT +} StrComparison; + +/* Returns TRUE if container is a parent of child + */ +static StrComparison +path_contains (const char **container, + const char **child) +{ + int i; + + i = 0; + while (child[i] != NULL) + { + int v; + + if (container[i] == NULL) + return STR_PREFIX; /* container ran out, child continues; + * thus the container is a parent of the + * child. + */ + + _dbus_assert (container[i] != NULL); + _dbus_assert (child[i] != NULL); + + v = strcmp (container[i], child[i]); + + if (v != 0) + return STR_DIFFERENT; /* they overlap until here and then are different, + * not overlapping + */ + + ++i; + } + + /* Child ran out; if container also did, they are equal; + * otherwise, the child is a parent of the container. + */ + if (container[i] == NULL) + return STR_EQUAL; + else + return STR_DIFFERENT; +} + +#if 0 +static void +spew_subtree_recurse (DBusObjectSubtree *subtree, + int indent) +{ + int i; + + i = 0; + while (i < indent) + { + _dbus_verbose (" "); + ++i; + } + + _dbus_verbose ("%s (%d children)\n", + subtree->name, subtree->n_subtrees); + + i = 0; + while (i < subtree->n_subtrees) + { + spew_subtree_recurse (subtree->subtrees[i], indent + 2); + + ++i; + } +} + +static void +spew_tree (DBusObjectTree *tree) +{ + spew_subtree_recurse (tree->root, 0); +} +#endif + +/** + * Callback data used in tests + */ +typedef struct +{ + const char **path; /**< Path */ + dbus_bool_t handler_fallback; /**< true if the handler may be called as fallback */ + dbus_bool_t message_handled; /**< Gets set to true if message handler called */ + dbus_bool_t handler_unregistered; /**< gets set to true if handler is unregistered */ +} TreeTestData; + + +static void +test_unregister_function (DBusConnection *connection, + void *user_data) +{ + TreeTestData *ttd = user_data; + + ttd->handler_unregistered = TRUE; +} + +static DBusHandlerResult +test_message_function (DBusConnection *connection, + DBusMessage *message, + void *user_data) +{ + TreeTestData *ttd = user_data; + + ttd->message_handled = TRUE; + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static dbus_bool_t +do_register (DBusObjectTree *tree, + const char **path, + dbus_bool_t fallback, + int i, + TreeTestData *tree_test_data) +{ + DBusObjectPathVTable vtable = { test_unregister_function, + test_message_function, NULL }; + + tree_test_data[i].message_handled = FALSE; + tree_test_data[i].handler_unregistered = FALSE; + tree_test_data[i].handler_fallback = fallback; + tree_test_data[i].path = path; + + if (!_dbus_object_tree_register (tree, fallback, path, + &vtable, + &tree_test_data[i], + NULL)) + return FALSE; + + _dbus_assert (_dbus_object_tree_get_user_data_unlocked (tree, path) == + &tree_test_data[i]); + + return TRUE; +} + +static dbus_bool_t +do_test_dispatch (DBusObjectTree *tree, + const char **path, + int i, + TreeTestData *tree_test_data, + int n_test_data) +{ + DBusMessage *message; + int j; + DBusHandlerResult result; + char *flat; + + message = NULL; + + flat = flatten_path (path); + if (flat == NULL) + goto oom; + + message = dbus_message_new_method_call (NULL, + flat, + "org.freedesktop.TestInterface", + "Foo"); + dbus_free (flat); + if (message == NULL) + goto oom; + + j = 0; + while (j < n_test_data) + { + tree_test_data[j].message_handled = FALSE; + ++j; + } + + result = _dbus_object_tree_dispatch_and_unlock (tree, message, NULL); + if (result == DBUS_HANDLER_RESULT_NEED_MEMORY) + goto oom; + + _dbus_assert (tree_test_data[i].message_handled); + + j = 0; + while (j < n_test_data) + { + if (tree_test_data[j].message_handled) + { + if (tree_test_data[j].handler_fallback) + _dbus_assert (path_contains (tree_test_data[j].path, + path) != STR_DIFFERENT); + else + _dbus_assert (path_contains (tree_test_data[j].path, path) == STR_EQUAL); + } + else + { + if (tree_test_data[j].handler_fallback) + _dbus_assert (path_contains (tree_test_data[j].path, + path) == STR_DIFFERENT); + else + _dbus_assert (path_contains (tree_test_data[j].path, path) != STR_EQUAL); + } + + ++j; + } + + dbus_message_unref (message); + + return TRUE; + + oom: + if (message) + dbus_message_unref (message); + return FALSE; +} + +typedef struct +{ + const char *path; + const char *result[20]; +} DecomposePathTest; + +static DecomposePathTest decompose_tests[] = { + { "/foo", { "foo", NULL } }, + { "/foo/bar", { "foo", "bar", NULL } }, + { "/", { NULL } }, + { "/a/b", { "a", "b", NULL } }, + { "/a/b/c", { "a", "b", "c", NULL } }, + { "/a/b/c/d", { "a", "b", "c", "d", NULL } }, + { "/foo/bar/q", { "foo", "bar", "q", NULL } }, + { "/foo/bar/this/is/longer", { "foo", "bar", "this", "is", "longer", NULL } } +}; + +/* Return TRUE on success, FALSE on OOM, die with an assertion failure + * on failure. */ +static dbus_bool_t +run_decompose_tests (void) +{ + int i; + + i = 0; + while (i < _DBUS_N_ELEMENTS (decompose_tests)) + { + char **result; + int result_len; + int expected_len; + + if (!_dbus_decompose_path (decompose_tests[i].path, + strlen (decompose_tests[i].path), + &result, &result_len)) + return FALSE; + + expected_len = _dbus_string_array_length (decompose_tests[i].result); + + if (result_len != (int) _dbus_string_array_length ((const char**)result) || + expected_len != result_len || + path_contains (decompose_tests[i].result, + (const char**) result) != STR_EQUAL) + { + int real_len = _dbus_string_array_length ((const char**)result); + _dbus_warn ("Expected decompose of %s to have len %d, returned %d, appears to have %d", + decompose_tests[i].path, expected_len, result_len, + real_len); + _dbus_warn ("Decompose resulted in elements: { "); + i = 0; + while (i < real_len) + { + _dbus_warn ("\"%s\"%s", result[i], + (i + 1) == real_len ? "" : ", "); + ++i; + } + _dbus_warn ("}"); + _dbus_test_fatal ("path decompose failed"); + } + + dbus_free_string_array (result); + + ++i; + } + + return TRUE; +} + +static DBusObjectSubtree* +find_subtree_registered_or_unregistered (DBusObjectTree *tree, + const char **path) +{ +#if VERBOSE_FIND + _dbus_verbose ("Looking for exact subtree, registered or unregistered\n"); +#endif + + return find_subtree_recurse (tree->root, path, FALSE, NULL, NULL); +} + +/* Returns TRUE if the right thing happens, but the right thing might + * be OOM. */ +static dbus_bool_t +object_tree_test_iteration (void *data, + dbus_bool_t have_memory) +{ + const char *path0[] = { NULL }; + const char *path1[] = { "foo", NULL }; + const char *path2[] = { "foo", "bar", NULL }; + const char *path3[] = { "foo", "bar", "baz", NULL }; + const char *path4[] = { "foo", "bar", "boo", NULL }; + const char *path5[] = { "blah", NULL }; + const char *path6[] = { "blah", "boof", NULL }; + const char *path7[] = { "blah", "boof", "this", "is", "really", "long", NULL }; + const char *path8[] = { "childless", NULL }; + const char *path9[] = { "blah", "a", NULL }; + const char *path10[] = { "blah", "b", NULL }; + const char *path11[] = { "blah", "c", NULL }; + const char *path12[] = { "blah", "a", "d", NULL }; + const char *path13[] = { "blah", "b", "d", NULL }; + const char *path14[] = { "blah", "c", "d", NULL }; + DBusObjectPathVTable test_vtable = { NULL, test_message_function, NULL }; + DBusObjectTree *tree; + TreeTestData tree_test_data[9]; + int i; + dbus_bool_t exact_match; + + if (!run_decompose_tests ()) + return TRUE; /* OOM is OK */ + + tree = NULL; + + tree = _dbus_object_tree_new (NULL); + if (tree == NULL) + goto out; + + if (!do_register (tree, path0, TRUE, 0, tree_test_data)) + goto out; + + _dbus_assert (find_subtree (tree, path0, NULL)); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree (tree, path4, NULL)); + _dbus_assert (!find_subtree (tree, path5, NULL)); + _dbus_assert (!find_subtree (tree, path6, NULL)); + _dbus_assert (!find_subtree (tree, path7, NULL)); + _dbus_assert (!find_subtree (tree, path8, NULL)); + + _dbus_assert (find_handler (tree, path0, &exact_match) && exact_match); + _dbus_assert (find_handler (tree, path1, &exact_match) == tree->root && !exact_match); + _dbus_assert (find_handler (tree, path2, &exact_match) == tree->root && !exact_match); + _dbus_assert (find_handler (tree, path3, &exact_match) == tree->root && !exact_match); + _dbus_assert (find_handler (tree, path4, &exact_match) == tree->root && !exact_match); + _dbus_assert (find_handler (tree, path5, &exact_match) == tree->root && !exact_match); + _dbus_assert (find_handler (tree, path6, &exact_match) == tree->root && !exact_match); + _dbus_assert (find_handler (tree, path7, &exact_match) == tree->root && !exact_match); + _dbus_assert (find_handler (tree, path8, &exact_match) == tree->root && !exact_match); + + if (!do_register (tree, path1, TRUE, 1, tree_test_data)) + goto out; + + _dbus_assert (find_subtree (tree, path0, NULL)); + _dbus_assert (find_subtree (tree, path1, NULL)); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree (tree, path4, NULL)); + _dbus_assert (!find_subtree (tree, path5, NULL)); + _dbus_assert (!find_subtree (tree, path6, NULL)); + _dbus_assert (!find_subtree (tree, path7, NULL)); + _dbus_assert (!find_subtree (tree, path8, NULL)); + + _dbus_assert (find_handler (tree, path0, &exact_match) && exact_match); + _dbus_assert (find_handler (tree, path1, &exact_match) && exact_match); + _dbus_assert (find_handler (tree, path2, &exact_match) && !exact_match); + _dbus_assert (find_handler (tree, path3, &exact_match) && !exact_match); + _dbus_assert (find_handler (tree, path4, &exact_match) && !exact_match); + _dbus_assert (find_handler (tree, path5, &exact_match) == tree->root && !exact_match); + _dbus_assert (find_handler (tree, path6, &exact_match) == tree->root && !exact_match); + _dbus_assert (find_handler (tree, path7, &exact_match) == tree->root && !exact_match); + _dbus_assert (find_handler (tree, path8, &exact_match) == tree->root && !exact_match); + + if (!do_register (tree, path2, TRUE, 2, tree_test_data)) + goto out; + + _dbus_assert (find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree (tree, path4, NULL)); + _dbus_assert (!find_subtree (tree, path5, NULL)); + _dbus_assert (!find_subtree (tree, path6, NULL)); + _dbus_assert (!find_subtree (tree, path7, NULL)); + _dbus_assert (!find_subtree (tree, path8, NULL)); + + if (!do_register (tree, path3, TRUE, 3, tree_test_data)) + goto out; + + _dbus_assert (find_subtree (tree, path0, NULL)); + _dbus_assert (find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree (tree, path2, NULL)); + _dbus_assert (find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree (tree, path4, NULL)); + _dbus_assert (!find_subtree (tree, path5, NULL)); + _dbus_assert (!find_subtree (tree, path6, NULL)); + _dbus_assert (!find_subtree (tree, path7, NULL)); + _dbus_assert (!find_subtree (tree, path8, NULL)); + + if (!do_register (tree, path4, TRUE, 4, tree_test_data)) + goto out; + + _dbus_assert (find_subtree (tree, path0, NULL)); + _dbus_assert (find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree (tree, path2, NULL)); + _dbus_assert (find_subtree (tree, path3, NULL)); + _dbus_assert (find_subtree (tree, path4, NULL)); + _dbus_assert (!find_subtree (tree, path5, NULL)); + _dbus_assert (!find_subtree (tree, path6, NULL)); + _dbus_assert (!find_subtree (tree, path7, NULL)); + _dbus_assert (!find_subtree (tree, path8, NULL)); + + if (!do_register (tree, path5, TRUE, 5, tree_test_data)) + goto out; + + _dbus_assert (find_subtree (tree, path0, NULL)); + _dbus_assert (find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree (tree, path2, NULL)); + _dbus_assert (find_subtree (tree, path3, NULL)); + _dbus_assert (find_subtree (tree, path4, NULL)); + _dbus_assert (find_subtree (tree, path5, NULL)); + _dbus_assert (!find_subtree (tree, path6, NULL)); + _dbus_assert (!find_subtree (tree, path7, NULL)); + _dbus_assert (!find_subtree (tree, path8, NULL)); + + _dbus_assert (find_handler (tree, path0, &exact_match) == tree->root && exact_match); + _dbus_assert (find_handler (tree, path1, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path2, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path3, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path4, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path5, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path6, &exact_match) != tree->root && !exact_match); + _dbus_assert (find_handler (tree, path7, &exact_match) != tree->root && !exact_match); + _dbus_assert (find_handler (tree, path8, &exact_match) == tree->root && !exact_match); + + if (!do_register (tree, path6, TRUE, 6, tree_test_data)) + goto out; + + _dbus_assert (find_subtree (tree, path0, NULL)); + _dbus_assert (find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree (tree, path2, NULL)); + _dbus_assert (find_subtree (tree, path3, NULL)); + _dbus_assert (find_subtree (tree, path4, NULL)); + _dbus_assert (find_subtree (tree, path5, NULL)); + _dbus_assert (find_subtree (tree, path6, NULL)); + _dbus_assert (!find_subtree (tree, path7, NULL)); + _dbus_assert (!find_subtree (tree, path8, NULL)); + + if (!do_register (tree, path7, TRUE, 7, tree_test_data)) + goto out; + + _dbus_assert (find_subtree (tree, path0, NULL)); + _dbus_assert (find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree (tree, path2, NULL)); + _dbus_assert (find_subtree (tree, path3, NULL)); + _dbus_assert (find_subtree (tree, path4, NULL)); + _dbus_assert (find_subtree (tree, path5, NULL)); + _dbus_assert (find_subtree (tree, path6, NULL)); + _dbus_assert (find_subtree (tree, path7, NULL)); + _dbus_assert (!find_subtree (tree, path8, NULL)); + + if (!do_register (tree, path8, TRUE, 8, tree_test_data)) + goto out; + + _dbus_assert (find_subtree (tree, path0, NULL)); + _dbus_assert (find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree (tree, path2, NULL)); + _dbus_assert (find_subtree (tree, path3, NULL)); + _dbus_assert (find_subtree (tree, path4, NULL)); + _dbus_assert (find_subtree (tree, path5, NULL)); + _dbus_assert (find_subtree (tree, path6, NULL)); + _dbus_assert (find_subtree (tree, path7, NULL)); + _dbus_assert (find_subtree (tree, path8, NULL)); + + _dbus_assert (find_handler (tree, path0, &exact_match) == tree->root && exact_match); + _dbus_assert (find_handler (tree, path1, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path2, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path3, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path4, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path5, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path6, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path7, &exact_match) != tree->root && exact_match); + _dbus_assert (find_handler (tree, path8, &exact_match) != tree->root && exact_match); + + /* test the list_registered function */ + + { + const char *root[] = { NULL }; + char **child_entries; + int nb; + + _dbus_object_tree_list_registered_unlocked (tree, path1, &child_entries); + if (child_entries != NULL) + { + nb = _dbus_string_array_length ((const char**)child_entries); + _dbus_assert (nb == 1); + dbus_free_string_array (child_entries); + } + + _dbus_object_tree_list_registered_unlocked (tree, path2, &child_entries); + if (child_entries != NULL) + { + nb = _dbus_string_array_length ((const char**)child_entries); + _dbus_assert (nb == 2); + dbus_free_string_array (child_entries); + } + + _dbus_object_tree_list_registered_unlocked (tree, path8, &child_entries); + if (child_entries != NULL) + { + nb = _dbus_string_array_length ((const char**)child_entries); + _dbus_assert (nb == 0); + dbus_free_string_array (child_entries); + } + + _dbus_object_tree_list_registered_unlocked (tree, root, &child_entries); + if (child_entries != NULL) + { + nb = _dbus_string_array_length ((const char**)child_entries); + _dbus_assert (nb == 3); + dbus_free_string_array (child_entries); + } + } + + /* Check that destroying tree calls unregister funcs */ + _dbus_object_tree_unref (tree); + + i = 0; + while (i < (int) _DBUS_N_ELEMENTS (tree_test_data)) + { + _dbus_assert (tree_test_data[i].handler_unregistered); + _dbus_assert (!tree_test_data[i].message_handled); + ++i; + } + + /* Now start again and try the individual unregister function */ + tree = _dbus_object_tree_new (NULL); + if (tree == NULL) + goto out; + + if (!do_register (tree, path0, TRUE, 0, tree_test_data)) + goto out; + if (!do_register (tree, path1, TRUE, 1, tree_test_data)) + goto out; + if (!do_register (tree, path2, TRUE, 2, tree_test_data)) + goto out; + if (!do_register (tree, path3, TRUE, 3, tree_test_data)) + goto out; + if (!do_register (tree, path4, TRUE, 4, tree_test_data)) + goto out; + if (!do_register (tree, path5, TRUE, 5, tree_test_data)) + goto out; + if (!do_register (tree, path6, TRUE, 6, tree_test_data)) + goto out; + if (!do_register (tree, path7, TRUE, 7, tree_test_data)) + goto out; + if (!do_register (tree, path8, TRUE, 8, tree_test_data)) + goto out; + + _dbus_object_tree_unregister_and_unlock (tree, path0); + _dbus_assert (_dbus_object_tree_get_user_data_unlocked (tree, path0) == NULL); + + _dbus_assert (!find_subtree (tree, path0, NULL)); + _dbus_assert (find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree (tree, path2, NULL)); + _dbus_assert (find_subtree (tree, path3, NULL)); + _dbus_assert (find_subtree (tree, path4, NULL)); + _dbus_assert (find_subtree (tree, path5, NULL)); + _dbus_assert (find_subtree (tree, path6, NULL)); + _dbus_assert (find_subtree (tree, path7, NULL)); + _dbus_assert (find_subtree (tree, path8, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path1); + _dbus_assert (_dbus_object_tree_get_user_data_unlocked (tree, path1) == NULL); + + _dbus_assert (!find_subtree (tree, path0, NULL)); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree (tree, path2, NULL)); + _dbus_assert (find_subtree (tree, path3, NULL)); + _dbus_assert (find_subtree (tree, path4, NULL)); + _dbus_assert (find_subtree (tree, path5, NULL)); + _dbus_assert (find_subtree (tree, path6, NULL)); + _dbus_assert (find_subtree (tree, path7, NULL)); + _dbus_assert (find_subtree (tree, path8, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path2); + _dbus_assert (_dbus_object_tree_get_user_data_unlocked (tree, path2) == NULL); + + _dbus_assert (!find_subtree (tree, path0, NULL)); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (find_subtree (tree, path3, NULL)); + _dbus_assert (find_subtree (tree, path4, NULL)); + _dbus_assert (find_subtree (tree, path5, NULL)); + _dbus_assert (find_subtree (tree, path6, NULL)); + _dbus_assert (find_subtree (tree, path7, NULL)); + _dbus_assert (find_subtree (tree, path8, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path3); + _dbus_assert (_dbus_object_tree_get_user_data_unlocked (tree, path3) == NULL); + + _dbus_assert (!find_subtree (tree, path0, NULL)); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (find_subtree (tree, path4, NULL)); + _dbus_assert (find_subtree (tree, path5, NULL)); + _dbus_assert (find_subtree (tree, path6, NULL)); + _dbus_assert (find_subtree (tree, path7, NULL)); + _dbus_assert (find_subtree (tree, path8, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path4); + _dbus_assert (_dbus_object_tree_get_user_data_unlocked (tree, path4) == NULL); + + _dbus_assert (!find_subtree (tree, path0, NULL)); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree (tree, path4, NULL)); + _dbus_assert (find_subtree (tree, path5, NULL)); + _dbus_assert (find_subtree (tree, path6, NULL)); + _dbus_assert (find_subtree (tree, path7, NULL)); + _dbus_assert (find_subtree (tree, path8, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path5); + _dbus_assert (_dbus_object_tree_get_user_data_unlocked (tree, path5) == NULL); + + _dbus_assert (!find_subtree (tree, path0, NULL)); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree (tree, path4, NULL)); + _dbus_assert (!find_subtree (tree, path5, NULL)); + _dbus_assert (find_subtree (tree, path6, NULL)); + _dbus_assert (find_subtree (tree, path7, NULL)); + _dbus_assert (find_subtree (tree, path8, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path6); + _dbus_assert (_dbus_object_tree_get_user_data_unlocked (tree, path6) == NULL); + + _dbus_assert (!find_subtree (tree, path0, NULL)); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree (tree, path4, NULL)); + _dbus_assert (!find_subtree (tree, path5, NULL)); + _dbus_assert (!find_subtree (tree, path6, NULL)); + _dbus_assert (find_subtree (tree, path7, NULL)); + _dbus_assert (find_subtree (tree, path8, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path7); + _dbus_assert (_dbus_object_tree_get_user_data_unlocked (tree, path7) == NULL); + + _dbus_assert (!find_subtree (tree, path0, NULL)); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree (tree, path4, NULL)); + _dbus_assert (!find_subtree (tree, path5, NULL)); + _dbus_assert (!find_subtree (tree, path6, NULL)); + _dbus_assert (!find_subtree (tree, path7, NULL)); + _dbus_assert (find_subtree (tree, path8, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path8); + _dbus_assert (_dbus_object_tree_get_user_data_unlocked (tree, path8) == NULL); + + _dbus_assert (!find_subtree (tree, path0, NULL)); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree (tree, path4, NULL)); + _dbus_assert (!find_subtree (tree, path5, NULL)); + _dbus_assert (!find_subtree (tree, path6, NULL)); + _dbus_assert (!find_subtree (tree, path7, NULL)); + _dbus_assert (!find_subtree (tree, path8, NULL)); + + i = 0; + while (i < (int) _DBUS_N_ELEMENTS (tree_test_data)) + { + _dbus_assert (tree_test_data[i].handler_unregistered); + _dbus_assert (!tree_test_data[i].message_handled); + ++i; + } + + /* Test removal of newly-childless unregistered nodes */ + if (!do_register (tree, path2, TRUE, 2, tree_test_data)) + goto out; + + _dbus_object_tree_unregister_and_unlock (tree, path2); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path2)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path1)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path0)); + + /* Test that unregistered parents cannot be freed out from under their + children */ + if (!do_register (tree, path2, TRUE, 2, tree_test_data)) + goto out; + + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path1)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path0)); + +#if 0 + /* This triggers the "Attempted to unregister path ..." warning message */ + _dbus_object_tree_unregister_and_unlock (tree, path1); +#endif + _dbus_assert (find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path1)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path0)); + + _dbus_object_tree_unregister_and_unlock (tree, path2); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path2)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path1)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path0)); + + /* Test that registered parents cannot be freed out from under their + children, and that if they are unregistered before their children, they + are still freed when their children are unregistered */ + if (!do_register (tree, path1, TRUE, 1, tree_test_data)) + goto out; + if (!do_register (tree, path2, TRUE, 2, tree_test_data)) + goto out; + + _dbus_assert (find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree (tree, path2, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path1); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (find_subtree (tree, path2, NULL)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path1)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path0)); + + _dbus_object_tree_unregister_and_unlock (tree, path2); + _dbus_assert (!find_subtree (tree, path1, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path1)); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path2)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path0)); + + /* Test with NULL unregister_function and user_data */ + if (!_dbus_object_tree_register (tree, TRUE, path2, + &test_vtable, + NULL, + NULL)) + goto out; + + _dbus_assert (_dbus_object_tree_get_user_data_unlocked (tree, path2) == NULL); + _dbus_object_tree_unregister_and_unlock (tree, path2); + _dbus_assert (!find_subtree (tree, path2, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path2)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path1)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path0)); + + /* Test freeing a long path */ + if (!do_register (tree, path3, TRUE, 3, tree_test_data)) + goto out; + + _dbus_object_tree_unregister_and_unlock (tree, path3); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path3)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path2)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path1)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path0)); + + /* Test freeing multiple children from the same path */ + if (!do_register (tree, path3, TRUE, 3, tree_test_data)) + goto out; + if (!do_register (tree, path4, TRUE, 4, tree_test_data)) + goto out; + + _dbus_assert (find_subtree (tree, path3, NULL)); + _dbus_assert (find_subtree (tree, path4, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path3); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path3)); + _dbus_assert (find_subtree (tree, path4, NULL)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path4)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path2)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path1)); + + _dbus_object_tree_unregister_and_unlock (tree, path4); + _dbus_assert (!find_subtree (tree, path4, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path4)); + _dbus_assert (!find_subtree (tree, path3, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path3)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path2)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path1)); + + /* Test subtree removal */ + if (!_dbus_object_tree_register (tree, TRUE, path12, + &test_vtable, + NULL, + NULL)) + goto out; + + _dbus_assert (find_subtree (tree, path12, NULL)); + + if (!_dbus_object_tree_register (tree, TRUE, path13, + &test_vtable, + NULL, + NULL)) + goto out; + + _dbus_assert (find_subtree (tree, path13, NULL)); + + if (!_dbus_object_tree_register (tree, TRUE, path14, + &test_vtable, + NULL, + NULL)) + goto out; + + _dbus_assert (find_subtree (tree, path14, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path12); + + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path12)); + _dbus_assert (find_subtree (tree, path13, NULL)); + _dbus_assert (find_subtree (tree, path14, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path9)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path5)); + + if (!_dbus_object_tree_register (tree, TRUE, path12, + &test_vtable, + NULL, + NULL)) + goto out; + + _dbus_assert (find_subtree (tree, path12, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path13); + + _dbus_assert (find_subtree (tree, path12, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path13)); + _dbus_assert (find_subtree (tree, path14, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path10)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path5)); + + if (!_dbus_object_tree_register (tree, TRUE, path13, + &test_vtable, + NULL, + NULL)) + goto out; + + _dbus_assert (find_subtree (tree, path13, NULL)); + + _dbus_object_tree_unregister_and_unlock (tree, path14); + + _dbus_assert (find_subtree (tree, path12, NULL)); + _dbus_assert (find_subtree (tree, path13, NULL)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path14)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path11)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path5)); + + _dbus_object_tree_unregister_and_unlock (tree, path12); + + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path12)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path9)); + _dbus_assert (find_subtree_registered_or_unregistered (tree, path5)); + + _dbus_object_tree_unregister_and_unlock (tree, path13); + + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path13)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path10)); + _dbus_assert (!find_subtree_registered_or_unregistered (tree, path5)); + +#if 0 + /* Test attempting to unregister non-existent paths. These trigger + "Attempted to unregister path ..." warning messages */ + _dbus_object_tree_unregister_and_unlock (tree, path0); + _dbus_object_tree_unregister_and_unlock (tree, path1); + _dbus_object_tree_unregister_and_unlock (tree, path2); + _dbus_object_tree_unregister_and_unlock (tree, path3); + _dbus_object_tree_unregister_and_unlock (tree, path4); +#endif + + /* Register it all again, and test dispatch */ + + if (!do_register (tree, path0, TRUE, 0, tree_test_data)) + goto out; + if (!do_register (tree, path1, FALSE, 1, tree_test_data)) + goto out; + if (!do_register (tree, path2, TRUE, 2, tree_test_data)) + goto out; + if (!do_register (tree, path3, TRUE, 3, tree_test_data)) + goto out; + if (!do_register (tree, path4, TRUE, 4, tree_test_data)) + goto out; + if (!do_register (tree, path5, TRUE, 5, tree_test_data)) + goto out; + if (!do_register (tree, path6, FALSE, 6, tree_test_data)) + goto out; + if (!do_register (tree, path7, TRUE, 7, tree_test_data)) + goto out; + if (!do_register (tree, path8, TRUE, 8, tree_test_data)) + goto out; + +#if 0 + spew_tree (tree); +#endif + + if (!do_test_dispatch (tree, path0, 0, tree_test_data, _DBUS_N_ELEMENTS (tree_test_data))) + goto out; + if (!do_test_dispatch (tree, path1, 1, tree_test_data, _DBUS_N_ELEMENTS (tree_test_data))) + goto out; + if (!do_test_dispatch (tree, path2, 2, tree_test_data, _DBUS_N_ELEMENTS (tree_test_data))) + goto out; + if (!do_test_dispatch (tree, path3, 3, tree_test_data, _DBUS_N_ELEMENTS (tree_test_data))) + goto out; + if (!do_test_dispatch (tree, path4, 4, tree_test_data, _DBUS_N_ELEMENTS (tree_test_data))) + goto out; + if (!do_test_dispatch (tree, path5, 5, tree_test_data, _DBUS_N_ELEMENTS (tree_test_data))) + goto out; + if (!do_test_dispatch (tree, path6, 6, tree_test_data, _DBUS_N_ELEMENTS (tree_test_data))) + goto out; + if (!do_test_dispatch (tree, path7, 7, tree_test_data, _DBUS_N_ELEMENTS (tree_test_data))) + goto out; + if (!do_test_dispatch (tree, path8, 8, tree_test_data, _DBUS_N_ELEMENTS (tree_test_data))) + goto out; + + out: + if (tree) + { + /* test ref */ + _dbus_object_tree_ref (tree); + _dbus_object_tree_unref (tree); + _dbus_object_tree_unref (tree); + } + + return TRUE; +} + +/** + * @ingroup DBusObjectTree + * Unit test for DBusObjectTree + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_object_tree_test (const char *test_data_dir _DBUS_GNUC_UNUSED) +{ + return _dbus_test_oom_handling ("object tree", + object_tree_test_iteration, + NULL); +} + +#endif /* !DOXYGEN_SHOULD_SKIP_THIS */ + +#endif /* DBUS_ENABLE_EMBEDDED_TESTS */ diff --git a/src/3rdparty/libdbus/dbus/dbus-object-tree.h b/src/3rdparty/libdbus/dbus/dbus-object-tree.h new file mode 100644 index 00000000..245f9336 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-object-tree.h @@ -0,0 +1,65 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-object-tree.h DBusObjectTree (internals of DBusConnection) + * + * Copyright (C) 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_OBJECT_TREE_H +#define DBUS_OBJECT_TREE_H + +#include <dbus/dbus-connection.h> + +DBUS_BEGIN_DECLS + +typedef struct DBusObjectTree DBusObjectTree; + +DBusObjectTree* _dbus_object_tree_new (DBusConnection *connection); +DBusObjectTree* _dbus_object_tree_ref (DBusObjectTree *tree); +void _dbus_object_tree_unref (DBusObjectTree *tree); + +dbus_bool_t _dbus_object_tree_register (DBusObjectTree *tree, + dbus_bool_t fallback, + const char **path, + const DBusObjectPathVTable *vtable, + void *user_data, + DBusError *error); +void _dbus_object_tree_unregister_and_unlock (DBusObjectTree *tree, + const char **path); +DBusHandlerResult _dbus_object_tree_dispatch_and_unlock (DBusObjectTree *tree, + DBusMessage *message, + dbus_bool_t *found_object); +void* _dbus_object_tree_get_user_data_unlocked (DBusObjectTree *tree, + const char **path); +void _dbus_object_tree_free_all_unlocked (DBusObjectTree *tree); + + +dbus_bool_t _dbus_object_tree_list_registered_and_unlock (DBusObjectTree *tree, + const char **parent_path, + char ***child_entries); + +dbus_bool_t _dbus_decompose_path (const char *data, + int len, + char ***path, + int *path_len); + +DBUS_END_DECLS + +#endif /* DBUS_OBJECT_TREE_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-pending-call-internal.h b/src/3rdparty/libdbus/dbus/dbus-pending-call-internal.h new file mode 100644 index 00000000..a3734937 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-pending-call-internal.h @@ -0,0 +1,73 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-pending-call-internal.h DBusPendingCall internal interfaces + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_PENDING_CALL_INTERNAL_H +#define DBUS_PENDING_CALL_INTERNAL_H + + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-message.h> +#include <dbus/dbus-connection.h> +#include <dbus/dbus-list.h> + +DBUS_BEGIN_DECLS + +dbus_bool_t _dbus_pending_call_is_timeout_added_unlocked (DBusPendingCall *pending); +void _dbus_pending_call_set_timeout_added_unlocked (DBusPendingCall *pending, + dbus_bool_t is_added); +DBusTimeout * _dbus_pending_call_get_timeout_unlocked (DBusPendingCall *pending); +dbus_uint32_t _dbus_pending_call_get_reply_serial_unlocked (DBusPendingCall *pending); +void _dbus_pending_call_set_reply_serial_unlocked (DBusPendingCall *pending, + dbus_uint32_t serial); +DBusConnection * _dbus_pending_call_get_connection_and_lock (DBusPendingCall *pending); +DBusConnection * _dbus_pending_call_get_connection_unlocked (DBusPendingCall *pending); +dbus_bool_t _dbus_pending_call_get_completed_unlocked (DBusPendingCall *pending); + +void _dbus_pending_call_start_completion_unlocked (DBusPendingCall *pending); +void _dbus_pending_call_finish_completion (DBusPendingCall *pending); + +void _dbus_pending_call_set_reply_unlocked (DBusPendingCall *pending, + DBusMessage *message); +void _dbus_pending_call_queue_timeout_error_unlocked (DBusPendingCall *pending, + DBusConnection *connection); +dbus_bool_t _dbus_pending_call_set_timeout_error_unlocked (DBusPendingCall *pending, + DBusMessage *message, + dbus_uint32_t serial); +DBUS_PRIVATE_EXPORT +DBusPendingCall* _dbus_pending_call_new_unlocked (DBusConnection *connection, + int timeout_milliseconds, + DBusTimeoutHandler timeout_handler); +DBUS_PRIVATE_EXPORT +DBusPendingCall* _dbus_pending_call_ref_unlocked (DBusPendingCall *pending); +DBUS_PRIVATE_EXPORT +void _dbus_pending_call_unref_and_unlock (DBusPendingCall *pending); +dbus_bool_t _dbus_pending_call_set_data_unlocked (DBusPendingCall *pending, + dbus_int32_t slot, + void *data, + DBusFreeFunction free_data_func); + + +DBUS_END_DECLS + +#endif /* DBUS_PENDING_CALL_INTERNAL_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-pending-call.c b/src/3rdparty/libdbus/dbus/dbus-pending-call.c new file mode 100644 index 00000000..f0b39d90 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-pending-call.c @@ -0,0 +1,872 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-pending-call.c Object representing a call in progress. + * + * Copyright (C) 2002, 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-connection-internal.h" +#include "dbus-message-internal.h" +#include "dbus-pending-call-internal.h" +#include "dbus-pending-call.h" +#include "dbus-list.h" +#include "dbus-threads.h" +#include "dbus-test.h" + +/** + * @defgroup DBusPendingCallInternals DBusPendingCall implementation details + * @ingroup DBusInternals + * @brief DBusPendingCall private implementation details. + * + * The guts of DBusPendingCall and its methods. + * + * @{ + */ + +/** + * @brief Internals of DBusPendingCall + * + * Opaque object representing a reply message that we're waiting for. + */ + +/** + * shorter and more visible way to write _dbus_connection_lock() + */ +#define CONNECTION_LOCK(connection) _dbus_connection_lock(connection) +/** + * shorter and more visible way to write _dbus_connection_unlock() + */ +#define CONNECTION_UNLOCK(connection) _dbus_connection_unlock(connection) + +/** + * Implementation details of #DBusPendingCall - all fields are private. + */ +struct DBusPendingCall +{ + DBusAtomic refcount; /**< reference count */ + + DBusDataSlotList slot_list; /**< Data stored by allocated integer ID */ + + DBusPendingCallNotifyFunction function; /**< Notifier when reply arrives. */ + + DBusConnection *connection; /**< Connections we're associated with */ + DBusMessage *reply; /**< Reply (after we've received it) */ + DBusTimeout *timeout; /**< Timeout */ + + DBusList *timeout_link; /**< Preallocated timeout response */ + + dbus_uint32_t reply_serial; /**< Expected serial of reply */ + + /** + * TRUE if some thread has taken responsibility for completing this + * pending call: either the pending call has completed, or it is about + * to be completed. Protected by the connection lock. + */ + unsigned int completed : 1; + /** + * TRUE if we have added the timeout. Protected by the connection lock. + */ + unsigned int timeout_added : 1; +}; + +static void +_dbus_pending_call_trace_ref (DBusPendingCall *pending_call, + int old_refcount, + int new_refcount, + const char *why) +{ +#ifdef DBUS_ENABLE_VERBOSE_MODE + static int enabled = -1; + + _dbus_trace_ref ("DBusPendingCall", pending_call, old_refcount, + new_refcount, why, "DBUS_PENDING_CALL_TRACE", &enabled); +#endif +} + +/* protected by _DBUS_LOCK_pending_call_slots */ +static dbus_int32_t notify_user_data_slot = -1; + +/** + * Creates a new pending reply object. + * + * @param connection connection where reply will arrive + * @param timeout_milliseconds length of timeout, -1 (or + * #DBUS_TIMEOUT_USE_DEFAULT) for default, + * #DBUS_TIMEOUT_INFINITE for no timeout + * @param timeout_handler timeout handler, takes pending call as data + * @returns a new #DBusPendingCall or #NULL if no memory. + */ +DBusPendingCall* +_dbus_pending_call_new_unlocked (DBusConnection *connection, + int timeout_milliseconds, + DBusTimeoutHandler timeout_handler) +{ + DBusPendingCall *pending; + DBusTimeout *timeout; + + _dbus_assert (timeout_milliseconds >= 0 || timeout_milliseconds == -1); + + if (timeout_milliseconds == -1) + timeout_milliseconds = _DBUS_DEFAULT_TIMEOUT_VALUE; + + if (!dbus_pending_call_allocate_data_slot (¬ify_user_data_slot)) + return NULL; + + pending = dbus_new0 (DBusPendingCall, 1); + + if (pending == NULL) + { + dbus_pending_call_free_data_slot (¬ify_user_data_slot); + return NULL; + } + + if (timeout_milliseconds != DBUS_TIMEOUT_INFINITE) + { + timeout = _dbus_timeout_new (timeout_milliseconds, + timeout_handler, + pending, NULL); + + if (timeout == NULL) + { + dbus_pending_call_free_data_slot (¬ify_user_data_slot); + dbus_free (pending); + return NULL; + } + + pending->timeout = timeout; + } + else + { + pending->timeout = NULL; + } + + _dbus_atomic_inc (&pending->refcount); + pending->connection = connection; + _dbus_connection_ref_unlocked (pending->connection); + + _dbus_data_slot_list_init (&pending->slot_list); + + _dbus_pending_call_trace_ref (pending, 0, 1, "new_unlocked"); + + return pending; +} + +/** + * Sets the reply of a pending call with the given message, + * or if the message is #NULL, by timing out the pending call. + * + * @param pending the pending call + * @param message the message to complete the call with, or #NULL + * to time out the call + */ +void +_dbus_pending_call_set_reply_unlocked (DBusPendingCall *pending, + DBusMessage *message) +{ + if (message == NULL) + { + message = pending->timeout_link->data; + _dbus_list_clear (&pending->timeout_link); + } + else + dbus_message_ref (message); + + _dbus_verbose (" handing message %p (%s) to pending call serial %u\n", + message, + dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_RETURN ? + "method return" : + dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR ? + "error" : "other type", + pending->reply_serial); + + _dbus_assert (pending->reply == NULL); + _dbus_assert (pending->reply_serial == dbus_message_get_reply_serial (message)); + pending->reply = message; +} + +/** + * Sets the pending call to completed + * + * This method is called with the connection lock held, to protect + * pending->completed. It must be paired with a call to + * _dbus_pending_call_finish_completion() after the connection lock has + * been released. + * + * @param pending the pending call + */ +void +_dbus_pending_call_start_completion_unlocked (DBusPendingCall *pending) +{ + _dbus_assert (!pending->completed); + + pending->completed = TRUE; +} + +/** + * Call the notifier function for the pending call. + * + * This method must be called after the connection lock has been + * released, and must be paired with a call to + * _dbus_pending_call_start_completion_unlocked(). + * + * @param pending the pending call + */ +void +_dbus_pending_call_finish_completion (DBusPendingCall *pending) +{ + _dbus_assert (pending->completed); + + if (pending->function) + { + void *user_data; + user_data = dbus_pending_call_get_data (pending, + notify_user_data_slot); + + (* pending->function) (pending, user_data); + } +} + +/** + * If the pending call hasn't been timed out, add its timeout + * error reply to the connection's incoming message queue. + * + * @param pending the pending call + * @param connection the connection the call was sent to + */ +void +_dbus_pending_call_queue_timeout_error_unlocked (DBusPendingCall *pending, + DBusConnection *connection) +{ + _dbus_assert (connection == pending->connection); + + if (pending->timeout_link) + { + _dbus_connection_queue_synthesized_message_link (connection, + pending->timeout_link); + pending->timeout_link = NULL; + } +} + +/** + * Checks to see if a timeout has been added + * + * @param pending the pending_call + * @returns #TRUE if there is a timeout or #FALSE if not + */ +dbus_bool_t +_dbus_pending_call_is_timeout_added_unlocked (DBusPendingCall *pending) +{ + _dbus_assert (pending != NULL); + + return pending->timeout_added; +} + + +/** + * Sets wether the timeout has been added + * + * @param pending the pending_call + * @param is_added whether or not a timeout is added + */ +void +_dbus_pending_call_set_timeout_added_unlocked (DBusPendingCall *pending, + dbus_bool_t is_added) +{ + _dbus_assert (pending != NULL); + + pending->timeout_added = is_added; +} + + +/** + * Retrives the timeout + * + * @param pending the pending_call + * @returns a timeout object or NULL if call has no timeout + */ +DBusTimeout * +_dbus_pending_call_get_timeout_unlocked (DBusPendingCall *pending) +{ + _dbus_assert (pending != NULL); + + return pending->timeout; +} + +/** + * Gets the reply's serial number + * + * @param pending the pending_call + * @returns a serial number for the reply or 0 + */ +dbus_uint32_t +_dbus_pending_call_get_reply_serial_unlocked (DBusPendingCall *pending) +{ + _dbus_assert (pending != NULL); + + return pending->reply_serial; +} + +/** + * Sets the reply's serial number + * + * @param pending the pending_call + * @param serial the serial number + */ +void +_dbus_pending_call_set_reply_serial_unlocked (DBusPendingCall *pending, + dbus_uint32_t serial) +{ + _dbus_assert (pending != NULL); + _dbus_assert (pending->reply_serial == 0); + + pending->reply_serial = serial; +} + +/** + * Gets the connection associated with this pending call. + * + * @param pending the pending_call + * @returns the connection associated with the pending call + */ +DBusConnection * +_dbus_pending_call_get_connection_and_lock (DBusPendingCall *pending) +{ + _dbus_assert (pending != NULL); + + CONNECTION_LOCK (pending->connection); + return pending->connection; +} + +/** + * Gets the connection associated with this pending call. + * + * @param pending the pending_call + * @returns the connection associated with the pending call + */ +DBusConnection * +_dbus_pending_call_get_connection_unlocked (DBusPendingCall *pending) +{ + _dbus_assert (pending != NULL); + + return pending->connection; +} + +/** + * Sets the reply message associated with the pending call to a timeout error + * + * @param pending the pending_call + * @param message the message we are sending the error reply to + * @param serial serial number for the reply + * @return #FALSE on OOM + */ +dbus_bool_t +_dbus_pending_call_set_timeout_error_unlocked (DBusPendingCall *pending, + DBusMessage *message, + dbus_uint32_t serial) +{ + DBusList *reply_link; + DBusMessage *reply; + + reply = dbus_message_new_error (message, DBUS_ERROR_NO_REPLY, + "Did not receive a reply. Possible causes include: " + "the remote application did not send a reply, " + "the message bus security policy blocked the reply, " + "the reply timeout expired, or " + "the network connection was broken."); + if (reply == NULL) + return FALSE; + + reply_link = _dbus_list_alloc_link (reply); + if (reply_link == NULL) + { + /* it's OK to unref this, nothing that could have attached a callback + * has ever seen it */ + dbus_message_unref (reply); + return FALSE; + } + + pending->timeout_link = reply_link; + + _dbus_pending_call_set_reply_serial_unlocked (pending, serial); + + return TRUE; +} + +/** + * Increments the reference count on a pending call, + * while the lock on its connection is already held. + * + * @param pending the pending call object + * @returns the pending call object + */ +DBusPendingCall * +_dbus_pending_call_ref_unlocked (DBusPendingCall *pending) +{ + dbus_int32_t old_refcount; + + old_refcount = _dbus_atomic_inc (&pending->refcount); + _dbus_pending_call_trace_ref (pending, old_refcount, old_refcount + 1, + "ref_unlocked"); + + return pending; +} + + +static void +_dbus_pending_call_last_unref (DBusPendingCall *pending) +{ + DBusConnection *connection; + + /* If we get here, we should be already detached + * from the connection, or never attached. + */ + _dbus_assert (!pending->timeout_added); + + connection = pending->connection; + + /* this assumes we aren't holding connection lock... */ + _dbus_data_slot_list_free (&pending->slot_list); + + if (pending->timeout != NULL) + _dbus_timeout_unref (pending->timeout); + + if (pending->timeout_link) + { + dbus_message_unref ((DBusMessage *)pending->timeout_link->data); + _dbus_list_free_link (pending->timeout_link); + pending->timeout_link = NULL; + } + + if (pending->reply) + { + dbus_message_unref (pending->reply); + pending->reply = NULL; + } + + dbus_free (pending); + + dbus_pending_call_free_data_slot (¬ify_user_data_slot); + + /* connection lock should not be held. */ + /* Free the connection last to avoid a weird state while + * calling out to application code where the pending exists + * but not the connection. + */ + dbus_connection_unref (connection); +} + +/** + * Decrements the reference count on a pending call, + * freeing it if the count reaches 0. Assumes + * connection lock is already held. + * + * @param pending the pending call object + */ +void +_dbus_pending_call_unref_and_unlock (DBusPendingCall *pending) +{ + dbus_int32_t old_refcount; + + old_refcount = _dbus_atomic_dec (&pending->refcount); + _dbus_assert (old_refcount > 0); + _dbus_pending_call_trace_ref (pending, old_refcount, + old_refcount - 1, "unref_and_unlock"); + + CONNECTION_UNLOCK (pending->connection); + + if (old_refcount == 1) + _dbus_pending_call_last_unref (pending); +} + +/** + * Checks whether the pending call has received a reply + * yet, or not. Assumes connection lock is held. + * + * @param pending the pending call + * @returns #TRUE if a reply has been received + */ +dbus_bool_t +_dbus_pending_call_get_completed_unlocked (DBusPendingCall *pending) +{ + return pending->completed; +} + +static DBusDataSlotAllocator slot_allocator = + _DBUS_DATA_SLOT_ALLOCATOR_INIT (_DBUS_LOCK_NAME (pending_call_slots)); + +/** + * Stores a pointer on a #DBusPendingCall, along + * with an optional function to be used for freeing + * the data when the data is set again, or when + * the pending call is finalized. The slot number + * must have been allocated with dbus_pending_call_allocate_data_slot(). + * + * @param pending the pending_call + * @param slot the slot number + * @param data the data to store + * @param free_data_func finalizer function for the data + * @returns #TRUE if there was enough memory to store the data + */ +dbus_bool_t +_dbus_pending_call_set_data_unlocked (DBusPendingCall *pending, + dbus_int32_t slot, + void *data, + DBusFreeFunction free_data_func) +{ + DBusFreeFunction old_free_func; + void *old_data; + dbus_bool_t retval; + + retval = _dbus_data_slot_list_set (&slot_allocator, + &pending->slot_list, + slot, data, free_data_func, + &old_free_func, &old_data); + + /* Drop locks to call out to app code */ + CONNECTION_UNLOCK (pending->connection); + + if (retval) + { + if (old_free_func) + (* old_free_func) (old_data); + } + + CONNECTION_LOCK (pending->connection); + + return retval; +} + +/** @} */ + +/** + * @defgroup DBusPendingCall DBusPendingCall + * @ingroup DBus + * @brief Pending reply to a method call message + * + * A DBusPendingCall is an object representing an + * expected reply. A #DBusPendingCall can be created + * when you send a message that should have a reply. + * + * @{ + */ + +/** + * @def DBUS_TIMEOUT_INFINITE + * + * An integer constant representing an infinite timeout. This has the + * numeric value 0x7fffffff (the largest 32-bit signed integer). + * + * For source compatibility with D-Bus versions earlier than 1.4.12, use + * 0x7fffffff, or INT32_MAX (assuming your platform has it). + */ + +/** + * @def DBUS_TIMEOUT_USE_DEFAULT + * + * An integer constant representing a request to use the default timeout. + * This has numeric value -1. + * + * For source compatibility with D-Bus versions earlier than 1.4.12, use a + * literal -1. + */ + +/** + * @typedef DBusPendingCall + * + * Opaque data type representing a message pending. + */ + +/** + * Increments the reference count on a pending call. + * + * @param pending the pending call object + * @returns the pending call object + */ +DBusPendingCall * +dbus_pending_call_ref (DBusPendingCall *pending) +{ + dbus_int32_t old_refcount; + + _dbus_return_val_if_fail (pending != NULL, NULL); + + old_refcount = _dbus_atomic_inc (&pending->refcount); + _dbus_pending_call_trace_ref (pending, old_refcount, old_refcount + 1, + "ref"); + + return pending; +} + +/** + * Decrements the reference count on a pending call, + * freeing it if the count reaches 0. + * + * @param pending the pending call object + */ +void +dbus_pending_call_unref (DBusPendingCall *pending) +{ + dbus_int32_t old_refcount; + + _dbus_return_if_fail (pending != NULL); + + old_refcount = _dbus_atomic_dec (&pending->refcount); + _dbus_pending_call_trace_ref (pending, old_refcount, old_refcount - 1, + "unref"); + + if (old_refcount == 1) + _dbus_pending_call_last_unref(pending); +} + +/** + * Sets a notification function to be called when the reply is + * received or the pending call times out. + * + * @param pending the pending call + * @param function notifier function + * @param user_data data to pass to notifier function + * @param free_user_data function to free the user data + * @returns #FALSE if not enough memory + */ +dbus_bool_t +dbus_pending_call_set_notify (DBusPendingCall *pending, + DBusPendingCallNotifyFunction function, + void *user_data, + DBusFreeFunction free_user_data) +{ + dbus_bool_t ret = FALSE; + + _dbus_return_val_if_fail (pending != NULL, FALSE); + + CONNECTION_LOCK (pending->connection); + + /* could invoke application code! */ + if (!_dbus_pending_call_set_data_unlocked (pending, notify_user_data_slot, + user_data, free_user_data)) + goto out; + + pending->function = function; + ret = TRUE; + +out: + CONNECTION_UNLOCK (pending->connection); + + return ret; +} + +/** + * Cancels the pending call, such that any reply or error received + * will just be ignored. Drops the dbus library's internal reference + * to the #DBusPendingCall so will free the call if nobody else is + * holding a reference. However you usually get a reference from + * dbus_connection_send_with_reply() so probably your app owns a ref + * also. + * + * Note that canceling a pending call will <em>not</em> simulate a + * timed-out call; if a call times out, then a timeout error reply is + * received. If you cancel the call, no reply is received unless the + * the reply was already received before you canceled. + * + * @param pending the pending call + */ +void +dbus_pending_call_cancel (DBusPendingCall *pending) +{ + _dbus_return_if_fail (pending != NULL); + + _dbus_connection_remove_pending_call (pending->connection, + pending); +} + +/** + * Checks whether the pending call has received a reply + * yet, or not. + * + * @param pending the pending call + * @returns #TRUE if a reply has been received + */ +dbus_bool_t +dbus_pending_call_get_completed (DBusPendingCall *pending) +{ + dbus_bool_t completed; + + _dbus_return_val_if_fail (pending != NULL, FALSE); + + CONNECTION_LOCK (pending->connection); + completed = pending->completed; + CONNECTION_UNLOCK (pending->connection); + + return completed; +} + +/** + * Gets the reply, or returns #NULL if none has been received + * yet. Ownership of the reply message passes to the caller. This + * function can only be called once per pending call, since the reply + * message is tranferred to the caller. + * + * @param pending the pending call + * @returns the reply message or #NULL. + */ +DBusMessage* +dbus_pending_call_steal_reply (DBusPendingCall *pending) +{ + DBusMessage *message; + + _dbus_return_val_if_fail (pending != NULL, NULL); + _dbus_return_val_if_fail (pending->completed, NULL); + _dbus_return_val_if_fail (pending->reply != NULL, NULL); + + CONNECTION_LOCK (pending->connection); + + message = pending->reply; + pending->reply = NULL; + + CONNECTION_UNLOCK (pending->connection); + + _dbus_message_trace_ref (message, -1, -1, "dbus_pending_call_steal_reply"); + return message; +} + +/** + * Block until the pending call is completed. The blocking is as with + * dbus_connection_send_with_reply_and_block(); it does not enter the + * main loop or process other messages, it simply waits for the reply + * in question. + * + * If the pending call is already completed, this function returns + * immediately. + * + * @todo when you start blocking, the timeout is reset, but it should + * really only use time remaining since the pending call was created. + * This requires storing timestamps instead of intervals in the timeout + * + * @param pending the pending call + */ +void +dbus_pending_call_block (DBusPendingCall *pending) +{ + _dbus_return_if_fail (pending != NULL); + + _dbus_connection_block_pending_call (pending); +} + +/** + * Allocates an integer ID to be used for storing application-specific + * data on any DBusPendingCall. The allocated ID may then be used + * with dbus_pending_call_set_data() and dbus_pending_call_get_data(). + * The passed-in slot must be initialized to -1, and is filled in + * with the slot ID. If the passed-in slot is not -1, it's assumed + * to be already allocated, and its refcount is incremented. + * + * The allocated slot is global, i.e. all DBusPendingCall objects will + * have a slot with the given integer ID reserved. + * + * @param slot_p address of a global variable storing the slot + * @returns #FALSE on failure (no memory) + */ +dbus_bool_t +dbus_pending_call_allocate_data_slot (dbus_int32_t *slot_p) +{ + _dbus_return_val_if_fail (slot_p != NULL, FALSE); + + return _dbus_data_slot_allocator_alloc (&slot_allocator, + slot_p); +} + +/** + * Deallocates a global ID for #DBusPendingCall data slots. + * dbus_pending_call_get_data() and dbus_pending_call_set_data() may + * no longer be used with this slot. Existing data stored on existing + * DBusPendingCall objects will be freed when the #DBusPendingCall is + * finalized, but may not be retrieved (and may only be replaced if + * someone else reallocates the slot). When the refcount on the + * passed-in slot reaches 0, it is set to -1. + * + * @param slot_p address storing the slot to deallocate + */ +void +dbus_pending_call_free_data_slot (dbus_int32_t *slot_p) +{ + _dbus_return_if_fail (slot_p != NULL); + _dbus_return_if_fail (*slot_p >= 0); + + _dbus_data_slot_allocator_free (&slot_allocator, slot_p); +} + +/** + * Stores a pointer on a #DBusPendingCall, along + * with an optional function to be used for freeing + * the data when the data is set again, or when + * the pending call is finalized. The slot number + * must have been allocated with dbus_pending_call_allocate_data_slot(). + * + * @param pending the pending_call + * @param slot the slot number + * @param data the data to store + * @param free_data_func finalizer function for the data + * @returns #TRUE if there was enough memory to store the data + */ +dbus_bool_t +dbus_pending_call_set_data (DBusPendingCall *pending, + dbus_int32_t slot, + void *data, + DBusFreeFunction free_data_func) +{ + dbus_bool_t retval; + + _dbus_return_val_if_fail (pending != NULL, FALSE); + _dbus_return_val_if_fail (slot >= 0, FALSE); + + + CONNECTION_LOCK (pending->connection); + retval = _dbus_pending_call_set_data_unlocked (pending, slot, data, free_data_func); + CONNECTION_UNLOCK (pending->connection); + return retval; +} + +/** + * Retrieves data previously set with dbus_pending_call_set_data(). + * The slot must still be allocated (must not have been freed). + * + * @param pending the pending_call + * @param slot the slot to get data from + * @returns the data, or #NULL if not found + */ +void* +dbus_pending_call_get_data (DBusPendingCall *pending, + dbus_int32_t slot) +{ + void *res; + + _dbus_return_val_if_fail (pending != NULL, NULL); + + CONNECTION_LOCK (pending->connection); + res = _dbus_data_slot_list_get (&slot_allocator, + &pending->slot_list, + slot); + CONNECTION_UNLOCK (pending->connection); + + return res; +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-pending-call.h b/src/3rdparty/libdbus/dbus/dbus-pending-call.h new file mode 100644 index 00000000..3539a1f2 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-pending-call.h @@ -0,0 +1,100 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-pending-call.h Object representing a call in progress. + * + * Copyright (C) 2002, 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_PENDING_CALL_H +#define DBUS_PENDING_CALL_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-connection.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusPendingCall + * @{ + */ + +#define DBUS_TIMEOUT_INFINITE ((int) 0x7fffffff) +#define DBUS_TIMEOUT_USE_DEFAULT (-1) + +DBUS_EXPORT +DBusPendingCall* dbus_pending_call_ref (DBusPendingCall *pending); +DBUS_EXPORT +void dbus_pending_call_unref (DBusPendingCall *pending); +DBUS_EXPORT +dbus_bool_t dbus_pending_call_set_notify (DBusPendingCall *pending, + DBusPendingCallNotifyFunction function, + void *user_data, + DBusFreeFunction free_user_data); +DBUS_EXPORT +void dbus_pending_call_cancel (DBusPendingCall *pending); +DBUS_EXPORT +dbus_bool_t dbus_pending_call_get_completed (DBusPendingCall *pending); +DBUS_EXPORT +DBusMessage* dbus_pending_call_steal_reply (DBusPendingCall *pending); +DBUS_EXPORT +void dbus_pending_call_block (DBusPendingCall *pending); + +DBUS_EXPORT +dbus_bool_t dbus_pending_call_allocate_data_slot (dbus_int32_t *slot_p); +DBUS_EXPORT +void dbus_pending_call_free_data_slot (dbus_int32_t *slot_p); +DBUS_EXPORT +dbus_bool_t dbus_pending_call_set_data (DBusPendingCall *pending, + dbus_int32_t slot, + void *data, + DBusFreeFunction free_data_func); +DBUS_EXPORT +void* dbus_pending_call_get_data (DBusPendingCall *pending, + dbus_int32_t slot); + +/** + * Clear a variable or struct member that contains a #DBusPendingCall. + * If it does not contain #NULL, the pending call that was previously + * there is unreferenced with dbus_pending_call_unref(). + * + * This is very similar to dbus_clear_connection(): see that function + * for more details. + * + * @param pointer_to_pending_call A pointer to a variable or struct member. + * pointer_to_pending_call must not be #NULL, but *pointer_to_pending_call + * may be #NULL. + */ +static inline void +dbus_clear_pending_call (DBusPendingCall **pointer_to_pending_call) +{ + _dbus_clear_pointer_impl (DBusPendingCall, pointer_to_pending_call, + dbus_pending_call_unref); +} + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_PENDING_CALL_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-pipe-unix.c b/src/3rdparty/libdbus/dbus/dbus-pipe-unix.c new file mode 100644 index 00000000..314c7293 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-pipe-unix.c @@ -0,0 +1,85 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-pipe-unix.c unix related pipe implementation + * + * Copyright (C) 2002, 2003, 2006 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-protocol.h" +#include "dbus-string.h" +#include "dbus-internals.h" +#include "dbus-pipe.h" +#include "dbus-sysdeps-unix.h" + +#include <errno.h> + +/** + * write data to a pipe. + * + * @param pipe the pipe instance + * @param buffer the buffer to write data from + * @param start the first byte in the buffer to write + * @param len the number of bytes to try to write + * @param error error return + * @returns the number of bytes written or -1 on error + */ +int +_dbus_pipe_write (DBusPipe *pipe, + const DBusString *buffer, + int start, + int len, + DBusError *error) +{ + int written; + + written = _dbus_write (pipe->fd, buffer, start, len); + if (written < 0) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Writing to pipe: %s\n", + _dbus_strerror (errno)); + } + return written; +} + +/** + * close a pipe. + * + * @param pipe the pipe instance + * @param error return location for an error + * @returns -1 if error is set + */ +int +_dbus_pipe_close (DBusPipe *pipe, + DBusError *error) +{ + if (!_dbus_close (pipe->fd, error)) + { + return -1; + } + else + { + _dbus_pipe_invalidate (pipe); + return 0; + } +} diff --git a/src/3rdparty/libdbus/dbus/dbus-pipe-win.c b/src/3rdparty/libdbus/dbus/dbus-pipe-win.c new file mode 100644 index 00000000..5667ad91 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-pipe-win.c @@ -0,0 +1,92 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-pipe-win.c windows related pipe implementation + * + * Copyright (C) 2002, 2003, 2006 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-protocol.h" +#include "dbus-string.h" +#include "dbus-internals.h" +#include "dbus-pipe.h" + +#include <windows.h> +#include <io.h> + +/** + * write data to a pipe. + * + * @param pipe the pipe instance + * @param buffer the buffer to write data from + * @param start the first byte in the buffer to write + * @param len the number of bytes to try to write + * @param error error return + * @returns the number of bytes written or -1 on error + */ +int +_dbus_pipe_write (DBusPipe *pipe, + const DBusString *buffer, + int start, + int len, + DBusError *error) +{ + const char *buffer_c = _dbus_string_get_const_data (buffer); + int written; + + written = _write (pipe->fd, buffer_c + start, len); + + if (written >= 0) + return written; + + dbus_set_error (error, _dbus_error_from_system_errno (), + "Writing to pipe: %s", + _dbus_strerror_from_errno ()); + return -1; +} + +/** + * close a pipe. + * + * @param pipe the pipe instance + * @param error return location for an error + * @returns #FALSE if error is set + */ +int +_dbus_pipe_close (DBusPipe *pipe, + DBusError *error) +{ + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (_close (pipe->fd) != 0) + { + dbus_set_error (error, _dbus_error_from_system_errno (), + "Could not close pipe fd %d: %s", pipe->fd, + _dbus_strerror_from_errno ()); + return -1; + } + else + { + _dbus_pipe_invalidate (pipe); + return 0; + } +} diff --git a/src/3rdparty/libdbus/dbus/dbus-pipe.c b/src/3rdparty/libdbus/dbus/dbus-pipe.c new file mode 100644 index 00000000..f1fb19ff --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-pipe.c @@ -0,0 +1,87 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-pipe.c pipe implementation (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003, 2006 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-pipe.h" + +/* + * init a pipe instance. + * + * @param pipe the pipe + * @param fd the file descriptor to init from + */ +void +_dbus_pipe_init (DBusPipe *pipe, + int fd) +{ + pipe->fd = fd; +} + +/** + * init a pipe with stdout + * + * @param pipe the pipe + */ +void +_dbus_pipe_init_stdout (DBusPipe *pipe) +{ + _dbus_pipe_init (pipe, 1); +} + +/** + * check if a pipe is valid; pipes can be set invalid, similar to + * a -1 file descriptor. + * + * @param pipe the pipe instance + * @returns #FALSE if pipe is not valid + */ +dbus_bool_t +_dbus_pipe_is_valid(DBusPipe *pipe) +{ + return pipe->fd >= 0; +} + +/** + * Check if a pipe is stdout or stderr. + * + * @param pipe the pipe instance + * @returns #TRUE if pipe is one of the standard out/err channels + */ +dbus_bool_t +_dbus_pipe_is_stdout_or_stderr (DBusPipe *pipe) +{ + return pipe->fd == 1 || pipe->fd == 2; +} + +/** + * Initializes a pipe to an invalid value. + * @param pipe the pipe + */ +void +_dbus_pipe_invalidate (DBusPipe *pipe) +{ + pipe->fd = -1; +} diff --git a/src/3rdparty/libdbus/dbus/dbus-pipe.h b/src/3rdparty/libdbus/dbus/dbus-pipe.h new file mode 100644 index 00000000..1e579aa4 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-pipe.h @@ -0,0 +1,66 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sysdeps.h Wrappers around system/libc features (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_PIPE_H +#define DBUS_PIPE_H + +#include <stdint.h> + +#ifdef HAVE_INTTYPES_H +#include <inttypes.h> +#endif + +#include <dbus/dbus-types.h> +#include <dbus/dbus-errors.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-sysdeps.h> + +struct DBusPipe { + int fd; +}; + +DBUS_PRIVATE_EXPORT +void _dbus_pipe_init (DBusPipe *pipe, + int fd); +DBUS_PRIVATE_EXPORT +void _dbus_pipe_init_stdout (DBusPipe *pipe); +DBUS_PRIVATE_EXPORT +int _dbus_pipe_write (DBusPipe *pipe, + const DBusString *buffer, + int start, + int len, + DBusError *error); +DBUS_PRIVATE_EXPORT +int _dbus_pipe_close (DBusPipe *pipe, + DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_pipe_is_valid (DBusPipe *pipe); +DBUS_PRIVATE_EXPORT +void _dbus_pipe_invalidate (DBusPipe *pipe); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_pipe_is_stdout_or_stderr (DBusPipe *pipe); + +#endif diff --git a/src/3rdparty/libdbus/dbus/dbus-protocol.h b/src/3rdparty/libdbus/dbus/dbus-protocol.h new file mode 100644 index 00000000..33e39df3 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-protocol.h @@ -0,0 +1,488 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-protocol.h D-Bus protocol constants + * + * Copyright (C) 2002, 2003 CodeFactory AB + * Copyright (C) 2004, 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_PROTOCOL_H +#define DBUS_PROTOCOL_H + +/* Don't include anything in here from anywhere else. It's + * intended for use by any random library. + */ + +#ifdef __cplusplus +extern "C" { +#if 0 +} /* avoids confusing emacs indentation */ +#endif +#endif + +/* Normally docs are in .c files, but there isn't a .c file for this. */ +/** + * @defgroup DBusProtocol Protocol constants + * @ingroup DBus + * + * @brief Defines constants which are part of the D-Bus protocol + * + * This header is intended for use by any library, not only libdbus. + * + * @{ + */ + + +/* Message byte order */ +#define DBUS_LITTLE_ENDIAN ('l') /**< Code marking LSB-first byte order in the wire protocol. */ +#define DBUS_BIG_ENDIAN ('B') /**< Code marking MSB-first byte order in the wire protocol. */ + +/** Protocol version. */ +#define DBUS_MAJOR_PROTOCOL_VERSION 1 + +/** Type code that is never equal to a legitimate type code */ +#define DBUS_TYPE_INVALID ((int) '\0') +/** #DBUS_TYPE_INVALID as a string literal instead of a int literal */ +#define DBUS_TYPE_INVALID_AS_STRING "\0" + +/* Primitive types */ +/** Type code marking an 8-bit unsigned integer */ +#define DBUS_TYPE_BYTE ((int) 'y') +/** #DBUS_TYPE_BYTE as a string literal instead of a int literal */ +#define DBUS_TYPE_BYTE_AS_STRING "y" +/** Type code marking a boolean */ +#define DBUS_TYPE_BOOLEAN ((int) 'b') +/** #DBUS_TYPE_BOOLEAN as a string literal instead of a int literal */ +#define DBUS_TYPE_BOOLEAN_AS_STRING "b" +/** Type code marking a 16-bit signed integer */ +#define DBUS_TYPE_INT16 ((int) 'n') +/** #DBUS_TYPE_INT16 as a string literal instead of a int literal */ +#define DBUS_TYPE_INT16_AS_STRING "n" +/** Type code marking a 16-bit unsigned integer */ +#define DBUS_TYPE_UINT16 ((int) 'q') +/** #DBUS_TYPE_UINT16 as a string literal instead of a int literal */ +#define DBUS_TYPE_UINT16_AS_STRING "q" +/** Type code marking a 32-bit signed integer */ +#define DBUS_TYPE_INT32 ((int) 'i') +/** #DBUS_TYPE_INT32 as a string literal instead of a int literal */ +#define DBUS_TYPE_INT32_AS_STRING "i" +/** Type code marking a 32-bit unsigned integer */ +#define DBUS_TYPE_UINT32 ((int) 'u') +/** #DBUS_TYPE_UINT32 as a string literal instead of a int literal */ +#define DBUS_TYPE_UINT32_AS_STRING "u" +/** Type code marking a 64-bit signed integer */ +#define DBUS_TYPE_INT64 ((int) 'x') +/** #DBUS_TYPE_INT64 as a string literal instead of a int literal */ +#define DBUS_TYPE_INT64_AS_STRING "x" +/** Type code marking a 64-bit unsigned integer */ +#define DBUS_TYPE_UINT64 ((int) 't') +/** #DBUS_TYPE_UINT64 as a string literal instead of a int literal */ +#define DBUS_TYPE_UINT64_AS_STRING "t" +/** Type code marking an 8-byte double in IEEE 754 format */ +#define DBUS_TYPE_DOUBLE ((int) 'd') +/** #DBUS_TYPE_DOUBLE as a string literal instead of a int literal */ +#define DBUS_TYPE_DOUBLE_AS_STRING "d" +/** Type code marking a UTF-8 encoded, nul-terminated Unicode string */ +#define DBUS_TYPE_STRING ((int) 's') +/** #DBUS_TYPE_STRING as a string literal instead of a int literal */ +#define DBUS_TYPE_STRING_AS_STRING "s" +/** Type code marking a D-Bus object path */ +#define DBUS_TYPE_OBJECT_PATH ((int) 'o') +/** #DBUS_TYPE_OBJECT_PATH as a string literal instead of a int literal */ +#define DBUS_TYPE_OBJECT_PATH_AS_STRING "o" +/** Type code marking a D-Bus type signature */ +#define DBUS_TYPE_SIGNATURE ((int) 'g') +/** #DBUS_TYPE_SIGNATURE as a string literal instead of a int literal */ +#define DBUS_TYPE_SIGNATURE_AS_STRING "g" +/** Type code marking a unix file descriptor */ +#define DBUS_TYPE_UNIX_FD ((int) 'h') +/** #DBUS_TYPE_UNIX_FD as a string literal instead of a int literal */ +#define DBUS_TYPE_UNIX_FD_AS_STRING "h" + +/* Compound types */ +/** Type code marking a D-Bus array type */ +#define DBUS_TYPE_ARRAY ((int) 'a') +/** #DBUS_TYPE_ARRAY as a string literal instead of a int literal */ +#define DBUS_TYPE_ARRAY_AS_STRING "a" +/** Type code marking a D-Bus variant type */ +#define DBUS_TYPE_VARIANT ((int) 'v') +/** #DBUS_TYPE_VARIANT as a string literal instead of a int literal */ +#define DBUS_TYPE_VARIANT_AS_STRING "v" + +/** STRUCT and DICT_ENTRY are sort of special since their codes can't + * appear in a type string, instead + * DBUS_STRUCT_BEGIN_CHAR/DBUS_DICT_ENTRY_BEGIN_CHAR have to appear + */ +/** Type code used to represent a struct; however, this type code does not appear + * in type signatures, instead #DBUS_STRUCT_BEGIN_CHAR and #DBUS_STRUCT_END_CHAR will + * appear in a signature. + */ +#define DBUS_TYPE_STRUCT ((int) 'r') +/** #DBUS_TYPE_STRUCT as a string literal instead of a int literal */ +#define DBUS_TYPE_STRUCT_AS_STRING "r" +/** Type code used to represent a dict entry; however, this type code does not appear + * in type signatures, instead #DBUS_DICT_ENTRY_BEGIN_CHAR and #DBUS_DICT_ENTRY_END_CHAR will + * appear in a signature. + */ +#define DBUS_TYPE_DICT_ENTRY ((int) 'e') +/** #DBUS_TYPE_DICT_ENTRY as a string literal instead of a int literal */ +#define DBUS_TYPE_DICT_ENTRY_AS_STRING "e" + +/** Does not include #DBUS_TYPE_INVALID, #DBUS_STRUCT_BEGIN_CHAR, #DBUS_STRUCT_END_CHAR, + * #DBUS_DICT_ENTRY_BEGIN_CHAR, or #DBUS_DICT_ENTRY_END_CHAR - i.e. it is the number of + * valid types, not the number of distinct characters that may appear in a type signature. + */ +#define DBUS_NUMBER_OF_TYPES (16) + +/* characters other than typecodes that appear in type signatures */ + +/** Code marking the start of a struct type in a type signature */ +#define DBUS_STRUCT_BEGIN_CHAR ((int) '(') +/** #DBUS_STRUCT_BEGIN_CHAR as a string literal instead of a int literal */ +#define DBUS_STRUCT_BEGIN_CHAR_AS_STRING "(" +/** Code marking the end of a struct type in a type signature */ +#define DBUS_STRUCT_END_CHAR ((int) ')') +/** #DBUS_STRUCT_END_CHAR a string literal instead of a int literal */ +#define DBUS_STRUCT_END_CHAR_AS_STRING ")" +/** Code marking the start of a dict entry type in a type signature */ +#define DBUS_DICT_ENTRY_BEGIN_CHAR ((int) '{') +/** #DBUS_DICT_ENTRY_BEGIN_CHAR as a string literal instead of a int literal */ +#define DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING "{" +/** Code marking the end of a dict entry type in a type signature */ +#define DBUS_DICT_ENTRY_END_CHAR ((int) '}') +/** #DBUS_DICT_ENTRY_END_CHAR as a string literal instead of a int literal */ +#define DBUS_DICT_ENTRY_END_CHAR_AS_STRING "}" + +/** Max length in bytes of a bus name, interface, or member (not object + * path, paths are unlimited). This is limited because lots of stuff + * is O(n) in this number, plus it would be obnoxious to type in a + * paragraph-long method name so most likely something like that would + * be an exploit. + */ +#define DBUS_MAXIMUM_NAME_LENGTH 255 + +/** This one is 255 so it fits in a byte */ +#define DBUS_MAXIMUM_SIGNATURE_LENGTH 255 + +/** Max length of a match rule string; to keep people from hosing the + * daemon with some huge rule + */ +#define DBUS_MAXIMUM_MATCH_RULE_LENGTH 1024 + +/** Max arg number you can match on in a match rule, e.g. + * arg0='hello' is OK, arg3489720987='hello' is not + */ +#define DBUS_MAXIMUM_MATCH_RULE_ARG_NUMBER 63 + +/** Max length of a marshaled array in bytes (64M, 2^26) We use signed + * int for lengths so must be INT_MAX or less. We need something a + * bit smaller than INT_MAX because the array is inside a message with + * header info, etc. so an INT_MAX array wouldn't allow the message + * overhead. The 64M number is an attempt at a larger number than + * we'd reasonably ever use, but small enough that your bus would chew + * through it fairly quickly without locking up forever. If you have + * data that's likely to be larger than this, you should probably be + * sending it in multiple incremental messages anyhow. + */ +#define DBUS_MAXIMUM_ARRAY_LENGTH (67108864) +/** Number of bits you need in an unsigned to store the max array size */ +#define DBUS_MAXIMUM_ARRAY_LENGTH_BITS 26 + +/** The maximum total message size including header and body; similar + * rationale to max array size. + */ +#define DBUS_MAXIMUM_MESSAGE_LENGTH (DBUS_MAXIMUM_ARRAY_LENGTH * 2) +/** Number of bits you need in an unsigned to store the max message size */ +#define DBUS_MAXIMUM_MESSAGE_LENGTH_BITS 27 + +/** The maximum total number of unix fds in a message. Similar + * rationale as DBUS_MAXIMUM_MESSAGE_LENGTH. However we divide by four + * given that one fd is an int and hence at least 32 bits. + */ +#define DBUS_MAXIMUM_MESSAGE_UNIX_FDS (DBUS_MAXIMUM_MESSAGE_LENGTH/4) +/** Number of bits you need in an unsigned to store the max message unix fds */ +#define DBUS_MAXIMUM_MESSAGE_UNIX_FDS_BITS (DBUS_MAXIMUM_MESSAGE_LENGTH_BITS-2) + +/** Depth of recursion in the type tree. This is automatically limited + * to DBUS_MAXIMUM_SIGNATURE_LENGTH since you could only have an array + * of array of array of ... that fit in the max signature. But that's + * probably a bit too large. + */ +#define DBUS_MAXIMUM_TYPE_RECURSION_DEPTH 32 + +/* Types of message */ + +/** This value is never a valid message type, see dbus_message_get_type() */ +#define DBUS_MESSAGE_TYPE_INVALID 0 +/** Message type of a method call message, see dbus_message_get_type() */ +#define DBUS_MESSAGE_TYPE_METHOD_CALL 1 +/** Message type of a method return message, see dbus_message_get_type() */ +#define DBUS_MESSAGE_TYPE_METHOD_RETURN 2 +/** Message type of an error reply message, see dbus_message_get_type() */ +#define DBUS_MESSAGE_TYPE_ERROR 3 +/** Message type of a signal message, see dbus_message_get_type() */ +#define DBUS_MESSAGE_TYPE_SIGNAL 4 + +#define DBUS_NUM_MESSAGE_TYPES 5 + +/* Header flags */ + +/** If set, this flag means that the sender of a message does not care about getting + * a reply, so the recipient need not send one. See dbus_message_set_no_reply(). + */ +#define DBUS_HEADER_FLAG_NO_REPLY_EXPECTED 0x1 +/** + * If set, this flag means that even if the message bus knows how to start an owner for + * the destination bus name (see dbus_message_set_destination()), it should not + * do so. If this flag is not set, the bus may launch a program to process the + * message. + */ +#define DBUS_HEADER_FLAG_NO_AUTO_START 0x2 +/** + * If set on a method call, this flag means that the caller is prepared to + * wait for interactive authorization. + */ +#define DBUS_HEADER_FLAG_ALLOW_INTERACTIVE_AUTHORIZATION 0x4 + +/* Header fields */ + +/** Not equal to any valid header field code */ +#define DBUS_HEADER_FIELD_INVALID 0 +/** Header field code for the path - the path is the object emitting a signal or the object receiving a method call. + * See dbus_message_set_path(). + */ +#define DBUS_HEADER_FIELD_PATH 1 +/** Header field code for the interface containing a member (method or signal). + * See dbus_message_set_interface(). + */ +#define DBUS_HEADER_FIELD_INTERFACE 2 +/** Header field code for a member (method or signal). See dbus_message_set_member(). */ +#define DBUS_HEADER_FIELD_MEMBER 3 +/** Header field code for an error name (found in #DBUS_MESSAGE_TYPE_ERROR messages). + * See dbus_message_set_error_name(). + */ +#define DBUS_HEADER_FIELD_ERROR_NAME 4 +/** Header field code for a reply serial, used to match a #DBUS_MESSAGE_TYPE_METHOD_RETURN message with the + * message that it's a reply to. See dbus_message_set_reply_serial(). + */ +#define DBUS_HEADER_FIELD_REPLY_SERIAL 5 +/** + * Header field code for the destination bus name of a message. See dbus_message_set_destination(). + */ +#define DBUS_HEADER_FIELD_DESTINATION 6 +/** + * Header field code for the sender of a message; usually initialized by the message bus. + * See dbus_message_set_sender(). + */ +#define DBUS_HEADER_FIELD_SENDER 7 +/** + * Header field code for the type signature of a message. + */ +#define DBUS_HEADER_FIELD_SIGNATURE 8 +/** + * Header field code for the number of unix file descriptors associated + * with this message. + */ +#define DBUS_HEADER_FIELD_UNIX_FDS 9 +/** + * Header field code for the container instance that sent this message. + */ +#define DBUS_HEADER_FIELD_CONTAINER_INSTANCE 10 + + +/** + * Value of the highest-numbered header field code, can be used to determine + * the size of an array indexed by header field code. Remember though + * that unknown codes must be ignored, so check for that before + * indexing the array. + */ +#define DBUS_HEADER_FIELD_LAST DBUS_HEADER_FIELD_CONTAINER_INSTANCE + +/** Header format is defined as a signature: + * byte byte order + * byte message type ID + * byte flags + * byte protocol version + * uint32 body length + * uint32 serial + * array of struct (byte,variant) (field name, value) + * + * The length of the header can be computed as the + * fixed size of the initial data, plus the length of + * the array at the end, plus padding to an 8-boundary. + */ +#define DBUS_HEADER_SIGNATURE \ + DBUS_TYPE_BYTE_AS_STRING \ + DBUS_TYPE_BYTE_AS_STRING \ + DBUS_TYPE_BYTE_AS_STRING \ + DBUS_TYPE_BYTE_AS_STRING \ + DBUS_TYPE_UINT32_AS_STRING \ + DBUS_TYPE_UINT32_AS_STRING \ + DBUS_TYPE_ARRAY_AS_STRING \ + DBUS_STRUCT_BEGIN_CHAR_AS_STRING \ + DBUS_TYPE_BYTE_AS_STRING \ + DBUS_TYPE_VARIANT_AS_STRING \ + DBUS_STRUCT_END_CHAR_AS_STRING + + +/** + * The smallest header size that can occur. (It won't be valid due to + * missing required header fields.) This is 4 bytes, two uint32, an + * array length. This isn't any kind of resource limit, just the + * necessary/logical outcome of the header signature. + */ +#define DBUS_MINIMUM_HEADER_SIZE 16 + +/* Errors */ +/* WARNING these get autoconverted to an enum in dbus-glib.h. Thus, + * if you change the order it breaks the ABI. Keep them in order. + * Also, don't change the formatting since that will break the sed + * script. + */ +/** A generic error; "something went wrong" - see the error message for more. */ +#define DBUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed" +/** There was not enough memory to complete an operation. */ +#define DBUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory" +/** The bus doesn't know how to launch a service to supply the bus name you wanted. */ +#define DBUS_ERROR_SERVICE_UNKNOWN "org.freedesktop.DBus.Error.ServiceUnknown" +/** The bus name you referenced doesn't exist (i.e. no application owns it). */ +#define DBUS_ERROR_NAME_HAS_NO_OWNER "org.freedesktop.DBus.Error.NameHasNoOwner" +/** No reply to a message expecting one, usually means a timeout occurred. */ +#define DBUS_ERROR_NO_REPLY "org.freedesktop.DBus.Error.NoReply" +/** Something went wrong reading or writing to a socket, for example. */ +#define DBUS_ERROR_IO_ERROR "org.freedesktop.DBus.Error.IOError" +/** A D-Bus bus address was malformed. */ +#define DBUS_ERROR_BAD_ADDRESS "org.freedesktop.DBus.Error.BadAddress" +/** Requested operation isn't supported (like ENOSYS on UNIX). */ +#define DBUS_ERROR_NOT_SUPPORTED "org.freedesktop.DBus.Error.NotSupported" +/** Some limited resource is exhausted. */ +#define DBUS_ERROR_LIMITS_EXCEEDED "org.freedesktop.DBus.Error.LimitsExceeded" +/** Security restrictions don't allow doing what you're trying to do. */ +#define DBUS_ERROR_ACCESS_DENIED "org.freedesktop.DBus.Error.AccessDenied" +/** Authentication didn't work. */ +#define DBUS_ERROR_AUTH_FAILED "org.freedesktop.DBus.Error.AuthFailed" +/** Unable to connect to server (probably caused by ECONNREFUSED on a socket). */ +#define DBUS_ERROR_NO_SERVER "org.freedesktop.DBus.Error.NoServer" +/** Certain timeout errors, possibly ETIMEDOUT on a socket. + * Note that #DBUS_ERROR_NO_REPLY is used for message reply timeouts. + * @warning this is confusingly-named given that #DBUS_ERROR_TIMED_OUT also exists. We can't fix + * it for compatibility reasons so just be careful. + */ +#define DBUS_ERROR_TIMEOUT "org.freedesktop.DBus.Error.Timeout" +/** No network access (probably ENETUNREACH on a socket). */ +#define DBUS_ERROR_NO_NETWORK "org.freedesktop.DBus.Error.NoNetwork" +/** Can't bind a socket since its address is in use (i.e. EADDRINUSE). */ +#define DBUS_ERROR_ADDRESS_IN_USE "org.freedesktop.DBus.Error.AddressInUse" +/** The connection is disconnected and you're trying to use it. */ +#define DBUS_ERROR_DISCONNECTED "org.freedesktop.DBus.Error.Disconnected" +/** Invalid arguments passed to a method call. */ +#define DBUS_ERROR_INVALID_ARGS "org.freedesktop.DBus.Error.InvalidArgs" +/** Missing file. */ +#define DBUS_ERROR_FILE_NOT_FOUND "org.freedesktop.DBus.Error.FileNotFound" +/** Existing file and the operation you're using does not silently overwrite. */ +#define DBUS_ERROR_FILE_EXISTS "org.freedesktop.DBus.Error.FileExists" +/** Method name you invoked isn't known by the object you invoked it on. */ +#define DBUS_ERROR_UNKNOWN_METHOD "org.freedesktop.DBus.Error.UnknownMethod" +/** Object you invoked a method on isn't known. */ +#define DBUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject" +/** Interface you invoked a method on isn't known by the object. */ +#define DBUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface" +/** Property you tried to access isn't known by the object. */ +#define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" +/** Property you tried to set is read-only. */ +#define DBUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly" +/** Certain timeout errors, e.g. while starting a service. + * @warning this is confusingly-named given that #DBUS_ERROR_TIMEOUT also exists. We can't fix + * it for compatibility reasons so just be careful. + */ +#define DBUS_ERROR_TIMED_OUT "org.freedesktop.DBus.Error.TimedOut" +/** Tried to remove or modify a match rule that didn't exist. */ +#define DBUS_ERROR_MATCH_RULE_NOT_FOUND "org.freedesktop.DBus.Error.MatchRuleNotFound" +/** The match rule isn't syntactically valid. */ +#define DBUS_ERROR_MATCH_RULE_INVALID "org.freedesktop.DBus.Error.MatchRuleInvalid" +/** While starting a new process, the exec() call failed. */ +#define DBUS_ERROR_SPAWN_EXEC_FAILED "org.freedesktop.DBus.Error.Spawn.ExecFailed" +/** While starting a new process, the fork() call failed. */ +#define DBUS_ERROR_SPAWN_FORK_FAILED "org.freedesktop.DBus.Error.Spawn.ForkFailed" +/** While starting a new process, the child exited with a status code. */ +#define DBUS_ERROR_SPAWN_CHILD_EXITED "org.freedesktop.DBus.Error.Spawn.ChildExited" +/** While starting a new process, the child exited on a signal. */ +#define DBUS_ERROR_SPAWN_CHILD_SIGNALED "org.freedesktop.DBus.Error.Spawn.ChildSignaled" +/** While starting a new process, something went wrong. */ +#define DBUS_ERROR_SPAWN_FAILED "org.freedesktop.DBus.Error.Spawn.Failed" +/** We failed to setup the environment correctly. */ +#define DBUS_ERROR_SPAWN_SETUP_FAILED "org.freedesktop.DBus.Error.Spawn.FailedToSetup" +/** We failed to setup the config parser correctly. */ +#define DBUS_ERROR_SPAWN_CONFIG_INVALID "org.freedesktop.DBus.Error.Spawn.ConfigInvalid" +/** Bus name was not valid. */ +#define DBUS_ERROR_SPAWN_SERVICE_INVALID "org.freedesktop.DBus.Error.Spawn.ServiceNotValid" +/** Service file not found in system-services directory. */ +#define DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND "org.freedesktop.DBus.Error.Spawn.ServiceNotFound" +/** Permissions are incorrect on the setuid helper. */ +#define DBUS_ERROR_SPAWN_PERMISSIONS_INVALID "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid" +/** Service file invalid (Name, User or Exec missing). */ +#define DBUS_ERROR_SPAWN_FILE_INVALID "org.freedesktop.DBus.Error.Spawn.FileInvalid" +/** There was not enough memory to complete the operation. */ +#define DBUS_ERROR_SPAWN_NO_MEMORY "org.freedesktop.DBus.Error.Spawn.NoMemory" +/** Tried to get a UNIX process ID and it wasn't available. */ +#define DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN "org.freedesktop.DBus.Error.UnixProcessIdUnknown" +/** A type signature is not valid. */ +#define DBUS_ERROR_INVALID_SIGNATURE "org.freedesktop.DBus.Error.InvalidSignature" +/** A file contains invalid syntax or is otherwise broken. */ +#define DBUS_ERROR_INVALID_FILE_CONTENT "org.freedesktop.DBus.Error.InvalidFileContent" +/** Asked for SELinux security context and it wasn't available. */ +#define DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown" +/** Asked for ADT audit data and it wasn't available. */ +#define DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN "org.freedesktop.DBus.Error.AdtAuditDataUnknown" +/** There's already an object with the requested object path. */ +#define DBUS_ERROR_OBJECT_PATH_IN_USE "org.freedesktop.DBus.Error.ObjectPathInUse" +/** The message meta data does not match the payload. e.g. expected + number of file descriptors were not sent over the socket this message was received on. */ +#define DBUS_ERROR_INCONSISTENT_MESSAGE "org.freedesktop.DBus.Error.InconsistentMessage" +/** The message is not allowed without performing interactive authorization, + * but could have succeeded if an interactive authorization step was + * allowed. */ +#define DBUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired" +/** The connection is not from a container, or the specified container instance + * does not exist. */ +#define DBUS_ERROR_NOT_CONTAINER "org.freedesktop.DBus.Error.NotContainer" + +/* XML introspection format */ + +/** XML namespace of the introspection format version 1.0 */ +#define DBUS_INTROSPECT_1_0_XML_NAMESPACE "http://www.freedesktop.org/standards/dbus" +/** XML public identifier of the introspection format version 1.0 */ +#define DBUS_INTROSPECT_1_0_XML_PUBLIC_IDENTIFIER "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +/** XML system identifier of the introspection format version 1.0 */ +#define DBUS_INTROSPECT_1_0_XML_SYSTEM_IDENTIFIER "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd" +/** XML document type declaration of the introspection format version 1.0 */ +#define DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE "<!DOCTYPE node PUBLIC \"" DBUS_INTROSPECT_1_0_XML_PUBLIC_IDENTIFIER "\"\n\"" DBUS_INTROSPECT_1_0_XML_SYSTEM_IDENTIFIER "\">\n" + +/** @} */ + +#ifdef __cplusplus +#if 0 +{ /* avoids confusing emacs indentation */ +#endif +} +#endif + +#endif /* DBUS_PROTOCOL_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-resources.c b/src/3rdparty/libdbus/dbus/dbus-resources.c new file mode 100644 index 00000000..7e1c3e55 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-resources.c @@ -0,0 +1,342 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-resources.c Resource tracking/limits + * + * Copyright (C) 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include <dbus/dbus-resources.h> +#include <dbus/dbus-internals.h> + +/** + * @defgroup DBusResources Resource limits related code + * @ingroup DBusInternals + * @brief DBusCounter and other stuff related to resource limits + * + * Types and functions related to tracking resource limits, + * such as the maximum amount of memory/unix fds a connection can use + * for messages, etc. + */ + +/** + * @defgroup DBusResourcesInternals Resource limits implementation details + * @ingroup DBusInternals + * @brief Resource limits implementation details + * + * Implementation details of resource limits code. + * + * @{ + */ + +/** + * @brief Internals of DBusCounter. + * + * DBusCounter internals. DBusCounter is an opaque object, it must be + * used via accessor functions. + */ +struct DBusCounter +{ + int refcount; /**< reference count */ + + long size_value; /**< current size counter value */ + long unix_fd_value; /**< current unix fd counter value */ + +#ifdef DBUS_ENABLE_STATS + long peak_size_value; /**< largest ever size counter value */ + long peak_unix_fd_value; /**< largest ever unix fd counter value */ +#endif + + long notify_size_guard_value; /**< call notify function when crossing this size value */ + long notify_unix_fd_guard_value; /**< call notify function when crossing this unix fd value */ + + DBusCounterNotifyFunction notify_function; /**< notify function */ + void *notify_data; /**< data for notify function */ + dbus_bool_t notify_pending : 1; /**< TRUE if the guard value has been crossed */ + DBusRMutex *mutex; /**< Lock on the entire DBusCounter */ +}; + +/** @} */ /* end of resource limits internals docs */ + +/** + * @addtogroup DBusResources + * @{ + */ + +/** + * Creates a new DBusCounter. DBusCounter is used + * to count usage of some resource such as memory. + * + * @returns new counter or #NULL on failure + */ +DBusCounter* +_dbus_counter_new (void) +{ + DBusCounter *counter; + + counter = dbus_new0 (DBusCounter, 1); + if (counter == NULL) + return NULL; + + counter->refcount = 1; + + _dbus_rmutex_new_at_location (&counter->mutex); + if (counter->mutex == NULL) + { + dbus_free (counter); + counter = NULL; + } + + return counter; +} + +/** + * Increments refcount of the counter + * + * @param counter the counter + * @returns the counter + */ +DBusCounter * +_dbus_counter_ref (DBusCounter *counter) +{ + _dbus_rmutex_lock (counter->mutex); + + _dbus_assert (counter->refcount > 0); + + counter->refcount += 1; + + _dbus_rmutex_unlock (counter->mutex); + + return counter; +} + +/** + * Decrements refcount of the counter and possibly + * finalizes the counter. + * + * @param counter the counter + */ +void +_dbus_counter_unref (DBusCounter *counter) +{ + dbus_bool_t last_ref = FALSE; + + _dbus_rmutex_lock (counter->mutex); + + _dbus_assert (counter->refcount > 0); + + counter->refcount -= 1; + last_ref = (counter->refcount == 0); + + _dbus_rmutex_unlock (counter->mutex); + + if (last_ref) + { + _dbus_rmutex_free_at_location (&counter->mutex); + dbus_free (counter); + } +} + +/** + * Adjusts the value of the size counter by the given + * delta which may be positive or negative. + * + * This function may be called with locks held. After calling it, when + * any relevant locks are no longer held you must call _dbus_counter_notify(). + * + * @param counter the counter + * @param delta value to add to the size counter's current value + */ +void +_dbus_counter_adjust_size (DBusCounter *counter, + long delta) +{ + long old = 0; + + _dbus_rmutex_lock (counter->mutex); + + old = counter->size_value; + + counter->size_value += delta; + +#ifdef DBUS_ENABLE_STATS + if (counter->peak_size_value < counter->size_value) + counter->peak_size_value = counter->size_value; +#endif + +#if 0 + _dbus_verbose ("Adjusting counter %ld by %ld = %ld\n", + old, delta, counter->size_value); +#endif + + if (counter->notify_function != NULL && + ((old < counter->notify_size_guard_value && + counter->size_value >= counter->notify_size_guard_value) || + (old >= counter->notify_size_guard_value && + counter->size_value < counter->notify_size_guard_value))) + counter->notify_pending = TRUE; + + _dbus_rmutex_unlock (counter->mutex); +} + +/** + * Calls the notify function from _dbus_counter_set_notify(), + * if that function has been specified and the counter has crossed the + * guard value (in either direction) since the last call to this function. + * + * This function must not be called with locks held, since it can call out + * to user code. + */ +void +_dbus_counter_notify (DBusCounter *counter) +{ + DBusCounterNotifyFunction notify_function = NULL; + void *notify_data = NULL; + + _dbus_rmutex_lock (counter->mutex); + if (counter->notify_pending) + { + counter->notify_pending = FALSE; + notify_function = counter->notify_function; + notify_data = counter->notify_data; + } + _dbus_rmutex_unlock (counter->mutex); + + if (notify_function != NULL) + (* notify_function) (counter, notify_data); +} + +/** + * Adjusts the value of the unix fd counter by the given + * delta which may be positive or negative. + * + * This function may be called with locks held. After calling it, when + * any relevant locks are no longer held you must call _dbus_counter_notify(). + * + * @param counter the counter + * @param delta value to add to the unix fds counter's current value + */ +void +_dbus_counter_adjust_unix_fd (DBusCounter *counter, + long delta) +{ + long old = 0; + + _dbus_rmutex_lock (counter->mutex); + + old = counter->unix_fd_value; + + counter->unix_fd_value += delta; + +#ifdef DBUS_ENABLE_STATS + if (counter->peak_unix_fd_value < counter->unix_fd_value) + counter->peak_unix_fd_value = counter->unix_fd_value; +#endif + +#if 0 + _dbus_verbose ("Adjusting counter %ld by %ld = %ld\n", + old, delta, counter->unix_fd_value); +#endif + + if (counter->notify_function != NULL && + ((old < counter->notify_unix_fd_guard_value && + counter->unix_fd_value >= counter->notify_unix_fd_guard_value) || + (old >= counter->notify_unix_fd_guard_value && + counter->unix_fd_value < counter->notify_unix_fd_guard_value))) + counter->notify_pending = TRUE; + + _dbus_rmutex_unlock (counter->mutex); +} + +/** + * Gets the current value of the size counter. + * + * @param counter the counter + * @returns its current size value + */ +long +_dbus_counter_get_size_value (DBusCounter *counter) +{ + long result; + _dbus_rmutex_lock (counter->mutex); + result = counter->size_value; + _dbus_rmutex_unlock (counter->mutex); + return result; +} + +/** + * Gets the current value of the unix fd counter. + * + * @param counter the counter + * @returns its current unix fd value + */ +long +_dbus_counter_get_unix_fd_value (DBusCounter *counter) +{ + long result; + _dbus_rmutex_lock (counter->mutex); + result = counter->unix_fd_value; + _dbus_rmutex_unlock (counter->mutex); + return result; +} + +/** + * Sets the notify function for this counter; the notify function is + * called whenever the counter's values cross the guard values in + * either direction (moving up, or moving down). + * + * @param counter the counter + * @param size_guard_value the value we're notified if the size counter crosses + * @param unix_fd_guard_value the value we're notified if the unix fd counter crosses + * @param function function to call in order to notify + * @param user_data data to pass to the function + */ +void +_dbus_counter_set_notify (DBusCounter *counter, + long size_guard_value, + long unix_fd_guard_value, + DBusCounterNotifyFunction function, + void *user_data) +{ + _dbus_rmutex_lock (counter->mutex); + counter->notify_size_guard_value = size_guard_value; + counter->notify_unix_fd_guard_value = unix_fd_guard_value; + counter->notify_function = function; + counter->notify_data = user_data; + counter->notify_pending = FALSE; + _dbus_rmutex_unlock (counter->mutex); +} + +#ifdef DBUS_ENABLE_STATS +long +_dbus_counter_get_peak_size_value (DBusCounter *counter) +{ + return counter->peak_size_value; +} + +long +_dbus_counter_get_peak_unix_fd_value (DBusCounter *counter) +{ + return counter->peak_unix_fd_value; +} +#endif + +/** @} */ /* end of resource limits exported API */ diff --git a/src/3rdparty/libdbus/dbus/dbus-resources.h b/src/3rdparty/libdbus/dbus/dbus-resources.h new file mode 100644 index 00000000..2df56322 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-resources.h @@ -0,0 +1,69 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-resources.h Resource tracking/limits + * + * Copyright (C) 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_RESOURCES_H +#define DBUS_RESOURCES_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-macros-internal.h> +#include <dbus/dbus-errors.h> +#include <dbus/dbus-connection.h> + +DBUS_BEGIN_DECLS + +typedef struct DBusCounter DBusCounter; + +typedef void (* DBusCounterNotifyFunction) (DBusCounter *counter, + void *user_data); +DBUS_EMBEDDED_TESTS_EXPORT +DBusCounter* _dbus_counter_new (void); +DBusCounter* _dbus_counter_ref (DBusCounter *counter); +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_counter_unref (DBusCounter *counter); + +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_counter_adjust_size (DBusCounter *counter, + long delta); +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_counter_adjust_unix_fd (DBusCounter *counter, + long delta); +void _dbus_counter_notify (DBusCounter *counter); +DBUS_EMBEDDED_TESTS_EXPORT +long _dbus_counter_get_size_value (DBusCounter *counter); +DBUS_EMBEDDED_TESTS_EXPORT +long _dbus_counter_get_unix_fd_value (DBusCounter *counter); + +void _dbus_counter_set_notify (DBusCounter *counter, + long size_guard_value, + long unix_fd_guard_value, + DBusCounterNotifyFunction function, + void *user_data); + +/* if DBUS_ENABLE_STATS */ +long _dbus_counter_get_peak_size_value (DBusCounter *counter); +long _dbus_counter_get_peak_unix_fd_value (DBusCounter *counter); + +DBUS_END_DECLS + +#endif /* DBUS_RESOURCES_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-server-debug-pipe.c b/src/3rdparty/libdbus/dbus/dbus-server-debug-pipe.c new file mode 100644 index 00000000..bf91939a --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-server-debug-pipe.c @@ -0,0 +1,433 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server-debug-pipe.c In-proc debug server implementation + * + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2003, 2004 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-server-debug-pipe.h" +#include "dbus-transport-socket.h" +#include "dbus-connection-internal.h" +#include "dbus-hash.h" +#include "dbus-string.h" +#include "dbus-protocol.h" + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + +/** + * @defgroup DBusServerDebugPipe DBusServerDebugPipe + * @ingroup DBusInternals + * @brief In-process pipe debug server used in unit tests. + * + * Types and functions related to DBusServerDebugPipe. + * This is used for unit testing. + * + * @{ + */ + +/** + * Opaque object representing a debug server implementation. + */ +typedef struct DBusServerDebugPipe DBusServerDebugPipe; + +/** + * Implementation details of DBusServerDebugPipe. All members + * are private. + */ +struct DBusServerDebugPipe +{ + DBusServer base; /**< Parent class members. */ + + char *name; /**< Server name. */ + + dbus_bool_t disconnected; /**< TRUE if disconnect has been called */ +}; + +/* FIXME not threadsafe (right now the test suite doesn't use threads anyhow ) */ +static DBusHashTable *server_pipe_hash; +static int server_pipe_hash_refcount = 0; + +static dbus_bool_t +pipe_hash_ref (void) +{ + if (!server_pipe_hash) + { + _dbus_assert (server_pipe_hash_refcount == 0); + + server_pipe_hash = _dbus_hash_table_new (DBUS_HASH_STRING, NULL, NULL); + + if (!server_pipe_hash) + return FALSE; + } + + server_pipe_hash_refcount = 1; + + return TRUE; +} + +static void +pipe_hash_unref (void) +{ + _dbus_assert (server_pipe_hash != NULL); + _dbus_assert (server_pipe_hash_refcount > 0); + + server_pipe_hash_refcount -= 1; + if (server_pipe_hash_refcount == 0) + { + _dbus_hash_table_unref (server_pipe_hash); + server_pipe_hash = NULL; + } +} + +static void +debug_finalize (DBusServer *server) +{ + DBusServerDebugPipe *debug_server = (DBusServerDebugPipe*) server; + + pipe_hash_unref (); + + _dbus_server_finalize_base (server); + + dbus_free (debug_server->name); + dbus_free (server); +} + +static void +debug_disconnect (DBusServer *server) +{ + ((DBusServerDebugPipe*)server)->disconnected = TRUE; +} + +static DBusServerVTable debug_vtable = { + debug_finalize, + debug_disconnect +}; + +/** + * Creates a new debug server using an in-process pipe + * + * @param server_name the name of the server. + * @param error address where an error can be returned. + * @returns a new server, or #NULL on failure. + */ +DBusServer* +_dbus_server_debug_pipe_new (const char *server_name, + DBusError *error) +{ + DBusServerDebugPipe *debug_server; + DBusString address; + DBusString name_str; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!pipe_hash_ref ()) + return NULL; + + if (_dbus_hash_table_lookup_string (server_pipe_hash, server_name) != NULL) + { + dbus_set_error (error, DBUS_ERROR_ADDRESS_IN_USE, NULL); + pipe_hash_unref (); + return NULL; + } + + debug_server = dbus_new0 (DBusServerDebugPipe, 1); + if (debug_server == NULL) + goto nomem_0; + + if (!_dbus_string_init (&address)) + goto nomem_1; + + _dbus_string_init_const (&name_str, server_name); + if (!_dbus_string_append (&address, "debug-pipe:name=") || + !_dbus_address_append_escaped (&address, &name_str)) + goto nomem_2; + + debug_server->name = _dbus_strdup (server_name); + if (debug_server->name == NULL) + goto nomem_2; + + if (!_dbus_server_init_base (&debug_server->base, + &debug_vtable, &address, + error)) + goto fail_3; + + if (!_dbus_hash_table_insert_string (server_pipe_hash, + debug_server->name, + debug_server)) + goto nomem_4; + + _dbus_string_free (&address); + + /* server keeps the pipe hash ref */ + + _dbus_server_trace_ref (&debug_server->base, 0, 1, "debug_pipe_new"); + return (DBusServer *)debug_server; + + nomem_4: + _dbus_server_finalize_base (&debug_server->base); + fail_3: + dbus_free (debug_server->name); + nomem_2: + _dbus_string_free (&address); + nomem_1: + dbus_free (debug_server); + nomem_0: + pipe_hash_unref (); + if (error != NULL && !dbus_error_is_set (error)) + _DBUS_SET_OOM (error); + return NULL; +} + +/** + * Creates the client-side transport for + * a debug-pipe connection connected to the + * given debug-pipe server name. + * + * @param server_name name of server to connect to + * @param error address where an error can be returned. + * @returns #NULL on no memory or transport + */ +DBusTransport* +_dbus_transport_debug_pipe_new (const char *server_name, + DBusError *error) +{ + DBusTransport *client_transport; + DBusTransport *server_transport; + DBusConnection *connection; + DBusSocket client_fd, server_fd; + DBusServer *server; + DBusString address; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (server_pipe_hash == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_SERVER, NULL); + return NULL; + } + + server = _dbus_hash_table_lookup_string (server_pipe_hash, + server_name); + if (server == NULL || + ((DBusServerDebugPipe*)server)->disconnected) + { + dbus_set_error (error, DBUS_ERROR_NO_SERVER, NULL); + return NULL; + } + + if (!_dbus_string_init (&address)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + if (!_dbus_string_append (&address, "debug-pipe:name=") || + !_dbus_string_append (&address, server_name)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&address); + return NULL; + } + + if (!_dbus_socketpair (&client_fd, &server_fd, FALSE, NULL)) + { + _dbus_verbose ("failed to create full duplex pipe\n"); + dbus_set_error (error, DBUS_ERROR_FAILED, "Could not create full-duplex pipe"); + _dbus_string_free (&address); + return NULL; + } + + client_transport = _dbus_transport_new_for_socket (client_fd, + NULL, &address); + if (client_transport == NULL) + { + _dbus_close_socket (&client_fd, NULL); + _dbus_close_socket (&server_fd, NULL); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&address); + return NULL; + } + + _dbus_string_free (&address); + + _dbus_socket_invalidate (&client_fd); + + server_transport = _dbus_transport_new_for_socket (server_fd, + &server->guid_hex, NULL); + if (server_transport == NULL) + { + _dbus_transport_unref (client_transport); + _dbus_close_socket (&server_fd, NULL); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + _dbus_socket_invalidate (&server_fd); + + if (!_dbus_transport_set_auth_mechanisms (server_transport, + (const char**) server->auth_mechanisms)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_transport_unref (server_transport); + _dbus_transport_unref (client_transport); + return NULL; + } + + connection = _dbus_connection_new_for_transport (server_transport); + _dbus_transport_unref (server_transport); + server_transport = NULL; + + if (connection == NULL) + { + _dbus_transport_unref (client_transport); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + /* See if someone wants to handle this new connection, + * self-referencing for paranoia + */ + if (server->new_connection_function) + { + dbus_server_ref (server); + (* server->new_connection_function) (server, connection, + server->new_connection_data); + dbus_server_unref (server); + } + + /* If no one grabbed a reference, the connection will die, + * and the client transport will get an immediate disconnect + */ + _dbus_connection_close_if_only_one_ref (connection); + dbus_connection_unref (connection); + + return client_transport; +} + +/** + * Tries to interpret the address entry as a debug pipe entry. + * + * Sets error if the result is not OK. + * + * @param entry an address entry + * @param server_p location to store a new DBusServer, or #NULL on failure. + * @param error location to store rationale for failure on bad address + * @returns the outcome + * + */ +DBusServerListenResult +_dbus_server_listen_debug_pipe (DBusAddressEntry *entry, + DBusServer **server_p, + DBusError *error) +{ + const char *method; + + *server_p = NULL; + + method = dbus_address_entry_get_method (entry); + + if (strcmp (method, "debug-pipe") == 0) + { + const char *name = dbus_address_entry_get_value (entry, "name"); + + if (name == NULL) + { + _dbus_set_bad_address(error, "debug-pipe", "name", + NULL); + return DBUS_SERVER_LISTEN_BAD_ADDRESS; + } + + *server_p = _dbus_server_debug_pipe_new (name, error); + + if (*server_p) + { + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + return DBUS_SERVER_LISTEN_OK; + } + else + { + _DBUS_ASSERT_ERROR_IS_SET(error); + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + return DBUS_SERVER_LISTEN_NOT_HANDLED; + } +} + +/** + * Opens a debug pipe transport, used in the test suite. + * + * @param entry the address entry to try opening as debug-pipe + * @param transport_p return location for the opened transport + * @param error error to be set + * @returns result of the attempt + */ +DBusTransportOpenResult +_dbus_transport_open_debug_pipe (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error) +{ + const char *method; + + method = dbus_address_entry_get_method (entry); + _dbus_assert (method != NULL); + + if (strcmp (method, "debug-pipe") == 0) + { + const char *name = dbus_address_entry_get_value (entry, "name"); + + if (name == NULL) + { + _dbus_set_bad_address (error, "debug-pipe", "name", + NULL); + return DBUS_TRANSPORT_OPEN_BAD_ADDRESS; + } + + *transport_p = _dbus_transport_debug_pipe_new (name, error); + + if (*transport_p == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_OK; + } + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_NOT_HANDLED; + } +} + + +/** @} */ + +#endif /* DBUS_ENABLE_EMBEDDED_TESTS */ diff --git a/src/3rdparty/libdbus/dbus/dbus-server-debug-pipe.h b/src/3rdparty/libdbus/dbus/dbus-server-debug-pipe.h new file mode 100644 index 00000000..4284dc57 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-server-debug-pipe.h @@ -0,0 +1,49 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server-debug-pipe.h In-proc debug server implementation + * + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2003 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_SERVER_DEBUG_PIPE_H +#define DBUS_SERVER_DEBUG_PIPE_H + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-server-protected.h> +#include <dbus/dbus-transport-protected.h> + +DBUS_BEGIN_DECLS + +DBusServer* _dbus_server_debug_pipe_new (const char *server_name, + DBusError *error); +DBusTransport* _dbus_transport_debug_pipe_new (const char *server_name, + DBusError *error); +DBusServerListenResult _dbus_server_listen_debug_pipe (DBusAddressEntry *entry, + DBusServer **server_p, + DBusError *error); +DBusTransportOpenResult _dbus_transport_open_debug_pipe (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error); + + +DBUS_END_DECLS + +#endif /* DBUS_SERVER_DEBUG_PIPE_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-server-protected.h b/src/3rdparty/libdbus/dbus/dbus-server-protected.h new file mode 100644 index 00000000..d5aee150 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-server-protected.h @@ -0,0 +1,186 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server-protected.h Used by subclasses of DBusServer object (internal to D-Bus implementation) + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_SERVER_PROTECTED_H +#define DBUS_SERVER_PROTECTED_H + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-threads-internal.h> +#include <dbus/dbus-server.h> +#include <dbus/dbus-address.h> +#include <dbus/dbus-timeout.h> +#include <dbus/dbus-watch.h> +#include <dbus/dbus-resources.h> +#include <dbus/dbus-dataslot.h> +#include <dbus/dbus-string.h> + +DBUS_BEGIN_DECLS + +typedef struct DBusServerVTable DBusServerVTable; + +/** + * Virtual table to be implemented by all server "subclasses" + */ +struct DBusServerVTable +{ + void (* finalize) (DBusServer *server); + /**< The finalize method must free the server. */ + + void (* disconnect) (DBusServer *server); + /**< Disconnect this server. */ +}; + +/** + * @ingroup DBusServerInternals + * Internals of DBusServer object + */ +struct DBusServer +{ + DBusAtomic refcount; /**< Reference count. */ + const DBusServerVTable *vtable; /**< Virtual methods for this instance. */ + DBusRMutex *mutex; /**< Lock on the server object */ + + DBusGUID guid; /**< Globally unique ID of server */ + + DBusString guid_hex; /**< Hex-encoded version of GUID */ + + DBusWatchList *watches; /**< Our watches */ + DBusTimeoutList *timeouts; /**< Our timeouts */ + + char *address; /**< Address this server is listening on. */ + dbus_bool_t published_address; /**< flag which indicates that server has published its bus address. */ + + int max_connections; /**< Max number of connections allowed at once. */ + + DBusDataSlotList slot_list; /**< Data stored by allocated integer ID */ + + DBusNewConnectionFunction new_connection_function; + /**< Callback to invoke when a new connection is created. */ + void *new_connection_data; + /**< Data for new connection callback */ + DBusFreeFunction new_connection_free_data_function; + /**< Callback to invoke to free new_connection_data + * when server is finalized or data is replaced. + */ + + char **auth_mechanisms; /**< Array of allowed authentication mechanisms */ + + unsigned int disconnected : 1; /**< TRUE if we are disconnected. */ + +#ifndef DBUS_DISABLE_CHECKS + unsigned int have_server_lock : 1; /**< Does someone have the server mutex locked */ +#endif +}; + +dbus_bool_t _dbus_server_init_base (DBusServer *server, + const DBusServerVTable *vtable, + const DBusString *address, + DBusError *error); +void _dbus_server_finalize_base (DBusServer *server); +void _dbus_server_disconnect_unlocked (DBusServer *server); +dbus_bool_t _dbus_server_add_watch (DBusServer *server, + DBusWatch *watch); +void _dbus_server_remove_watch (DBusServer *server, + DBusWatch *watch); +DBUS_PRIVATE_EXPORT +void _dbus_server_toggle_all_watches (DBusServer *server, + dbus_bool_t enabled); +dbus_bool_t _dbus_server_add_timeout (DBusServer *server, + DBusTimeout *timeout); +void _dbus_server_remove_timeout (DBusServer *server, + DBusTimeout *timeout); +void _dbus_server_toggle_timeout (DBusServer *server, + DBusTimeout *timeout, + dbus_bool_t enabled); + +DBUS_PRIVATE_EXPORT +void _dbus_server_ref_unlocked (DBusServer *server); +DBUS_PRIVATE_EXPORT +void _dbus_server_unref_unlocked (DBusServer *server); + +typedef enum +{ + DBUS_SERVER_LISTEN_NOT_HANDLED, /**< we aren't in charge of this address type */ + DBUS_SERVER_LISTEN_OK, /**< we set up the listen */ + DBUS_SERVER_LISTEN_BAD_ADDRESS, /**< malformed address */ + DBUS_SERVER_LISTEN_DID_NOT_CONNECT, /**< well-formed address but failed to set it up */ + DBUS_SERVER_LISTEN_ADDRESS_ALREADY_USED /**< address is already used */ +} DBusServerListenResult; + +DBusServerListenResult _dbus_server_listen_unix_socket (DBusAddressEntry *entry, + DBusServer **server_p, + DBusError *error); + +DBusServerListenResult _dbus_server_listen_platform_specific (DBusAddressEntry *entry, + DBusServer **server_p, + DBusError *error); + +#ifdef DBUS_ENABLE_VERBOSE_MODE +void _dbus_server_trace_ref (DBusServer *server, + int old_refcount, + int new_refcount, + const char *why); +#else +#define _dbus_server_trace_ref(s,o,n,w) \ + do \ + {\ + (void) (o); \ + (void) (n); \ + } while (0) +#endif + +#ifdef DBUS_DISABLE_CHECKS +#define TOOK_LOCK_CHECK(server) +#define RELEASING_LOCK_CHECK(server) +#define HAVE_LOCK_CHECK(server) +#else +#define TOOK_LOCK_CHECK(server) do { \ + _dbus_assert (!(server)->have_server_lock); \ + (server)->have_server_lock = TRUE; \ + } while (0) +#define RELEASING_LOCK_CHECK(server) do { \ + _dbus_assert ((server)->have_server_lock); \ + (server)->have_server_lock = FALSE; \ + } while (0) +#define HAVE_LOCK_CHECK(server) _dbus_assert ((server)->have_server_lock) +/* A "DO_NOT_HAVE_LOCK_CHECK" is impossible since we need the lock to check the flag */ +#endif + +#define TRACE_LOCKS 0 + +#define SERVER_LOCK(server) do { \ + if (TRACE_LOCKS) { _dbus_verbose ("LOCK\n"); } \ + _dbus_rmutex_lock ((server)->mutex); \ + TOOK_LOCK_CHECK (server); \ + } while (0) + +#define SERVER_UNLOCK(server) do { \ + if (TRACE_LOCKS) { _dbus_verbose ("UNLOCK\n"); } \ + RELEASING_LOCK_CHECK (server); \ + _dbus_rmutex_unlock ((server)->mutex); \ + } while (0) + +DBUS_END_DECLS + +#endif /* DBUS_SERVER_PROTECTED_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-server-socket.c b/src/3rdparty/libdbus/dbus/dbus-server-socket.c new file mode 100644 index 00000000..5dc1b54e --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-server-socket.c @@ -0,0 +1,884 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server-socket.c Server implementation for sockets + * + * Copyright (C) 2002, 2003, 2004, 2006 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-server-socket.h" +#include "dbus-transport-socket.h" +#include "dbus-connection-internal.h" +#include "dbus-memory.h" +#include "dbus-nonce.h" +#include "dbus-string.h" + +/** + * @defgroup DBusServerSocket DBusServer implementations for SOCKET + * @ingroup DBusInternals + * @brief Implementation details of DBusServer on SOCKET + * + * @{ + */ +/** + * + * Opaque object representing a Socket server implementation. + */ +typedef struct DBusServerSocket DBusServerSocket; + +/** + * Implementation details of DBusServerSocket. All members + * are private. + */ +struct DBusServerSocket +{ + DBusServer base; /**< Parent class members. */ + int n_fds; /**< Number of active file handles */ + DBusSocket *fds; /**< File descriptor or DBUS_SOCKET_INVALID if disconnected. */ + DBusWatch **watch; /**< File descriptor watch. */ + char *socket_name; /**< Name of domain socket, to unlink if appropriate */ + DBusNonceFile *noncefile; /**< Nonce file used to authenticate clients */ +}; + +static void +socket_finalize (DBusServer *server) +{ + DBusServerSocket *socket_server = (DBusServerSocket*) server; + int i; + + _dbus_server_finalize_base (server); + + for (i = 0 ; i < socket_server->n_fds ; i++) + if (socket_server->watch[i]) + { + _dbus_watch_unref (socket_server->watch[i]); + socket_server->watch[i] = NULL; + } + + dbus_free (socket_server->fds); + dbus_free (socket_server->watch); + dbus_free (socket_server->socket_name); + _dbus_noncefile_delete (&socket_server->noncefile, NULL); + dbus_free (server); +} + +/* Return value is just for memory, not other failures. */ +static dbus_bool_t +handle_new_client_fd_and_unlock (DBusServer *server, + DBusSocket client_fd) +{ + DBusConnection *connection; + DBusTransport *transport; + DBusNewConnectionFunction new_connection_function; + void *new_connection_data; + + _dbus_verbose ("Creating new client connection with fd %" DBUS_SOCKET_FORMAT "\n", + _dbus_socket_printable (client_fd)); + + HAVE_LOCK_CHECK (server); + + if (!_dbus_set_socket_nonblocking (client_fd, NULL)) + { + SERVER_UNLOCK (server); + return TRUE; + } + + transport = _dbus_transport_new_for_socket (client_fd, &server->guid_hex, NULL); + if (transport == NULL) + { + _dbus_close_socket (&client_fd, NULL); + SERVER_UNLOCK (server); + return FALSE; + } + + if (!_dbus_transport_set_auth_mechanisms (transport, + (const char **) server->auth_mechanisms)) + { + _dbus_transport_unref (transport); + SERVER_UNLOCK (server); + return FALSE; + } + + /* note that client_fd is now owned by the transport, and will be + * closed on transport disconnection/finalization + */ + + connection = _dbus_connection_new_for_transport (transport); + _dbus_transport_unref (transport); + transport = NULL; /* now under the connection lock */ + + if (connection == NULL) + { + SERVER_UNLOCK (server); + return FALSE; + } + + /* See if someone wants to handle this new connection, self-referencing + * for paranoia. + */ + new_connection_function = server->new_connection_function; + new_connection_data = server->new_connection_data; + + _dbus_server_ref_unlocked (server); + SERVER_UNLOCK (server); + + if (new_connection_function) + { + (* new_connection_function) (server, connection, + new_connection_data); + } + dbus_server_unref (server); + + /* If no one grabbed a reference, the connection will die. */ + _dbus_connection_close_if_only_one_ref (connection); + dbus_connection_unref (connection); + + return TRUE; +} + +static dbus_bool_t +socket_handle_watch (DBusWatch *watch, + unsigned int flags, + void *data) +{ + DBusServer *server = data; + DBusServerSocket *socket_server = data; + +#ifndef DBUS_DISABLE_ASSERT + int i; + dbus_bool_t found = FALSE; +#endif + + SERVER_LOCK (server); + +#ifndef DBUS_DISABLE_ASSERT + for (i = 0 ; i < socket_server->n_fds ; i++) + { + if (socket_server->watch[i] == watch) + { + found = TRUE; + break; + } + } + _dbus_assert (found); +#endif + + _dbus_verbose ("Handling client connection, flags 0x%x\n", flags); + + if (flags & DBUS_WATCH_READABLE) + { + DBusSocket client_fd; + DBusSocket listen_fd; + int saved_errno; + + listen_fd = _dbus_watch_get_socket (watch); + + if (socket_server->noncefile) + client_fd = _dbus_accept_with_noncefile (listen_fd, socket_server->noncefile); + else + client_fd = _dbus_accept (listen_fd); + + saved_errno = _dbus_save_socket_errno (); + + if (!_dbus_socket_is_valid (client_fd)) + { + /* EINTR handled for us */ + + if (_dbus_get_is_errno_eagain_or_ewouldblock (saved_errno)) + _dbus_verbose ("No client available to accept after all\n"); + else + _dbus_verbose ("Failed to accept a client connection: %s\n", + _dbus_strerror (saved_errno)); + + SERVER_UNLOCK (server); + } + else + { + if (!handle_new_client_fd_and_unlock (server, client_fd)) + _dbus_verbose ("Rejected client connection due to lack of memory\n"); + } + } + + if (flags & DBUS_WATCH_ERROR) + _dbus_verbose ("Error on server listening socket\n"); + + if (flags & DBUS_WATCH_HANGUP) + _dbus_verbose ("Hangup on server listening socket\n"); + + return TRUE; +} + +static void +socket_disconnect (DBusServer *server) +{ + DBusServerSocket *socket_server = (DBusServerSocket*) server; + int i; + + HAVE_LOCK_CHECK (server); + + for (i = 0 ; i < socket_server->n_fds ; i++) + { + if (socket_server->watch[i]) + { + _dbus_server_remove_watch (server, + socket_server->watch[i]); + _dbus_watch_invalidate (socket_server->watch[i]); + _dbus_watch_unref (socket_server->watch[i]); + socket_server->watch[i] = NULL; + } + + if (_dbus_socket_is_valid (socket_server->fds[i])) + _dbus_close_socket (&socket_server->fds[i], NULL); + } + + if (socket_server->socket_name != NULL) + { + DBusString tmp; + _dbus_string_init_const (&tmp, socket_server->socket_name); + _dbus_delete_file (&tmp, NULL); + } + + if (server->published_address) + _dbus_daemon_unpublish_session_bus_address(); + + HAVE_LOCK_CHECK (server); +} + +static const DBusServerVTable socket_vtable = { + socket_finalize, + socket_disconnect +}; + +/** + * Creates a new server listening on the given file descriptor. The + * file descriptor should be nonblocking (use + * _dbus_set_fd_nonblocking() to make it so). The file descriptor + * should be listening for connections, that is, listen() should have + * been successfully invoked on it. The server will use accept() to + * accept new client connections. + * + * @param fds list of file descriptors. + * @param n_fds number of file descriptors + * @param address the server's address + * @param noncefile to be used for authentication (NULL if not needed) + * @param error location to store reason for failure + * @returns the new server, or #NULL on OOM or other error. + * + */ +DBusServer* +_dbus_server_new_for_socket (DBusSocket *fds, + int n_fds, + const DBusString *address, + DBusNonceFile *noncefile, + DBusError *error) +{ + DBusServerSocket *socket_server; + DBusServer *server; + int i; + + socket_server = dbus_new0 (DBusServerSocket, 1); + if (socket_server == NULL) + goto failed; + + socket_server->noncefile = noncefile; + + socket_server->fds = dbus_new (DBusSocket, n_fds); + if (!socket_server->fds) + goto failed; + + socket_server->watch = dbus_new0 (DBusWatch *, n_fds); + if (!socket_server->watch) + goto failed; + + for (i = 0 ; i < n_fds ; i++) + { + DBusWatch *watch; + + watch = _dbus_watch_new (_dbus_socket_get_pollable (fds[i]), + DBUS_WATCH_READABLE, + TRUE, + socket_handle_watch, socket_server, + NULL); + if (watch == NULL) + goto failed; + + socket_server->n_fds++; + socket_server->fds[i] = fds[i]; + socket_server->watch[i] = watch; + } + + if (!_dbus_server_init_base (&socket_server->base, + &socket_vtable, address, + error)) + goto failed; + + server = (DBusServer*)socket_server; + + SERVER_LOCK (server); + + for (i = 0 ; i < n_fds ; i++) + { + if (!_dbus_server_add_watch (&socket_server->base, + socket_server->watch[i])) + { + int j; + + /* The caller is still responsible for closing the fds until + * we return successfully, so don't let socket_disconnect() + * close them */ + for (j = 0; j < n_fds; j++) + _dbus_socket_invalidate (&socket_server->fds[j]); + + /* socket_disconnect() will try to remove all the watches; + * make sure it doesn't see the ones that weren't even added + * yet */ + for (j = i; j < n_fds; j++) + { + _dbus_watch_invalidate (socket_server->watch[j]); + _dbus_watch_unref (socket_server->watch[j]); + socket_server->watch[j] = NULL; + } + + _dbus_server_disconnect_unlocked (server); + SERVER_UNLOCK (server); + _dbus_server_finalize_base (&socket_server->base); + goto failed; + } + } + + SERVER_UNLOCK (server); + + _dbus_server_trace_ref (&socket_server->base, 0, 1, "new_for_socket"); + return (DBusServer*) socket_server; + +failed: + if (socket_server != NULL) + { + if (socket_server->watch != NULL) + { + for (i = 0; i < n_fds; i++) + { + if (socket_server->watch[i] != NULL) + { + _dbus_watch_invalidate (socket_server->watch[i]); + _dbus_watch_unref (socket_server->watch[i]); + socket_server->watch[i] = NULL; + } + } + } + + dbus_free (socket_server->watch); + dbus_free (socket_server->fds); + dbus_free (socket_server); + } + + if (error != NULL && !dbus_error_is_set (error)) + _DBUS_SET_OOM (error); + + return NULL; +} + +/** + * Creates a new server listening on TCP. + * If host is NULL, it will default to localhost. + * If bind is NULL, it will default to the value for the host + * parameter, and if that is NULL, then localhost + * If bind is a hostname, it will be resolved and will listen + * on all returned addresses. + * If family is NULL, hostname resolution will try all address + * families, otherwise it can be ipv4 or ipv6 to restrict the + * addresses considered. + * + * @param host the hostname to report for the listen address + * @param bind the hostname to listen on + * @param port the port to listen on or 0 to let the OS choose + * @param family + * @param error location to store reason for failure. + * @param use_nonce whether to use a nonce for low-level authentication (nonce-tcp transport) or not (tcp transport) + * @returns the new server, or #NULL on failure. + */ +DBusServer* +_dbus_server_new_for_tcp_socket (const char *host, + const char *bind, + const char *port, + const char *family, + DBusError *error, + dbus_bool_t use_nonce) +{ + DBusServer *server = NULL; + DBusSocket *listen_fds = NULL; + int nlisten_fds = 0, i; + DBusString address = _DBUS_STRING_INIT_INVALID; + DBusString host_str; /* Initialized as const later, not freed */ + DBusString port_str = _DBUS_STRING_INIT_INVALID; + DBusNonceFile *noncefile = NULL; + const char *family_used = NULL; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&address)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + if (!_dbus_string_init (&port_str)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + if (host == NULL) + host = "localhost"; + + if (port == NULL) + port = "0"; + + if (bind == NULL) + bind = host; + else if (strcmp (bind, "*") == 0) + bind = NULL; + + nlisten_fds =_dbus_listen_tcp_socket (bind, port, family, + &port_str, + &family_used, + &listen_fds, error); + if (nlisten_fds <= 0) + { + _DBUS_ASSERT_ERROR_IS_SET(error); + goto failed; + } + + _dbus_string_init_const (&host_str, host); + if (!_dbus_string_append (&address, use_nonce ? "nonce-tcp:host=" : "tcp:host=") || + !_dbus_address_append_escaped (&address, &host_str) || + !_dbus_string_append (&address, ",port=") || + !_dbus_string_append (&address, _dbus_string_get_const_data(&port_str))) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + if (family_used && + (!_dbus_string_append (&address, ",family=") || + !_dbus_string_append (&address, family_used))) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + if (use_nonce) + { + if (!_dbus_noncefile_create (&noncefile, error)) + goto failed; + + if (!_dbus_string_append (&address, ",noncefile=") || + !_dbus_address_append_escaped (&address, _dbus_noncefile_get_path (noncefile))) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + } + + server = _dbus_server_new_for_socket (listen_fds, nlisten_fds, &address, noncefile, error); + if (server == NULL) + goto failed; + + /* server has taken ownership of noncefile and the fds in listen_fds */ + _dbus_string_free (&port_str); + _dbus_string_free (&address); + dbus_free(listen_fds); + + return server; + +failed: + _dbus_noncefile_delete (&noncefile, NULL); + + if (listen_fds != NULL) + { + for (i = 0; i < nlisten_fds; i++) + _dbus_close_socket (&listen_fds[i], NULL); + dbus_free (listen_fds); + } + + _dbus_string_free (&port_str); + _dbus_string_free (&address); + return NULL; +} + +/** + * Tries to interpret the address entry for various socket-related + * addresses (well, currently only tcp and nonce-tcp). + * + * Sets error if the result is not OK. + * + * @param entry an address entry + * @param server_p a new DBusServer, or #NULL on failure. + * @param error location to store rationale for failure on bad address + * @returns the outcome + * + */ +DBusServerListenResult +_dbus_server_listen_socket (DBusAddressEntry *entry, + DBusServer **server_p, + DBusError *error) +{ + const char *method; + + *server_p = NULL; + + method = dbus_address_entry_get_method (entry); + + if (strcmp (method, "tcp") == 0 || strcmp (method, "nonce-tcp") == 0) + { + const char *host; + const char *port; + const char *bind; + const char *family; + + host = dbus_address_entry_get_value (entry, "host"); + bind = dbus_address_entry_get_value (entry, "bind"); + port = dbus_address_entry_get_value (entry, "port"); + family = dbus_address_entry_get_value (entry, "family"); + + *server_p = _dbus_server_new_for_tcp_socket (host, bind, port, + family, error, strcmp (method, "nonce-tcp") == 0 ? TRUE : FALSE); + + if (*server_p) + { + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + return DBUS_SERVER_LISTEN_OK; + } + else + { + _DBUS_ASSERT_ERROR_IS_SET(error); + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + return DBUS_SERVER_LISTEN_NOT_HANDLED; + } +} + +/** + * This is a bad hack since it's really unix domain socket + * specific. Also, the function weirdly adopts ownership + * of the passed-in string. + * + * @param server a socket server + * @param filename socket filename to report/delete + * + */ +void +_dbus_server_socket_own_filename (DBusServer *server, + char *filename) +{ + DBusServerSocket *socket_server = (DBusServerSocket*) server; + + socket_server->socket_name = filename; +} + +/** + * Creates a new server listening on the given Unix domain socket. + * + * @param path the path for the domain socket. + * @param abstract #TRUE to use abstract socket namespace + * @param error location to store reason for failure. + * @returns the new server, or #NULL on failure. + */ +DBusServer* +_dbus_server_new_for_domain_socket (const char *path, + dbus_bool_t abstract, + DBusError *error) +{ + DBusServer *server; + DBusSocket listen_fd; + DBusString address; + char *path_copy; + DBusString path_str; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&address)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + _dbus_string_init_const (&path_str, path); + if ((abstract && + !_dbus_string_append (&address, "unix:abstract=")) || + (!abstract && + !_dbus_string_append (&address, "unix:path=")) || + !_dbus_address_append_escaped (&address, &path_str)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed_0; + } + + if (abstract) + { + path_copy = NULL; + } + else + { + path_copy = _dbus_strdup (path); + if (path_copy == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed_0; + } + } + + listen_fd = _dbus_listen_unix_socket (path, abstract, error); + + if (!_dbus_socket_is_valid (listen_fd)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed_1; + } + + server = _dbus_server_new_for_socket (&listen_fd, 1, &address, 0, error); + if (server == NULL) + { + goto failed_2; + } + + if (path_copy != NULL) + _dbus_server_socket_own_filename(server, path_copy); + + _dbus_string_free (&address); + + return server; + + failed_2: + _dbus_close_socket (&listen_fd, NULL); + failed_1: + dbus_free (path_copy); + failed_0: + _dbus_string_free (&address); + + return NULL; +} + +/** + * Creates a new Unix domain socket server listening under the given directory. + * This function is used for "unix:dir/tmpdir" kind of addresses. + * + * @param dir the path to a directory. + * @param error location to store reason for failure. + * @returns the new server, or #NULL on failure. + */ +static DBusServer * +_dbus_server_new_for_dir (const char *dir, + DBusError *error) +{ + DBusServer *server; + DBusString full_path; + DBusString filename; + + if (!_dbus_string_init (&full_path)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + if (!_dbus_string_init (&filename)) + { + _dbus_string_free (&full_path); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + if (!_dbus_string_append (&filename, "dbus-")) + { + _dbus_string_free (&full_path); + _dbus_string_free (&filename); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + if (!_dbus_generate_random_ascii (&filename, 10, error)) + { + _dbus_string_free (&full_path); + _dbus_string_free (&filename); + return NULL; + } + + if (!_dbus_string_append (&full_path, dir) || + !_dbus_concat_dir_and_file (&full_path, &filename)) + { + _dbus_string_free (&full_path); + _dbus_string_free (&filename); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + server = + _dbus_server_new_for_domain_socket (_dbus_string_get_const_data (&full_path), + FALSE, /* not abstract */ + error); + + _dbus_string_free (&full_path); + _dbus_string_free (&filename); + + return server; +} + +/** + * Tries to interpret the address entry for UNIX socket + * addresses. + * + * Sets error if the result is not OK. + * + * @param entry an address entry + * @param server_p location to store a new DBusServer, or #NULL on failure. + * @param error location to store rationale for failure on bad address + * @returns the outcome + * + */ +DBusServerListenResult +_dbus_server_listen_unix_socket (DBusAddressEntry *entry, + DBusServer **server_p, + DBusError *error) +{ + const char *method; + + *server_p = NULL; + + method = dbus_address_entry_get_method (entry); + + if (strcmp (method, "unix") == 0) + { + const char *path = dbus_address_entry_get_value (entry, "path"); + const char *dir = dbus_address_entry_get_value (entry, "dir"); + const char *tmpdir = dbus_address_entry_get_value (entry, "tmpdir"); + const char *abstract = dbus_address_entry_get_value (entry, "abstract"); + const char *runtime = dbus_address_entry_get_value (entry, "runtime"); + int mutually_exclusive_modes = 0; + + mutually_exclusive_modes = (path != NULL) + (tmpdir != NULL) + + (abstract != NULL) + (runtime != NULL) + (dir != NULL); + + if (mutually_exclusive_modes < 1) + { + _dbus_set_bad_address(error, "unix", + "path or tmpdir or abstract or runtime or dir", + NULL); + return DBUS_SERVER_LISTEN_BAD_ADDRESS; + } + + if (mutually_exclusive_modes > 1) + { + _dbus_set_bad_address(error, NULL, NULL, + "cannot specify two of \"path\", \"tmpdir\", \"abstract\", \"runtime\" and \"dir\" at the same time"); + return DBUS_SERVER_LISTEN_BAD_ADDRESS; + } + + if (runtime != NULL) + { + DBusString full_path; + DBusString filename; + const char *runtimedir; + + if (strcmp (runtime, "yes") != 0) + { + _dbus_set_bad_address(error, NULL, NULL, + "if given, the only value allowed for \"runtime\" is \"yes\""); + return DBUS_SERVER_LISTEN_BAD_ADDRESS; + } + + runtimedir = _dbus_getenv ("XDG_RUNTIME_DIR"); + + if (runtimedir == NULL) + { + dbus_set_error (error, + DBUS_ERROR_NOT_SUPPORTED, "\"XDG_RUNTIME_DIR\" is not set"); + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } + + _dbus_string_init_const (&filename, "bus"); + + if (!_dbus_string_init (&full_path)) + { + _DBUS_SET_OOM (error); + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } + + if (!_dbus_string_append (&full_path, runtimedir) || + !_dbus_concat_dir_and_file (&full_path, &filename)) + { + _dbus_string_free (&full_path); + _DBUS_SET_OOM (error); + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } + + /* We can safely use filesystem sockets in the runtime directory, + * and they are preferred because they can be bind-mounted between + * Linux containers. */ + *server_p = _dbus_server_new_for_domain_socket ( + _dbus_string_get_const_data (&full_path), + FALSE, error); + + _dbus_string_free (&full_path); + } + else if (tmpdir != NULL || dir != NULL) + { + /* tmpdir is now equivalent to dir. Previously it would try to + * use an abstract socket. */ + if (tmpdir != NULL) + dir = tmpdir; + + *server_p = _dbus_server_new_for_dir (dir, error); + } + else + { + if (path) + *server_p = _dbus_server_new_for_domain_socket (path, FALSE, error); + else + *server_p = _dbus_server_new_for_domain_socket (abstract, TRUE, error); + } + + if (*server_p != NULL) + { + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + return DBUS_SERVER_LISTEN_OK; + } + else + { + _DBUS_ASSERT_ERROR_IS_SET(error); + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } + } + else + { + /* If we don't handle the method, we return NULL with the + * error unset + */ + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + return DBUS_SERVER_LISTEN_NOT_HANDLED; + } +} + + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-server-socket.h b/src/3rdparty/libdbus/dbus/dbus-server-socket.h new file mode 100644 index 00000000..31b086e8 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-server-socket.h @@ -0,0 +1,61 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server-socket.h Server implementation for sockets + * + * Copyright (C) 2002, 2006 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_SERVER_SOCKET_H +#define DBUS_SERVER_SOCKET_H + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-server-protected.h> +#include <dbus/dbus-nonce.h> + +DBUS_BEGIN_DECLS + +DBusServer* _dbus_server_new_for_socket (DBusSocket *fds, + int n_fds, + const DBusString *address, + DBusNonceFile *noncefile, + DBusError *error); +DBusServer* _dbus_server_new_for_autolaunch (const DBusString *address, + DBusError *error); +DBUS_PRIVATE_EXPORT +DBusServer* _dbus_server_new_for_tcp_socket (const char *host, + const char *bind, + const char *port, + const char *family, + DBusError *error, + dbus_bool_t use_nonce); +DBusServerListenResult _dbus_server_listen_socket (DBusAddressEntry *entry, + DBusServer **server_p, + DBusError *error); + + +void _dbus_server_socket_own_filename (DBusServer *server, + char *filename); + +DBusServer* _dbus_server_new_for_domain_socket (const char *path, + dbus_bool_t abstract, + DBusError *error); +DBUS_END_DECLS + +#endif /* DBUS_SERVER_SOCKET_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-server-unix.c b/src/3rdparty/libdbus/dbus/dbus-server-unix.c new file mode 100644 index 00000000..dd1cb525 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-server-unix.c @@ -0,0 +1,145 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server-unix.c Server implementation for Unix network protocols. + * + * Copyright (C) 2002, 2003, 2004 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-server-socket.h" +#include "dbus-server-launchd.h" +#include "dbus-transport-unix.h" +#include "dbus-connection-internal.h" +#include "dbus-sysdeps-unix.h" +#include "dbus-string.h" + +/** + * @defgroup DBusServerUnix DBusServer implementations for UNIX + * @ingroup DBusInternals + * @brief Implementation details of DBusServer on UNIX + * + * @{ + */ + +/** + * Tries to interpret the address entry in a platform-specific + * way, creating a platform-specific server type if appropriate. + * Sets error if the result is not OK. + * + * @param entry an address entry + * @param server_p location to store a new DBusServer, or #NULL on failure. + * @param error location to store rationale for failure on bad address + * @returns the outcome + * + */ +DBusServerListenResult +_dbus_server_listen_platform_specific (DBusAddressEntry *entry, + DBusServer **server_p, + DBusError *error) +{ + const char *method; + + *server_p = NULL; + + method = dbus_address_entry_get_method (entry); + if (strcmp (method, "systemd") == 0) + { + int i, n; + DBusSocket *fds; + DBusString address; + + n = _dbus_listen_systemd_sockets (&fds, error); + if (n < 0) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } + + if (!_dbus_string_init (&address)) + goto systemd_oom; + + for (i = 0; i < n; i++) + { + if (i > 0) + { + if (!_dbus_string_append (&address, ";")) + goto systemd_oom; + } + if (!_dbus_append_address_from_socket (fds[i], &address, error)) + goto systemd_err; + } + + *server_p = _dbus_server_new_for_socket (fds, n, &address, NULL, error); + if (*server_p == NULL) + goto systemd_err; + + dbus_free (fds); + _dbus_string_free (&address); + + return DBUS_SERVER_LISTEN_OK; + + systemd_oom: + _DBUS_SET_OOM (error); + systemd_err: + for (i = 0; i < n; i++) + { + _dbus_close_socket (&fds[i], NULL); + } + dbus_free (fds); + _dbus_string_free (&address); + + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } +#ifdef DBUS_ENABLE_LAUNCHD + else if (strcmp (method, "launchd") == 0) + { + const char *launchd_env_var = dbus_address_entry_get_value (entry, "env"); + if (launchd_env_var == NULL) + { + _dbus_set_bad_address (error, "launchd", "env", NULL); + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } + *server_p = _dbus_server_new_for_launchd (launchd_env_var, error); + + if (*server_p != NULL) + { + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + return DBUS_SERVER_LISTEN_OK; + } + else + { + _DBUS_ASSERT_ERROR_IS_SET(error); + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } + } +#endif + else + { + /* If we don't handle the method, we return NULL with the + * error unset + */ + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + return DBUS_SERVER_LISTEN_NOT_HANDLED; + } +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-server-win.c b/src/3rdparty/libdbus/dbus/dbus-server-win.c new file mode 100644 index 00000000..af0b697e --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-server-win.c @@ -0,0 +1,97 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server-win.c Server implementation for WIN network protocols. + * + * Copyright (C) 2002, 2003, 2004 Red Hat Inc. + * Copyright (C) 2007 Ralf Habacker <ralf.habacker@freenet.de> + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-server-win.h" +#include "dbus-server-socket.h" + +/** + * @defgroup DBusServerWin DBusServer implementations for Windows + * @ingroup DBusInternals + * @brief Implementation details of DBusServer on Windows + * + * @{ + */ + +/** + * Tries to interpret the address entry in a platform-specific + * way, creating a platform-specific server type if appropriate. + * Sets error if the result is not OK. + * + * @param entry an address entry + * @param server_p location to store a new DBusServer, or #NULL on failure. + * @param error location to store rationale for failure on bad address + * @returns the outcome + * + */ +DBusServerListenResult +_dbus_server_listen_platform_specific (DBusAddressEntry *entry, + DBusServer **server_p, + DBusError *error) +{ + const char *method; + + *server_p = NULL; + + method = dbus_address_entry_get_method (entry); + + if (strcmp (method, "autolaunch") == 0) + { + const char *host = "localhost"; + const char *bind = "localhost"; + const char *port = "0"; + const char *family = "ipv4"; + const char *scope = dbus_address_entry_get_value (entry, "scope"); + + if (_dbus_daemon_is_session_bus_address_published (scope)) + return DBUS_SERVER_LISTEN_ADDRESS_ALREADY_USED; + + *server_p = _dbus_server_new_for_tcp_socket (host, bind, port, + family, error, FALSE); + if (*server_p) + { + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + (*server_p)->published_address = + _dbus_daemon_publish_session_bus_address ((*server_p)->address, scope); + return DBUS_SERVER_LISTEN_OK; + } + else + { + // make sure no handle is open + _dbus_daemon_unpublish_session_bus_address (); + _DBUS_ASSERT_ERROR_IS_SET(error); + return DBUS_SERVER_LISTEN_DID_NOT_CONNECT; + } + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + return DBUS_SERVER_LISTEN_NOT_HANDLED; + } +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-server-win.h b/src/3rdparty/libdbus/dbus/dbus-server-win.h new file mode 100644 index 00000000..e690404d --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-server-win.h @@ -0,0 +1,38 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server-win.h Server implementation for windows network protocols. + * + * Copyright (C) 2002 Red Hat Inc. + * Copyright (C) 2007 Ralf Habacker <ralf.habacker@freenet.de> + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_SERVER_WIN_H +#define DBUS_SERVER_WIN_H + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-server-protected.h> + +DBUS_BEGIN_DECLS + +/* add definitions here */ + +DBUS_END_DECLS + +#endif /* DBUS_SERVER_WIN_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-server.c b/src/3rdparty/libdbus/dbus/dbus-server.c new file mode 100644 index 00000000..27c31477 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-server.c @@ -0,0 +1,1197 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server.c DBusServer object + * + * Copyright (C) 2002, 2003, 2004, 2005 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-server.h" +#include "dbus-server-socket.h" +#include "dbus-string.h" +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +#include "dbus-server-debug-pipe.h" +#endif +#include "dbus-address.h" +#include "dbus-protocol.h" + +/** + * @defgroup DBusServer DBusServer + * @ingroup DBus + * @brief Server that listens for new connections. + * + * A DBusServer represents a server that other applications + * can connect to. Each connection from another application + * is represented by a #DBusConnection. + * + * @todo Thread safety hasn't been tested much for #DBusServer + * @todo Need notification to apps of disconnection, may matter for some transports + */ + +/** + * @defgroup DBusServerInternals DBusServer implementation details + * @ingroup DBusInternals + * @brief Implementation details of DBusServer + * + * @{ + */ + +#ifndef _dbus_server_trace_ref +void +_dbus_server_trace_ref (DBusServer *server, + int old_refcount, + int new_refcount, + const char *why) +{ + static int enabled = -1; + + _dbus_trace_ref ("DBusServer", server, old_refcount, new_refcount, why, + "DBUS_SERVER_TRACE", &enabled); +} +#endif + +/* this is a little fragile since it assumes the address doesn't + * already have a guid, but it shouldn't + */ +static char* +copy_address_with_guid_appended (const DBusString *address, + const DBusString *guid_hex) +{ + DBusString with_guid; + char *retval; + + if (!_dbus_string_init (&with_guid)) + return NULL; + + if (!_dbus_string_copy (address, 0, &with_guid, + _dbus_string_get_length (&with_guid)) || + !_dbus_string_append (&with_guid, ",guid=") || + !_dbus_string_copy (guid_hex, 0, + &with_guid, _dbus_string_get_length (&with_guid))) + { + _dbus_string_free (&with_guid); + return NULL; + } + + retval = NULL; + _dbus_string_steal_data (&with_guid, &retval); + + _dbus_string_free (&with_guid); + + return retval; /* may be NULL if steal_data failed */ +} + +/** + * Initializes the members of the DBusServer base class. + * Chained up to by subclass constructors. + * + * @param server the server. + * @param vtable the vtable for the subclass. + * @param address the server's address + * @param error location to store reason for failure + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_server_init_base (DBusServer *server, + const DBusServerVTable *vtable, + const DBusString *address, + DBusError *error) +{ + server->vtable = vtable; + +#ifdef DBUS_DISABLE_ASSERT + _dbus_atomic_inc (&server->refcount); +#else + { + dbus_int32_t old_refcount = _dbus_atomic_inc (&server->refcount); + + _dbus_assert (old_refcount == 0); + } +#endif + + server->address = NULL; + server->watches = NULL; + server->timeouts = NULL; + server->published_address = FALSE; + + if (!_dbus_string_init (&server->guid_hex)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_generate_uuid (&server->guid, error)) + goto failed; + + if (!_dbus_uuid_encode (&server->guid, &server->guid_hex)) + goto oom; + + server->address = copy_address_with_guid_appended (address, + &server->guid_hex); + if (server->address == NULL) + goto oom; + + _dbus_rmutex_new_at_location (&server->mutex); + if (server->mutex == NULL) + goto oom; + + server->watches = _dbus_watch_list_new (); + if (server->watches == NULL) + goto oom; + + server->timeouts = _dbus_timeout_list_new (); + if (server->timeouts == NULL) + goto oom; + + _dbus_data_slot_list_init (&server->slot_list); + + _dbus_verbose ("Initialized server on address %s\n", server->address); + + return TRUE; + + oom: + _DBUS_SET_OOM (error); + failed: + _dbus_rmutex_free_at_location (&server->mutex); + server->mutex = NULL; + if (server->watches) + { + _dbus_watch_list_free (server->watches); + server->watches = NULL; + } + if (server->timeouts) + { + _dbus_timeout_list_free (server->timeouts); + server->timeouts = NULL; + } + if (server->address) + { + dbus_free (server->address); + server->address = NULL; + } + _dbus_string_free (&server->guid_hex); + + return FALSE; +} + +/** + * Finalizes the members of the DBusServer base class. + * Chained up to by subclass finalizers. + * + * @param server the server. + */ +void +_dbus_server_finalize_base (DBusServer *server) +{ + /* We don't have the lock, but nobody should be accessing + * concurrently since they don't have a ref + */ +#ifndef DBUS_DISABLE_CHECKS + _dbus_assert (!server->have_server_lock); +#endif + _dbus_assert (server->disconnected); + + /* calls out to application code... */ + _dbus_data_slot_list_free (&server->slot_list); + + dbus_server_set_new_connection_function (server, NULL, NULL, NULL); + + _dbus_watch_list_free (server->watches); + _dbus_timeout_list_free (server->timeouts); + + _dbus_rmutex_free_at_location (&server->mutex); + + dbus_free (server->address); + + dbus_free_string_array (server->auth_mechanisms); + + _dbus_string_free (&server->guid_hex); +} + + +/** Function to be called in protected_change_watch() with refcount held */ +typedef dbus_bool_t (* DBusWatchAddFunction) (DBusWatchList *list, + DBusWatch *watch); +/** Function to be called in protected_change_watch() with refcount held */ +typedef void (* DBusWatchRemoveFunction) (DBusWatchList *list, + DBusWatch *watch); +/** Function to be called in protected_change_watch() with refcount held */ +typedef void (* DBusWatchToggleFunction) (DBusWatchList *list, + DBusWatch *watch, + dbus_bool_t enabled); + +static dbus_bool_t +protected_change_watch (DBusServer *server, + DBusWatch *watch, + DBusWatchAddFunction add_function, + DBusWatchRemoveFunction remove_function, + DBusWatchToggleFunction toggle_function, + dbus_bool_t enabled) +{ + DBusWatchList *watches; + dbus_bool_t retval; + + HAVE_LOCK_CHECK (server); + + /* This isn't really safe or reasonable; a better pattern is the "do + * everything, then drop lock and call out" one; but it has to be + * propagated up through all callers + */ + + watches = server->watches; + if (watches) + { + server->watches = NULL; + _dbus_server_ref_unlocked (server); + SERVER_UNLOCK (server); + + if (add_function) + retval = (* add_function) (watches, watch); + else if (remove_function) + { + retval = TRUE; + (* remove_function) (watches, watch); + } + else + { + retval = TRUE; + (* toggle_function) (watches, watch, enabled); + } + + SERVER_LOCK (server); + server->watches = watches; + _dbus_server_unref_unlocked (server); + + return retval; + } + else + return FALSE; +} + +/** + * Adds a watch for this server, chaining out to application-provided + * watch handlers. + * + * @param server the server. + * @param watch the watch to add. + */ +dbus_bool_t +_dbus_server_add_watch (DBusServer *server, + DBusWatch *watch) +{ + HAVE_LOCK_CHECK (server); + return protected_change_watch (server, watch, + _dbus_watch_list_add_watch, + NULL, NULL, FALSE); +} + +/** + * Removes a watch previously added with _dbus_server_remove_watch(). + * + * @param server the server. + * @param watch the watch to remove. + */ +void +_dbus_server_remove_watch (DBusServer *server, + DBusWatch *watch) +{ + HAVE_LOCK_CHECK (server); + protected_change_watch (server, watch, + NULL, + _dbus_watch_list_remove_watch, + NULL, FALSE); +} + +/** + * Toggles all watch and notifies app via server's + * DBusWatchToggledFunction if available. + * + * @param server the server. + * @param enabled whether to enable or disable + */ +void +_dbus_server_toggle_all_watches (DBusServer *server, + dbus_bool_t enabled) +{ + _dbus_watch_list_toggle_all_watches (server->watches, enabled); +} + +/** Function to be called in protected_change_timeout() with refcount held */ +typedef dbus_bool_t (* DBusTimeoutAddFunction) (DBusTimeoutList *list, + DBusTimeout *timeout); +/** Function to be called in protected_change_timeout() with refcount held */ +typedef void (* DBusTimeoutRemoveFunction) (DBusTimeoutList *list, + DBusTimeout *timeout); +/** Function to be called in protected_change_timeout() with refcount held */ +typedef void (* DBusTimeoutToggleFunction) (DBusTimeoutList *list, + DBusTimeout *timeout, + dbus_bool_t enabled); + + +static dbus_bool_t +protected_change_timeout (DBusServer *server, + DBusTimeout *timeout, + DBusTimeoutAddFunction add_function, + DBusTimeoutRemoveFunction remove_function, + DBusTimeoutToggleFunction toggle_function, + dbus_bool_t enabled) +{ + DBusTimeoutList *timeouts; + dbus_bool_t retval; + + HAVE_LOCK_CHECK (server); + + /* This isn't really safe or reasonable; a better pattern is the "do everything, then + * drop lock and call out" one; but it has to be propagated up through all callers + */ + + timeouts = server->timeouts; + if (timeouts) + { + server->timeouts = NULL; + _dbus_server_ref_unlocked (server); + SERVER_UNLOCK (server); + + if (add_function) + retval = (* add_function) (timeouts, timeout); + else if (remove_function) + { + retval = TRUE; + (* remove_function) (timeouts, timeout); + } + else + { + retval = TRUE; + (* toggle_function) (timeouts, timeout, enabled); + } + + SERVER_LOCK (server); + server->timeouts = timeouts; + _dbus_server_unref_unlocked (server); + + return retval; + } + else + return FALSE; +} + +/** + * Adds a timeout for this server, chaining out to + * application-provided timeout handlers. The timeout should be + * repeatedly handled with dbus_timeout_handle() at its given interval + * until it is removed. + * + * @param server the server. + * @param timeout the timeout to add. + */ +dbus_bool_t +_dbus_server_add_timeout (DBusServer *server, + DBusTimeout *timeout) +{ + return protected_change_timeout (server, timeout, + _dbus_timeout_list_add_timeout, + NULL, NULL, FALSE); +} + +/** + * Removes a timeout previously added with _dbus_server_add_timeout(). + * + * @param server the server. + * @param timeout the timeout to remove. + */ +void +_dbus_server_remove_timeout (DBusServer *server, + DBusTimeout *timeout) +{ + protected_change_timeout (server, timeout, + NULL, + _dbus_timeout_list_remove_timeout, + NULL, FALSE); +} + +/** + * Toggles a timeout and notifies app via server's + * DBusTimeoutToggledFunction if available. It's an error to call this + * function on a timeout that was not previously added. + * + * @param server the server. + * @param timeout the timeout to toggle. + * @param enabled whether to enable or disable + */ +void +_dbus_server_toggle_timeout (DBusServer *server, + DBusTimeout *timeout, + dbus_bool_t enabled) +{ + protected_change_timeout (server, timeout, + NULL, NULL, + _dbus_timeout_list_toggle_timeout, + enabled); +} + + +/** + * Like dbus_server_ref() but does not acquire the lock (must already be held) + * + * @param server the server. + */ +void +_dbus_server_ref_unlocked (DBusServer *server) +{ + dbus_int32_t old_refcount; + + _dbus_assert (server != NULL); + HAVE_LOCK_CHECK (server); + + old_refcount = _dbus_atomic_inc (&server->refcount); + _dbus_assert (old_refcount > 0); + _dbus_server_trace_ref (server, old_refcount, old_refcount + 1, + "ref_unlocked"); +} + +/** + * Like dbus_server_unref() but does not acquire the lock (must already be held) + * + * @param server the server. + */ +void +_dbus_server_unref_unlocked (DBusServer *server) +{ + dbus_int32_t old_refcount; + + /* Keep this in sync with dbus_server_unref */ + + _dbus_assert (server != NULL); + + HAVE_LOCK_CHECK (server); + + old_refcount = _dbus_atomic_dec (&server->refcount); + _dbus_assert (old_refcount > 0); + + _dbus_server_trace_ref (server, old_refcount, old_refcount - 1, + "unref_unlocked"); + + if (old_refcount == 1) + { + _dbus_assert (server->disconnected); + + SERVER_UNLOCK (server); + + _dbus_assert (server->vtable->finalize != NULL); + + (* server->vtable->finalize) (server); + } +} + +/** @} */ + +/** + * @addtogroup DBusServer + * + * @{ + */ + + +/** + * @typedef DBusServer + * + * An opaque object representing a server that listens for + * connections from other applications. Each time a connection + * is made, a new DBusConnection is created and made available + * via an application-provided DBusNewConnectionFunction. + * The DBusNewConnectionFunction is provided with + * dbus_server_set_new_connection_function(). + * + */ + +static const struct { + DBusServerListenResult (* func) (DBusAddressEntry *entry, + DBusServer **server_p, + DBusError *error); +} listen_funcs[] = { + { _dbus_server_listen_socket } + , { _dbus_server_listen_unix_socket } + , { _dbus_server_listen_platform_specific } +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + , { _dbus_server_listen_debug_pipe } +#endif +}; + +/** + * Listens for new connections on the given address. If there are + * multiple semicolon-separated address entries in the address, tries + * each one and listens on the first one that works. + * + * Returns #NULL and sets error if listening fails for any reason. + * Otherwise returns a new #DBusServer. + * dbus_server_set_new_connection_function(), + * dbus_server_set_watch_functions(), and + * dbus_server_set_timeout_functions() should be called immediately to + * render the server fully functional. + * + * To free the server, applications must call first + * dbus_server_disconnect() and then dbus_server_unref(). + * + * @param address the address of this server. + * @param error location to store reason for failure. + * @returns a new #DBusServer, or #NULL on failure. + * + */ +DBusServer* +dbus_server_listen (const char *address, + DBusError *error) +{ + DBusServer *server; + DBusAddressEntry **entries; + int len, i; + DBusError first_connect_error = DBUS_ERROR_INIT; + dbus_bool_t handled_once; + + _dbus_return_val_if_fail (address != NULL, NULL); + _dbus_return_val_if_error_is_set (error, NULL); + + if (!dbus_parse_address (address, &entries, &len, error)) + return NULL; + + server = NULL; + handled_once = FALSE; + + for (i = 0; i < len; i++) + { + int j; + + for (j = 0; j < (int) _DBUS_N_ELEMENTS (listen_funcs); ++j) + { + DBusServerListenResult result; + DBusError tmp_error = DBUS_ERROR_INIT; + + result = (* listen_funcs[j].func) (entries[i], + &server, + &tmp_error); + + if (result == DBUS_SERVER_LISTEN_OK) + { + _dbus_assert (server != NULL); + _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error); + handled_once = TRUE; + goto out; + } + else if (result == DBUS_SERVER_LISTEN_ADDRESS_ALREADY_USED) + { + _dbus_assert (server == NULL); + _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error); + dbus_set_error (error, + DBUS_ERROR_ADDRESS_IN_USE, + "Address '%s' already used", + dbus_address_entry_get_method (entries[0])); + handled_once = TRUE; + goto out; + } + else if (result == DBUS_SERVER_LISTEN_BAD_ADDRESS) + { + _dbus_assert (server == NULL); + _DBUS_ASSERT_ERROR_IS_SET (&tmp_error); + dbus_move_error (&tmp_error, error); + handled_once = TRUE; + goto out; + } + else if (result == DBUS_SERVER_LISTEN_NOT_HANDLED) + { + _dbus_assert (server == NULL); + _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error); + + /* keep trying addresses */ + } + else if (result == DBUS_SERVER_LISTEN_DID_NOT_CONNECT) + { + _dbus_assert (server == NULL); + _DBUS_ASSERT_ERROR_IS_SET (&tmp_error); + if (!dbus_error_is_set (&first_connect_error)) + dbus_move_error (&tmp_error, &first_connect_error); + else + dbus_error_free (&tmp_error); + + handled_once = TRUE; + + /* keep trying addresses */ + } + else + { + _dbus_assert_not_reached ("Unknown result in dbus_server_listen"); + } + } + + _dbus_assert (server == NULL); + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + } + + out: + + if (!handled_once) + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + if (len > 0) + dbus_set_error (error, + DBUS_ERROR_BAD_ADDRESS, + "Unknown address type '%s'", + dbus_address_entry_get_method (entries[0])); + else + dbus_set_error (error, + DBUS_ERROR_BAD_ADDRESS, + "Empty address '%s'", + address); + } + + dbus_address_entries_free (entries); + + if (server == NULL) + { + _dbus_assert (error == NULL || dbus_error_is_set (&first_connect_error) || + dbus_error_is_set (error)); + + if (error && dbus_error_is_set (error)) + { + /* already set the error */ + } + else + { + /* didn't set the error but either error should be + * NULL or first_connect_error should be set. + */ + _dbus_assert (error == NULL || dbus_error_is_set (&first_connect_error)); + dbus_move_error (&first_connect_error, error); + } + + _DBUS_ASSERT_ERROR_IS_CLEAR (&first_connect_error); /* be sure we freed it */ + _DBUS_ASSERT_ERROR_IS_SET (error); + + return NULL; + } + else + { + dbus_error_free (&first_connect_error); + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return server; + } +} + +/** + * Increments the reference count of a DBusServer. + * + * @param server the server. + * @returns the server + */ +DBusServer * +dbus_server_ref (DBusServer *server) +{ + dbus_int32_t old_refcount; + + _dbus_return_val_if_fail (server != NULL, NULL); + + old_refcount = _dbus_atomic_inc (&server->refcount); + +#ifndef DBUS_DISABLE_CHECKS + if (_DBUS_UNLIKELY (old_refcount <= 0)) + { + _dbus_atomic_dec (&server->refcount); + _dbus_warn_return_if_fail (_DBUS_FUNCTION_NAME, "old_refcount > 0", + __FILE__, __LINE__); + return NULL; + } +#endif + + _dbus_server_trace_ref (server, old_refcount, old_refcount + 1, "ref"); + + return server; +} + +/** + * Decrements the reference count of a DBusServer. Finalizes the + * server if the reference count reaches zero. + * + * The server must be disconnected before the refcount reaches zero. + * + * @param server the server. + */ +void +dbus_server_unref (DBusServer *server) +{ + dbus_int32_t old_refcount; + + /* keep this in sync with unref_unlocked */ + + _dbus_return_if_fail (server != NULL); + + old_refcount = _dbus_atomic_dec (&server->refcount); + +#ifndef DBUS_DISABLE_CHECKS + if (_DBUS_UNLIKELY (old_refcount <= 0)) + { + /* undo side-effect first + * please do not try to simplify the code here by using + * _dbus_atomic_get(), why we don't use it is + * because it issues another atomic operation even though + * DBUS_DISABLE_CHECKS defined. + * Bug: https://bugs.freedesktop.org/show_bug.cgi?id=68303 + */ + _dbus_atomic_inc (&server->refcount); + _dbus_warn_return_if_fail (_DBUS_FUNCTION_NAME, "old_refcount > 0", + __FILE__, __LINE__); + return; + } +#endif + + _dbus_server_trace_ref (server, old_refcount, old_refcount - 1, "unref"); + + if (old_refcount == 1) + { + /* lock not held! */ + _dbus_assert (server->disconnected); + + _dbus_assert (server->vtable->finalize != NULL); + + (* server->vtable->finalize) (server); + } +} + +void +_dbus_server_disconnect_unlocked (DBusServer *server) +{ + _dbus_assert (server->vtable->disconnect != NULL); + + if (!server->disconnected) + { + /* this has to be first so recursive calls to disconnect don't happen */ + server->disconnected = TRUE; + + (* server->vtable->disconnect) (server); + } +} + +/** + * Releases the server's address and stops listening for + * new clients. If called more than once, only the first + * call has an effect. Does not modify the server's + * reference count. + * + * @param server the server. + */ +void +dbus_server_disconnect (DBusServer *server) +{ + _dbus_return_if_fail (server != NULL); + + dbus_server_ref (server); + SERVER_LOCK (server); + + _dbus_server_disconnect_unlocked (server); + + SERVER_UNLOCK (server); + dbus_server_unref (server); +} + +/** + * Returns #TRUE if the server is still listening for new connections. + * + * @param server the server. + */ +dbus_bool_t +dbus_server_get_is_connected (DBusServer *server) +{ + dbus_bool_t retval; + + _dbus_return_val_if_fail (server != NULL, FALSE); + + SERVER_LOCK (server); + retval = !server->disconnected; + SERVER_UNLOCK (server); + + return retval; +} + +/** + * Returns the address of the server, as a newly-allocated + * string which must be freed by the caller. + * + * @param server the server + * @returns the address or #NULL if no memory + */ +char* +dbus_server_get_address (DBusServer *server) +{ + char *retval; + + _dbus_return_val_if_fail (server != NULL, NULL); + + SERVER_LOCK (server); + retval = _dbus_strdup (server->address); + SERVER_UNLOCK (server); + + return retval; +} + +/** + * Returns the unique ID of the server, as a newly-allocated + * string which must be freed by the caller. This ID is + * normally used by clients to tell when two #DBusConnection + * would be equivalent (because the server address passed + * to dbus_connection_open() will have the same guid in the + * two cases). dbus_connection_open() can re-use an existing + * connection with the same ID instead of opening a new + * connection. + * + * This is an ID unique to each #DBusServer. Remember that + * a #DBusServer represents only one mode of connecting, + * so e.g. a bus daemon can listen on multiple addresses + * which will mean it has multiple #DBusServer each with + * their own ID. + * + * The ID is not a UUID in the sense of RFC4122; the details + * are explained in the D-Bus specification. + * + * @param server the server + * @returns the id of the server or #NULL if no memory + */ +char* +dbus_server_get_id (DBusServer *server) +{ + char *retval; + + _dbus_return_val_if_fail (server != NULL, NULL); + + SERVER_LOCK (server); + retval = NULL; + _dbus_string_copy_data (&server->guid_hex, &retval); + SERVER_UNLOCK (server); + + return retval; +} + +/** + * Sets a function to be used for handling new connections. The given + * function is passed each new connection as the connection is + * created. If the new connection function increments the connection's + * reference count, the connection will stay alive. Otherwise, the + * connection will be unreferenced and closed. The new connection + * function may also close the connection itself, which is considered + * good form if the connection is not wanted. + * + * The connection here is private in the sense of + * dbus_connection_open_private(), so if the new connection function + * keeps a reference it must arrange for the connection to be closed. + * i.e. libdbus does not own this connection once the new connection + * function takes a reference. + * + * @param server the server. + * @param function a function to handle new connections. + * @param data data to pass to the new connection handler. + * @param free_data_function function to free the data. + */ +void +dbus_server_set_new_connection_function (DBusServer *server, + DBusNewConnectionFunction function, + void *data, + DBusFreeFunction free_data_function) +{ + DBusFreeFunction old_free_function; + void *old_data; + + _dbus_return_if_fail (server != NULL); + + SERVER_LOCK (server); + old_free_function = server->new_connection_free_data_function; + old_data = server->new_connection_data; + + server->new_connection_function = function; + server->new_connection_data = data; + server->new_connection_free_data_function = free_data_function; + SERVER_UNLOCK (server); + + if (old_free_function != NULL) + (* old_free_function) (old_data); +} + +/** + * Sets the watch functions for the server. These functions are + * responsible for making the application's main loop aware of file + * descriptors that need to be monitored for events. + * + * This function behaves exactly like dbus_connection_set_watch_functions(); + * see the documentation for that routine. + * + * @param server the server. + * @param add_function function to begin monitoring a new descriptor. + * @param remove_function function to stop monitoring a descriptor. + * @param toggled_function function to notify when the watch is enabled/disabled + * @param data data to pass to add_function and remove_function. + * @param free_data_function function to be called to free the data. + * @returns #FALSE on failure (no memory) + */ +dbus_bool_t +dbus_server_set_watch_functions (DBusServer *server, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + DBusWatchToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function) +{ + dbus_bool_t result; + DBusWatchList *watches; + + _dbus_return_val_if_fail (server != NULL, FALSE); + + SERVER_LOCK (server); + watches = server->watches; + server->watches = NULL; + if (watches) + { + SERVER_UNLOCK (server); + result = _dbus_watch_list_set_functions (watches, + add_function, + remove_function, + toggled_function, + data, + free_data_function); + SERVER_LOCK (server); + } + else + { + _dbus_warn_check_failed ("Re-entrant call to %s", _DBUS_FUNCTION_NAME); + result = FALSE; + } + server->watches = watches; + SERVER_UNLOCK (server); + + return result; +} + +/** + * Sets the timeout functions for the server. These functions are + * responsible for making the application's main loop aware of timeouts. + * + * This function behaves exactly like dbus_connection_set_timeout_functions(); + * see the documentation for that routine. + * + * @param server the server. + * @param add_function function to add a timeout. + * @param remove_function function to remove a timeout. + * @param toggled_function function to notify when the timeout is enabled/disabled + * @param data data to pass to add_function and remove_function. + * @param free_data_function function to be called to free the data. + * @returns #FALSE on failure (no memory) + */ +dbus_bool_t +dbus_server_set_timeout_functions (DBusServer *server, + DBusAddTimeoutFunction add_function, + DBusRemoveTimeoutFunction remove_function, + DBusTimeoutToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function) +{ + dbus_bool_t result; + DBusTimeoutList *timeouts; + + _dbus_return_val_if_fail (server != NULL, FALSE); + + SERVER_LOCK (server); + timeouts = server->timeouts; + server->timeouts = NULL; + if (timeouts) + { + SERVER_UNLOCK (server); + result = _dbus_timeout_list_set_functions (timeouts, + add_function, + remove_function, + toggled_function, + data, + free_data_function); + SERVER_LOCK (server); + } + else + { + _dbus_warn_check_failed ("Re-entrant call to %s", _DBUS_FUNCTION_NAME); + result = FALSE; + } + server->timeouts = timeouts; + SERVER_UNLOCK (server); + + return result; +} + +/** + * Sets the authentication mechanisms that this server offers to + * clients, as a #NULL-terminated array of mechanism names. This + * function only affects connections created <em>after</em> it is + * called. Pass #NULL instead of an array to use all available + * mechanisms (this is the default behavior). + * + * The D-Bus specification describes some of the supported mechanisms. + * + * @param server the server + * @param mechanisms #NULL-terminated array of mechanisms + * @returns #FALSE if no memory + */ +dbus_bool_t +dbus_server_set_auth_mechanisms (DBusServer *server, + const char **mechanisms) +{ + char **copy; + + _dbus_return_val_if_fail (server != NULL, FALSE); + + SERVER_LOCK (server); + + if (mechanisms != NULL) + { + copy = _dbus_dup_string_array (mechanisms); + if (copy == NULL) + { + SERVER_UNLOCK (server); + return FALSE; + } + } + else + copy = NULL; + + dbus_free_string_array (server->auth_mechanisms); + server->auth_mechanisms = copy; + + SERVER_UNLOCK (server); + + return TRUE; +} + +static DBusDataSlotAllocator slot_allocator = + _DBUS_DATA_SLOT_ALLOCATOR_INIT (_DBUS_LOCK_NAME (server_slots)); + +/** + * Allocates an integer ID to be used for storing application-specific + * data on any DBusServer. The allocated ID may then be used + * with dbus_server_set_data() and dbus_server_get_data(). + * The slot must be initialized with -1. If a nonnegative + * slot is passed in, the refcount is incremented on that + * slot, rather than creating a new slot. + * + * The allocated slot is global, i.e. all DBusServer objects will have + * a slot with the given integer ID reserved. + * + * @param slot_p address of global variable storing the slot ID + * @returns #FALSE on no memory + */ +dbus_bool_t +dbus_server_allocate_data_slot (dbus_int32_t *slot_p) +{ + return _dbus_data_slot_allocator_alloc (&slot_allocator, + slot_p); +} + +/** + * Deallocates a global ID for server data slots. + * dbus_server_get_data() and dbus_server_set_data() + * may no longer be used with this slot. + * Existing data stored on existing DBusServer objects + * will be freed when the server is finalized, + * but may not be retrieved (and may only be replaced + * if someone else reallocates the slot). + * + * @param slot_p address of the slot to deallocate + */ +void +dbus_server_free_data_slot (dbus_int32_t *slot_p) +{ + _dbus_return_if_fail (*slot_p >= 0); + + _dbus_data_slot_allocator_free (&slot_allocator, slot_p); +} + +/** + * Stores a pointer on a DBusServer, along + * with an optional function to be used for freeing + * the data when the data is set again, or when + * the server is finalized. The slot number + * must have been allocated with dbus_server_allocate_data_slot(). + * + * @param server the server + * @param slot the slot number + * @param data the data to store + * @param free_data_func finalizer function for the data + * @returns #TRUE if there was enough memory to store the data + */ +dbus_bool_t +dbus_server_set_data (DBusServer *server, + int slot, + void *data, + DBusFreeFunction free_data_func) +{ + DBusFreeFunction old_free_func; + void *old_data; + dbus_bool_t retval; + + _dbus_return_val_if_fail (server != NULL, FALSE); + + SERVER_LOCK (server); + + retval = _dbus_data_slot_list_set (&slot_allocator, + &server->slot_list, + slot, data, free_data_func, + &old_free_func, &old_data); + + + SERVER_UNLOCK (server); + + if (retval) + { + /* Do the actual free outside the server lock */ + if (old_free_func) + (* old_free_func) (old_data); + } + + return retval; +} + +/** + * Retrieves data previously set with dbus_server_set_data(). + * The slot must still be allocated (must not have been freed). + * + * @param server the server + * @param slot the slot to get data from + * @returns the data, or #NULL if not found + */ +void* +dbus_server_get_data (DBusServer *server, + int slot) +{ + void *res; + + _dbus_return_val_if_fail (server != NULL, NULL); + + SERVER_LOCK (server); + + res = _dbus_data_slot_list_get (&slot_allocator, + &server->slot_list, + slot); + + SERVER_UNLOCK (server); + + return res; +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-server.h b/src/3rdparty/libdbus/dbus/dbus-server.h new file mode 100644 index 00000000..1f8c5949 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-server.h @@ -0,0 +1,127 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-server.h DBusServer object + * + * Copyright (C) 2002, 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_SERVER_H +#define DBUS_SERVER_H + +#include <dbus/dbus-errors.h> +#include <dbus/dbus-macros.h> +#include <dbus/dbus-message.h> +#include <dbus/dbus-connection.h> +#include <dbus/dbus-protocol.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusServer + * @{ + */ + +typedef struct DBusServer DBusServer; + +/** Called when a new connection to the server is available. Must reference and save the new + * connection, or close the new connection. Set with dbus_server_set_new_connection_function(). + */ +typedef void (* DBusNewConnectionFunction) (DBusServer *server, + DBusConnection *new_connection, + void *data); + +DBUS_EXPORT +DBusServer* dbus_server_listen (const char *address, + DBusError *error); +DBUS_EXPORT +DBusServer* dbus_server_ref (DBusServer *server); +DBUS_EXPORT +void dbus_server_unref (DBusServer *server); +DBUS_EXPORT +void dbus_server_disconnect (DBusServer *server); +DBUS_EXPORT +dbus_bool_t dbus_server_get_is_connected (DBusServer *server); +DBUS_EXPORT +char* dbus_server_get_address (DBusServer *server); +DBUS_EXPORT +char* dbus_server_get_id (DBusServer *server); +DBUS_EXPORT +void dbus_server_set_new_connection_function (DBusServer *server, + DBusNewConnectionFunction function, + void *data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +dbus_bool_t dbus_server_set_watch_functions (DBusServer *server, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + DBusWatchToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +dbus_bool_t dbus_server_set_timeout_functions (DBusServer *server, + DBusAddTimeoutFunction add_function, + DBusRemoveTimeoutFunction remove_function, + DBusTimeoutToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function); +DBUS_EXPORT +dbus_bool_t dbus_server_set_auth_mechanisms (DBusServer *server, + const char **mechanisms); + +DBUS_EXPORT +dbus_bool_t dbus_server_allocate_data_slot (dbus_int32_t *slot_p); +DBUS_EXPORT +void dbus_server_free_data_slot (dbus_int32_t *slot_p); +DBUS_EXPORT +dbus_bool_t dbus_server_set_data (DBusServer *server, + int slot, + void *data, + DBusFreeFunction free_data_func); +DBUS_EXPORT +void* dbus_server_get_data (DBusServer *server, + int slot); + +/** + * Clear a variable or struct member that contains a #DBusServer. + * If it does not contain #NULL, the server that was previously + * there is unreferenced with dbus_server_unref(). + * + * This is very similar to dbus_clear_connection(): see that function + * for more details. + * + * @param pointer_to_server A pointer to a variable or struct member. + * pointer_to_server must not be #NULL, but *pointer_to_server + * may be #NULL. + */ +static inline void +dbus_clear_server (DBusServer **pointer_to_server) +{ + _dbus_clear_pointer_impl (DBusServer, pointer_to_server, dbus_server_unref); +} + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_SERVER_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-sha.c b/src/3rdparty/libdbus/dbus/dbus-sha.c new file mode 100644 index 00000000..effce82c --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-sha.c @@ -0,0 +1,513 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sha.c SHA-1 implementation + * + * Copyright (C) 2003 Red Hat Inc. + * Copyright (C) 1995 A. M. Kuchling + * SPDX-License-Identifier: (AFL-2.1 OR GPL-2.0-or-later) AND LicenseRef-pycrypto-orig + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-sha.h" +#include "dbus-marshal-basic.h" /* for byteswap routines */ +#include <string.h> + +/* The following comments have the history of where this code + * comes from. I actually copied it from GNet in GNOME CVS. + * - hp@redhat.com + */ + +/* + * sha.h : Implementation of the Secure Hash Algorithm + * + * Part of the Python Cryptography Toolkit, version 1.0.0 + * + * Copyright (C) 1995, A.M. Kuchling + * + * Distribute and use freely; there are no restrictions on further + * dissemination and usage except those imposed by the laws of your + * country of residence. + * + */ + +/* SHA: NIST's Secure Hash Algorithm */ + +/* Based on SHA code originally posted to sci.crypt by Peter Gutmann + in message <30ajo5$oe8@ccu2.auckland.ac.nz>. + Modified to test for endianness on creation of SHA objects by AMK. + Also, the original specification of SHA was found to have a weakness + by NSA/NIST. This code implements the fixed version of SHA. +*/ + +/* Here's the first paragraph of Peter Gutmann's posting: + +The following is my SHA (FIPS 180) code updated to allow use of the "fixed" +SHA, thanks to Jim Gillogly and an anonymous contributor for the information on +what's changed in the new version. The fix is a simple change which involves +adding a single rotate in the initial expansion function. It is unknown +whether this is an optimal solution to the problem which was discovered in the +SHA or whether it's simply a bandaid which fixes the problem with a minimum of +effort (for example the reengineering of a great many Capstone chips). +*/ + +/** + * @defgroup DBusSHA SHA implementation + * @ingroup DBusInternals + * @brief SHA-1 hash + * + * Types and functions related to computing SHA-1 hash. + */ + +/** + * @defgroup DBusSHAInternals SHA implementation details + * @ingroup DBusInternals + * @brief Internals of SHA implementation. + * + * The implementation of SHA-1 (see http://www.itl.nist.gov/fipspubs/fip180-1.htm). + * This SHA implementation was written by A.M. Kuchling + * + * @{ + */ + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +/* The SHA block size and message digest sizes, in bytes */ + +#define SHA_DATASIZE 64 +#define SHA_DIGESTSIZE 20 + +/* The SHA f()-functions. The f1 and f3 functions can be optimized to + save one boolean operation each - thanks to Rich Schroeppel, + rcs@cs.arizona.edu for discovering this */ + +/*#define f1(x,y,z) ( ( x & y ) | ( ~x & z ) ) // Rounds 0-19 */ +#define f1(x,y,z) ( z ^ ( x & ( y ^ z ) ) ) /* Rounds 0-19 */ +#define f2(x,y,z) ( x ^ y ^ z ) /* Rounds 20-39 */ +/*#define f3(x,y,z) ( ( x & y ) | ( x & z ) | ( y & z ) ) // Rounds 40-59 */ +#define f3(x,y,z) ( ( x & y ) | ( z & ( x | y ) ) ) /* Rounds 40-59 */ +#define f4(x,y,z) ( x ^ y ^ z ) /* Rounds 60-79 */ + +/* The SHA Mysterious Constants */ + +#define K1 0x5A827999L /* Rounds 0-19 */ +#define K2 0x6ED9EBA1L /* Rounds 20-39 */ +#define K3 0x8F1BBCDCL /* Rounds 40-59 */ +#define K4 0xCA62C1D6L /* Rounds 60-79 */ + +/* SHA initial values */ + +#define h0init 0x67452301L +#define h1init 0xEFCDAB89L +#define h2init 0x98BADCFEL +#define h3init 0x10325476L +#define h4init 0xC3D2E1F0L + +/* Note that it may be necessary to add parentheses to these macros if they + are to be called with expressions as arguments */ +/* 32-bit rotate left - kludged with shifts */ + +#define ROTL(n,X) ( ( ( X ) << n ) | ( ( X ) >> ( 32 - n ) ) ) + +/* The initial expanding function. The hash function is defined over an + 80-word expanded input array W, where the first 16 are copies of the input + data, and the remaining 64 are defined by + + W[ i ] = W[ i - 16 ] ^ W[ i - 14 ] ^ W[ i - 8 ] ^ W[ i - 3 ] + + This implementation generates these values on the fly in a circular + buffer - thanks to Colin Plumb, colin@nyx10.cs.du.edu for this + optimization. + + The updated SHA changes the expanding function by adding a rotate of 1 + bit. Thanks to Jim Gillogly, jim@rand.org, and an anonymous contributor + for this information */ + +#define expand(W,i) ( W[ i & 15 ] = ROTL( 1, ( W[ i & 15 ] ^ W[ (i - 14) & 15 ] ^ \ + W[ (i - 8) & 15 ] ^ W[ (i - 3) & 15 ] ) ) ) + + +/* The prototype SHA sub-round. The fundamental sub-round is: + + a' = e + ROTL( 5, a ) + f( b, c, d ) + k + data; + b' = a; + c' = ROTL( 30, b ); + d' = c; + e' = d; + + but this is implemented by unrolling the loop 5 times and renaming the + variables ( e, a, b, c, d ) = ( a', b', c', d', e' ) each iteration. + This code is then replicated 20 times for each of the 4 functions, using + the next 20 values from the W[] array each time */ + +#define subRound(a, b, c, d, e, f, k, data) \ + ( e += ROTL( 5, a ) + f( b, c, d ) + k + data, b = ROTL( 30, b ) ) + +#endif /* !DOXYGEN_SHOULD_SKIP_THIS */ + +/* Perform the SHA transformation. Note that this code, like MD5, seems to + break some optimizing compilers due to the complexity of the expressions + and the size of the basic block. It may be necessary to split it into + sections, e.g. based on the four subrounds + + Note that this corrupts the context->data area */ + +static void +SHATransform(dbus_uint32_t *digest, dbus_uint32_t *data) +{ + dbus_uint32_t A, B, C, D, E; /* Local vars */ + dbus_uint32_t eData[16]; /* Expanded data */ + + /* Set up first buffer and local data buffer */ + A = digest[0]; + B = digest[1]; + C = digest[2]; + D = digest[3]; + E = digest[4]; + memmove (eData, data, SHA_DATASIZE); + + /* Heavy mangling, in 4 sub-rounds of 20 interations each. */ + subRound (A, B, C, D, E, f1, K1, eData[0]); + subRound (E, A, B, C, D, f1, K1, eData[1]); + subRound (D, E, A, B, C, f1, K1, eData[2]); + subRound (C, D, E, A, B, f1, K1, eData[3]); + subRound (B, C, D, E, A, f1, K1, eData[4]); + subRound (A, B, C, D, E, f1, K1, eData[5]); + subRound (E, A, B, C, D, f1, K1, eData[6]); + subRound (D, E, A, B, C, f1, K1, eData[7]); + subRound (C, D, E, A, B, f1, K1, eData[8]); + subRound (B, C, D, E, A, f1, K1, eData[9]); + subRound (A, B, C, D, E, f1, K1, eData[10]); + subRound (E, A, B, C, D, f1, K1, eData[11]); + subRound (D, E, A, B, C, f1, K1, eData[12]); + subRound (C, D, E, A, B, f1, K1, eData[13]); + subRound (B, C, D, E, A, f1, K1, eData[14]); + subRound (A, B, C, D, E, f1, K1, eData[15]); + subRound (E, A, B, C, D, f1, K1, expand ( eData, 16) ); + subRound (D, E, A, B, C, f1, K1, expand ( eData, 17) ); + subRound (C, D, E, A, B, f1, K1, expand ( eData, 18) ); + subRound (B, C, D, E, A, f1, K1, expand ( eData, 19) ); + + subRound (A, B, C, D, E, f2, K2, expand ( eData, 20) ); + subRound (E, A, B, C, D, f2, K2, expand ( eData, 21) ); + subRound (D, E, A, B, C, f2, K2, expand ( eData, 22) ); + subRound (C, D, E, A, B, f2, K2, expand ( eData, 23) ); + subRound (B, C, D, E, A, f2, K2, expand ( eData, 24) ); + subRound (A, B, C, D, E, f2, K2, expand ( eData, 25) ); + subRound (E, A, B, C, D, f2, K2, expand ( eData, 26) ); + subRound (D, E, A, B, C, f2, K2, expand ( eData, 27) ); + subRound (C, D, E, A, B, f2, K2, expand ( eData, 28) ); + subRound (B, C, D, E, A, f2, K2, expand ( eData, 29) ); + subRound (A, B, C, D, E, f2, K2, expand ( eData, 30) ); + subRound (E, A, B, C, D, f2, K2, expand ( eData, 31) ); + subRound (D, E, A, B, C, f2, K2, expand ( eData, 32) ); + subRound (C, D, E, A, B, f2, K2, expand ( eData, 33) ); + subRound (B, C, D, E, A, f2, K2, expand ( eData, 34) ); + subRound (A, B, C, D, E, f2, K2, expand ( eData, 35) ); + subRound (E, A, B, C, D, f2, K2, expand ( eData, 36) ); + subRound (D, E, A, B, C, f2, K2, expand ( eData, 37) ); + subRound (C, D, E, A, B, f2, K2, expand ( eData, 38) ); + subRound (B, C, D, E, A, f2, K2, expand ( eData, 39) ); + + subRound (A, B, C, D, E, f3, K3, expand ( eData, 40) ); + subRound (E, A, B, C, D, f3, K3, expand ( eData, 41) ); + subRound (D, E, A, B, C, f3, K3, expand ( eData, 42) ); + subRound (C, D, E, A, B, f3, K3, expand ( eData, 43) ); + subRound (B, C, D, E, A, f3, K3, expand ( eData, 44) ); + subRound (A, B, C, D, E, f3, K3, expand ( eData, 45) ); + subRound (E, A, B, C, D, f3, K3, expand ( eData, 46) ); + subRound (D, E, A, B, C, f3, K3, expand ( eData, 47) ); + subRound (C, D, E, A, B, f3, K3, expand ( eData, 48) ); + subRound (B, C, D, E, A, f3, K3, expand ( eData, 49) ); + subRound (A, B, C, D, E, f3, K3, expand ( eData, 50) ); + subRound (E, A, B, C, D, f3, K3, expand ( eData, 51) ); + subRound (D, E, A, B, C, f3, K3, expand ( eData, 52) ); + subRound (C, D, E, A, B, f3, K3, expand ( eData, 53) ); + subRound (B, C, D, E, A, f3, K3, expand ( eData, 54) ); + subRound (A, B, C, D, E, f3, K3, expand ( eData, 55) ); + subRound (E, A, B, C, D, f3, K3, expand ( eData, 56) ); + subRound (D, E, A, B, C, f3, K3, expand ( eData, 57) ); + subRound (C, D, E, A, B, f3, K3, expand ( eData, 58) ); + subRound (B, C, D, E, A, f3, K3, expand ( eData, 59) ); + + subRound (A, B, C, D, E, f4, K4, expand ( eData, 60) ); + subRound (E, A, B, C, D, f4, K4, expand ( eData, 61) ); + subRound (D, E, A, B, C, f4, K4, expand ( eData, 62) ); + subRound (C, D, E, A, B, f4, K4, expand ( eData, 63) ); + subRound (B, C, D, E, A, f4, K4, expand ( eData, 64) ); + subRound (A, B, C, D, E, f4, K4, expand ( eData, 65) ); + subRound (E, A, B, C, D, f4, K4, expand ( eData, 66) ); + subRound (D, E, A, B, C, f4, K4, expand ( eData, 67) ); + subRound (C, D, E, A, B, f4, K4, expand ( eData, 68) ); + subRound (B, C, D, E, A, f4, K4, expand ( eData, 69) ); + subRound (A, B, C, D, E, f4, K4, expand ( eData, 70) ); + subRound (E, A, B, C, D, f4, K4, expand ( eData, 71) ); + subRound (D, E, A, B, C, f4, K4, expand ( eData, 72) ); + subRound (C, D, E, A, B, f4, K4, expand ( eData, 73) ); + subRound (B, C, D, E, A, f4, K4, expand ( eData, 74) ); + subRound (A, B, C, D, E, f4, K4, expand ( eData, 75) ); + subRound (E, A, B, C, D, f4, K4, expand ( eData, 76) ); + subRound (D, E, A, B, C, f4, K4, expand ( eData, 77) ); + subRound (C, D, E, A, B, f4, K4, expand ( eData, 78) ); + subRound (B, C, D, E, A, f4, K4, expand ( eData, 79) ); + + /* Build message digest */ + digest[0] += A; + digest[1] += B; + digest[2] += C; + digest[3] += D; + digest[4] += E; +} + +/* When run on a little-endian CPU we need to perform byte reversal on an + array of longwords. */ + +#ifdef WORDS_BIGENDIAN +#define swap_words(buffer, byte_count) +#else +static void +swap_words (dbus_uint32_t *buffer, + int byte_count) +{ + byte_count /= sizeof (dbus_uint32_t); + while (byte_count--) + { + *buffer = DBUS_UINT32_SWAP_LE_BE (*buffer); + ++buffer; + } +} +#endif + +static void +sha_init (DBusSHAContext *context) +{ + /* Set the h-vars to their initial values */ + context->digest[0] = h0init; + context->digest[1] = h1init; + context->digest[2] = h2init; + context->digest[3] = h3init; + context->digest[4] = h4init; + + /* Initialise bit count */ + context->count_lo = context->count_hi = 0; +} + +static void +sha_append (DBusSHAContext *context, + const unsigned char *buffer, + unsigned int count) +{ + dbus_uint32_t tmp; + unsigned int dataCount; + + /* Update bitcount */ + tmp = context->count_lo; + if (( context->count_lo = tmp + ( ( dbus_uint32_t) count << 3) ) < tmp) + context->count_hi++; /* Carry from low to high */ + context->count_hi += count >> 29; + + /* Get count of bytes already in data */ + dataCount = (int) (tmp >> 3) & 0x3F; + + /* Handle any leading odd-sized chunks */ + if (dataCount) + { + unsigned char *p = (unsigned char *) context->data + dataCount; + + dataCount = SHA_DATASIZE - dataCount; + if (count < dataCount) + { + memmove (p, buffer, count); + return; + } + memmove (p, buffer, dataCount); + swap_words (context->data, SHA_DATASIZE); + SHATransform (context->digest, context->data); + buffer += dataCount; + count -= dataCount; + } + + /* Process data in SHA_DATASIZE chunks */ + while (count >= SHA_DATASIZE) + { + memmove (context->data, buffer, SHA_DATASIZE); + swap_words (context->data, SHA_DATASIZE); + SHATransform (context->digest, context->data); + buffer += SHA_DATASIZE; + count -= SHA_DATASIZE; + } + + /* Handle any remaining bytes of data. */ + memmove (context->data, buffer, count); +} + + +/* Final wrapup - pad to SHA_DATASIZE-byte boundary with the bit pattern + 1 0* (64-bit count of bits processed, MSB-first) */ + +static void +sha_finish (DBusSHAContext *context, unsigned char digest[20]) +{ + int count; + unsigned char *data_p; + + /* Compute number of bytes mod 64 */ + count = (int) context->count_lo; + count = (count >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + data_p = (unsigned char *) context->data + count; + *data_p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = SHA_DATASIZE - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) + { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset (data_p, 0, count); + swap_words (context->data, SHA_DATASIZE); + SHATransform (context->digest, context->data); + + /* Now fill the next block with 56 bytes */ + memset (context->data, 0, SHA_DATASIZE - 8); + } + else + /* Pad block to 56 bytes */ + memset (data_p, 0, count - 8); + + /* Append length in bits and transform */ + context->data[14] = context->count_hi; + context->data[15] = context->count_lo; + + swap_words (context->data, SHA_DATASIZE - 8); + SHATransform (context->digest, context->data); + swap_words (context->digest, SHA_DIGESTSIZE); + memmove (digest, context->digest, SHA_DIGESTSIZE); +} + +/** @} */ /* End of internals */ + +/** + * @addtogroup DBusSHA + * + * @{ + */ + +/** + * Initializes the SHA context. + * + * @param context an uninitialized context, typically on the stack. + */ +void +_dbus_sha_init (DBusSHAContext *context) +{ + sha_init (context); +} + +/** + * Feeds more data into an existing shasum computation. + * + * @param context the SHA context + * @param data the additional data to hash + */ +void +_dbus_sha_update (DBusSHAContext *context, + const DBusString *data) +{ + unsigned int inputLen; + const unsigned char *input; + + input = (const unsigned char*) _dbus_string_get_const_data (data); + inputLen = _dbus_string_get_length (data); + + sha_append (context, input, inputLen); +} + +/** + * SHA finalization. Ends an SHA message-digest operation, writing the + * the message digest and zeroing the context. The results are + * returned as a raw 20-byte digest, not as the ascii-hex-digits + * string form of the digest. + * + * @param context the SHA context + * @param results string to append the 20-byte SHA digest to + * @returns #FALSE if not enough memory to append the digest + * + */ +dbus_bool_t +_dbus_sha_final (DBusSHAContext *context, + DBusString *results) +{ + unsigned char digest[20]; + + sha_finish (context, digest); + + if (!_dbus_string_append_len (results, (const char *) digest, 20)) + return FALSE; + + /* some kind of security paranoia, though it seems pointless + * to me given the nonzeroed stuff flying around + */ + _DBUS_ZERO(*context); + + return TRUE; +} + +/** + * Computes the ASCII hex-encoded shasum of the given data and + * appends it to the output string. + * + * @param data input data to be hashed + * @param ascii_output string to append ASCII shasum to + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_sha_compute (const DBusString *data, + DBusString *ascii_output) +{ + DBusSHAContext context; + DBusString digest; + + _dbus_sha_init (&context); + + _dbus_sha_update (&context, data); + + if (!_dbus_string_init (&digest)) + return FALSE; + + if (!_dbus_sha_final (&context, &digest)) + goto error; + + if (!_dbus_string_hex_encode (&digest, 0, ascii_output, + _dbus_string_get_length (ascii_output))) + goto error; + + _dbus_string_free (&digest); + + return TRUE; + + error: + _dbus_string_free (&digest); + return FALSE; +} + +/** @} */ /* end of exported functions */ diff --git a/src/3rdparty/libdbus/dbus/dbus-sha.h b/src/3rdparty/libdbus/dbus/dbus-sha.h new file mode 100644 index 00000000..e0677844 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-sha.h @@ -0,0 +1,58 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sha.h SHA-1 implementation + * + * Copyright (C) 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_SHA_H +#define DBUS_SHA_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-errors.h> +#include <dbus/dbus-string.h> + +DBUS_BEGIN_DECLS + +typedef struct DBusSHAContext DBusSHAContext; + +/** + * Struct storing state of the SHA algorithm + */ +struct DBusSHAContext +{ + dbus_uint32_t digest[5]; /**< Message digest */ + dbus_uint32_t count_lo; /**< 64-bit bit count */ + dbus_uint32_t count_hi; /**< No clue */ + dbus_uint32_t data[16]; /**< SHA data buffer */ +}; + +void _dbus_sha_init (DBusSHAContext *context); +void _dbus_sha_update (DBusSHAContext *context, + const DBusString *data); +dbus_bool_t _dbus_sha_final (DBusSHAContext *context, + DBusString *results); +DBUS_EMBEDDED_TESTS_EXPORT +dbus_bool_t _dbus_sha_compute (const DBusString *data, + DBusString *ascii_output); + +DBUS_END_DECLS + +#endif /* DBUS_SHA_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-shared.h b/src/3rdparty/libdbus/dbus/dbus-shared.h new file mode 100644 index 00000000..87c0bd84 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-shared.h @@ -0,0 +1,138 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-shared.h Stuff used by both dbus/dbus.h low-level and C/C++ binding APIs + * + * Copyright (C) 2004 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_SHARED_H +#define DBUS_SHARED_H + +/* Don't include anything in here from anywhere else. It's + * intended for use by any random library. + */ + +#ifdef __cplusplus +extern "C" { +#if 0 +} /* avoids confusing emacs indentation */ +#endif +#endif + +/* Normally docs are in .c files, but there isn't a .c file for this. */ +/** + * @defgroup DBusShared Shared constants + * @ingroup DBus + * + * @brief Shared header included by both libdbus and C/C++ bindings such as the GLib bindings. + * + * Usually a C/C++ binding such as the GLib or Qt binding won't want to include dbus.h in its + * public headers. However, a few constants and macros may be useful to include; those are + * found here and in dbus-protocol.h + * + * @{ + */ + + +/** + * Well-known bus types. See dbus_bus_get(). + */ +typedef enum +{ + DBUS_BUS_SESSION, /**< The login session bus */ + DBUS_BUS_SYSTEM, /**< The systemwide bus */ + DBUS_BUS_STARTER /**< The bus that started us, if any */ +} DBusBusType; + +/** + * Results that a message handler can return. + */ +typedef enum +{ + DBUS_HANDLER_RESULT_HANDLED, /**< Message has had its effect - no need to run more handlers. */ + DBUS_HANDLER_RESULT_NOT_YET_HANDLED, /**< Message has not had any effect - see if other handlers want it. */ + DBUS_HANDLER_RESULT_NEED_MEMORY /**< Need more memory in order to return #DBUS_HANDLER_RESULT_HANDLED or #DBUS_HANDLER_RESULT_NOT_YET_HANDLED. Please try again later with more memory. */ +} DBusHandlerResult; + +/* Bus names */ + +/** The bus name used to talk to the bus itself. */ +#define DBUS_SERVICE_DBUS "org.freedesktop.DBus" + +/* Paths */ +/** The object path used to talk to the bus itself. */ +#define DBUS_PATH_DBUS "/org/freedesktop/DBus" +/** The object path used in local/in-process-generated messages. */ +#define DBUS_PATH_LOCAL "/org/freedesktop/DBus/Local" + +/* Interfaces, these #define don't do much other than + * catch typos at compile time + */ +/** The interface exported by the object with #DBUS_SERVICE_DBUS and #DBUS_PATH_DBUS */ +#define DBUS_INTERFACE_DBUS "org.freedesktop.DBus" +/** The monitoring interface exported by the dbus-daemon */ +#define DBUS_INTERFACE_MONITORING "org.freedesktop.DBus.Monitoring" + +/** The verbose interface exported by the dbus-daemon */ +#define DBUS_INTERFACE_VERBOSE "org.freedesktop.DBus.Verbose" +/** The interface supported by introspectable objects */ +#define DBUS_INTERFACE_INTROSPECTABLE "org.freedesktop.DBus.Introspectable" +/** The interface supported by objects with properties */ +#define DBUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" +/** The interface supported by most dbus peers */ +#define DBUS_INTERFACE_PEER "org.freedesktop.DBus.Peer" + +/** This is a special interface whose methods can only be invoked + * by the local implementation (messages from remote apps aren't + * allowed to specify this interface). + */ +#define DBUS_INTERFACE_LOCAL "org.freedesktop.DBus.Local" + +/* Owner flags */ +#define DBUS_NAME_FLAG_ALLOW_REPLACEMENT 0x1 /**< Allow another service to become the primary owner if requested */ +#define DBUS_NAME_FLAG_REPLACE_EXISTING 0x2 /**< Request to replace the current primary owner */ +#define DBUS_NAME_FLAG_DO_NOT_QUEUE 0x4 /**< If we can not become the primary owner do not place us in the queue */ + +/* Replies to request for a name */ +#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 /**< Service has become the primary owner of the requested name */ +#define DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2 /**< Service could not become the primary owner and has been placed in the queue */ +#define DBUS_REQUEST_NAME_REPLY_EXISTS 3 /**< Service is already in the queue */ +#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4 /**< Service is already the primary owner */ + +/* Replies to releasing a name */ +#define DBUS_RELEASE_NAME_REPLY_RELEASED 1 /**< Service was released from the given name */ +#define DBUS_RELEASE_NAME_REPLY_NON_EXISTENT 2 /**< The given name does not exist on the bus */ +#define DBUS_RELEASE_NAME_REPLY_NOT_OWNER 3 /**< Service is not an owner of the given name */ + +/* Replies to service starts */ +#define DBUS_START_REPLY_SUCCESS 1 /**< Service was auto started */ +#define DBUS_START_REPLY_ALREADY_RUNNING 2 /**< Service was already running */ + +/** @} */ + +#ifdef __cplusplus +#if 0 +{ /* avoids confusing emacs indentation */ +#endif +} +#endif + +#endif /* DBUS_SHARED_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-signature.c b/src/3rdparty/libdbus/dbus/dbus-signature.c new file mode 100644 index 00000000..85d4fc49 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-signature.c @@ -0,0 +1,417 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-signature.c Routines for reading recursive type signatures + * + * Copyright (C) 2005 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#include "dbus-signature.h" +#include "dbus-marshal-recursive.h" +#include "dbus-marshal-basic.h" +#include "dbus-internals.h" +#include "dbus-test.h" +#include <dbus/dbus-test-tap.h> + +/** + * Implementation details of #DBusSignatureIter, all fields are private + */ +typedef struct +{ + const char *pos; /**< current position in the signature string */ + unsigned int finished : 1; /**< true if we are at the end iter */ + unsigned int in_array : 1; /**< true if we are a subiterator pointing to an array's element type */ +} DBusSignatureRealIter; + +_DBUS_STATIC_ASSERT (sizeof (DBusSignatureIter) >= sizeof (DBusSignatureRealIter)); + +/** macro that checks whether a typecode is a container type */ +#define TYPE_IS_CONTAINER(typecode) \ + ((typecode) == DBUS_TYPE_STRUCT || \ + (typecode) == DBUS_TYPE_DICT_ENTRY || \ + (typecode) == DBUS_TYPE_VARIANT || \ + (typecode) == DBUS_TYPE_ARRAY) + + +/** + * @defgroup DBusSignature Type signature parsing + * @ingroup DBus + * @brief Parsing D-Bus type signatures + * @{ + */ + +/** + * Initializes a #DBusSignatureIter for reading a type signature. This + * function is not safe to use on invalid signatures; be sure to + * validate potentially invalid signatures with dbus_signature_validate + * before using this function. + * + * @param iter pointer to an iterator to initialize + * @param signature the type signature + */ +void +dbus_signature_iter_init (DBusSignatureIter *iter, + const char *signature) +{ + DBusSignatureRealIter *real_iter = (DBusSignatureRealIter *) iter; + + real_iter->pos = signature; + real_iter->finished = FALSE; + real_iter->in_array = FALSE; +} + +/** + * Returns the current type pointed to by the iterator. + * If the iterator is pointing at a type code such as 's', then + * it will be returned directly. + * + * However, when the parser encounters a container type start + * character such as '(' for a structure, the corresponding type for + * the container will be returned, e.g. DBUS_TYPE_STRUCT, not '('. + * In this case, you should initialize a sub-iterator with + * dbus_signature_iter_recurse() to parse the container type. + * + * @param iter pointer to an iterator + * @returns current type (e.g. #DBUS_TYPE_STRING, #DBUS_TYPE_ARRAY) + */ +int +dbus_signature_iter_get_current_type (const DBusSignatureIter *iter) +{ + DBusSignatureRealIter *real_iter = (DBusSignatureRealIter *) iter; + + return _dbus_first_type_in_signature_c_str (real_iter->pos, 0); +} + +/** + * Returns the signature of the single complete type starting at the + * given iterator. + * + * For example, if the iterator is pointing at the start of "(ii)ii" + * (which is "a struct of two ints, followed by an int, followed by an + * int"), then "(ii)" would be returned. If the iterator is pointing at + * one of the "i" then just that "i" would be returned. + * + * @param iter pointer to an iterator + * @returns current signature; or #NULL if no memory. Should be freed with dbus_free() + */ +char * +dbus_signature_iter_get_signature (const DBusSignatureIter *iter) +{ + DBusSignatureRealIter *real_iter = (DBusSignatureRealIter *) iter; + DBusString str; + char *ret; + int pos; + + if (!_dbus_string_init (&str)) + return NULL; + + pos = 0; + _dbus_type_signature_next (real_iter->pos, &pos); + + if (!_dbus_string_append_len (&str, real_iter->pos, pos)) + return NULL; + if (!_dbus_string_steal_data (&str, &ret)) + ret = NULL; + _dbus_string_free (&str); + + return ret; +} + +/** + * Convenience function for returning the element type of an array; + * This function allows you to avoid initializing a sub-iterator and + * getting its current type. + * + * Undefined behavior results if you invoke this function when the + * current type of the iterator is not #DBUS_TYPE_ARRAY. + * + * @param iter pointer to an iterator + * @returns current array element type + */ +int +dbus_signature_iter_get_element_type (const DBusSignatureIter *iter) +{ + DBusSignatureRealIter *real_iter = (DBusSignatureRealIter *) iter; + + _dbus_return_val_if_fail (dbus_signature_iter_get_current_type (iter) == DBUS_TYPE_ARRAY, DBUS_TYPE_INVALID); + + return _dbus_first_type_in_signature_c_str (real_iter->pos, 1); +} + +/** + * Skip to the next value on this "level". e.g. the next field in a + * struct, the next value in an array. Returns #FALSE at the end of the + * current container. + * + * @param iter the iterator + * @returns FALSE if nothing more to read at or below this level + */ +dbus_bool_t +dbus_signature_iter_next (DBusSignatureIter *iter) +{ + DBusSignatureRealIter *real_iter = (DBusSignatureRealIter *) iter; + + if (real_iter->finished) + return FALSE; + else + { + int pos; + + if (real_iter->in_array) + { + real_iter->finished = TRUE; + return FALSE; + } + + pos = 0; + _dbus_type_signature_next (real_iter->pos, &pos); + real_iter->pos += pos; + + if (*real_iter->pos == DBUS_STRUCT_END_CHAR + || *real_iter->pos == DBUS_DICT_ENTRY_END_CHAR) + { + real_iter->finished = TRUE; + return FALSE; + } + + return *real_iter->pos != DBUS_TYPE_INVALID; + } +} + +/** + * Initialize a new iterator pointing to the first type in the current + * container. + * + * The results are undefined when calling this if the current type is + * a non-container (i.e. if dbus_type_is_container() returns #FALSE + * for the result of dbus_signature_iter_get_current_type()). + * + * @param iter the current interator + * @param subiter an iterator to initialize pointing to the first child + */ +void +dbus_signature_iter_recurse (const DBusSignatureIter *iter, + DBusSignatureIter *subiter) +{ + DBusSignatureRealIter *real_iter = (DBusSignatureRealIter *) iter; + DBusSignatureRealIter *real_sub_iter = (DBusSignatureRealIter *) subiter; + + _dbus_return_if_fail (dbus_type_is_container (dbus_signature_iter_get_current_type (iter))); + + *real_sub_iter = *real_iter; + real_sub_iter->in_array = FALSE; + real_sub_iter->pos++; + + if (dbus_signature_iter_get_current_type (iter) == DBUS_TYPE_ARRAY) + real_sub_iter->in_array = TRUE; +} + +/** + * Check a type signature for validity. Remember that #NULL can always + * be passed instead of a DBusError*, if you don't care about having + * an error name and message. + * + * @param signature a potentially invalid type signature + * @param error error return + * @returns #TRUE if signature is valid or #FALSE if an error is set + */ +dbus_bool_t +dbus_signature_validate (const char *signature, + DBusError *error) + +{ + DBusString str; + DBusValidity reason; + + _dbus_string_init_const (&str, signature); + reason = _dbus_validate_signature_with_reason (&str, 0, _dbus_string_get_length (&str)); + + if (reason == DBUS_VALID) + return TRUE; + else + { + dbus_set_error (error, DBUS_ERROR_INVALID_SIGNATURE, "%s", + _dbus_validity_to_error_message (reason)); + return FALSE; + } +} + +/** + * Check that a type signature is both valid and contains exactly one + * complete type. "One complete type" means a single basic type, + * array, struct, or dictionary, though the struct or array may be + * arbitrarily recursive and complex. More than one complete type + * would mean for example "ii" or two integers in sequence. + * + * @param signature a potentially invalid type signature + * @param error error return + * @returns #TRUE if signature is valid and has exactly one complete type + */ +dbus_bool_t +dbus_signature_validate_single (const char *signature, + DBusError *error) +{ + DBusSignatureIter iter; + + if (!dbus_signature_validate (signature, error)) + return FALSE; + + dbus_signature_iter_init (&iter, signature); + if (dbus_signature_iter_get_current_type (&iter) == DBUS_TYPE_INVALID) + goto lose; + if (!dbus_signature_iter_next (&iter)) + return TRUE; + lose: + dbus_set_error (error, DBUS_ERROR_INVALID_SIGNATURE, "Exactly one complete type required in signature"); + return FALSE; +} + +/** + * A "container type" can contain basic types, or nested + * container types. #DBUS_TYPE_INVALID is not a container type. + * + * It is an error to pass an invalid type-code, other than DBUS_TYPE_INVALID, + * to this function. The valid type-codes are defined by dbus-protocol.h + * and can be checked with dbus_type_is_valid(). + * + * @param typecode either a valid type-code or DBUS_TYPE_INVALID + * @returns #TRUE if type is a container + */ +dbus_bool_t +dbus_type_is_container (int typecode) +{ + /* only reasonable (non-line-noise) typecodes are allowed */ + _dbus_return_val_if_fail (dbus_type_is_valid (typecode) || typecode == DBUS_TYPE_INVALID, + FALSE); + return TYPE_IS_CONTAINER (typecode); +} + +/** + * A "basic type" is a somewhat arbitrary concept, but the intent is + * to include those types that are fully-specified by a single + * typecode, with no additional type information or nested values. So + * all numbers and strings are basic types and structs, arrays, and + * variants are not basic types. #DBUS_TYPE_INVALID is not a basic + * type. + * + * It is an error to pass an invalid type-code, other than DBUS_TYPE_INVALID, + * to this function. The valid type-codes are defined by dbus-protocol.h + * and can be checked with dbus_type_is_valid(). + * + * @param typecode either a valid type-code or DBUS_TYPE_INVALID + * @returns #TRUE if type is basic + */ +dbus_bool_t +dbus_type_is_basic (int typecode) +{ + /* only reasonable (non-line-noise) typecodes are allowed */ + _dbus_return_val_if_fail (dbus_type_is_valid (typecode) || typecode == DBUS_TYPE_INVALID, + FALSE); + + /* everything that isn't invalid or a container */ + return !(typecode == DBUS_TYPE_INVALID || TYPE_IS_CONTAINER (typecode)); +} + +/** + * Tells you whether values of this type can change length if you set + * them to some other value. For this purpose, you assume that the + * first byte of the old and new value would be in the same location, + * so alignment padding is not a factor. + * + * This function is useful to determine whether + * dbus_message_iter_get_fixed_array() may be used. + * + * Some structs are fixed-size (if they contain only fixed-size types) + * but struct is not considered a fixed type for purposes of this + * function. + * + * It is an error to pass an invalid type-code, other than DBUS_TYPE_INVALID, + * to this function. The valid type-codes are defined by dbus-protocol.h + * and can be checked with dbus_type_is_valid(). + * + * @param typecode either a valid type-code or DBUS_TYPE_INVALID + * @returns #FALSE if the type can occupy different lengths + */ +dbus_bool_t +dbus_type_is_fixed (int typecode) +{ + /* only reasonable (non-line-noise) typecodes are allowed */ + _dbus_return_val_if_fail (dbus_type_is_valid (typecode) || typecode == DBUS_TYPE_INVALID, + FALSE); + + switch (typecode) + { + case DBUS_TYPE_BYTE: + case DBUS_TYPE_BOOLEAN: + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + case DBUS_TYPE_UNIX_FD: + return TRUE; + default: + return FALSE; + } +} + +/** + * Return #TRUE if the argument is a valid typecode. + * #DBUS_TYPE_INVALID surprisingly enough is not considered valid, and + * random unknown bytes aren't either. This function is safe with + * untrusted data. + * + * @param typecode a potential type-code + * @returns #TRUE if valid + */ +dbus_bool_t +dbus_type_is_valid (int typecode) +{ + switch (typecode) + { + case DBUS_TYPE_BYTE: + case DBUS_TYPE_BOOLEAN: + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + case DBUS_TYPE_SIGNATURE: + case DBUS_TYPE_ARRAY: + case DBUS_TYPE_STRUCT: + case DBUS_TYPE_DICT_ENTRY: + case DBUS_TYPE_VARIANT: + case DBUS_TYPE_UNIX_FD: + return TRUE; + + default: + return FALSE; + } +} + +/** @} */ /* end of DBusSignature group */ diff --git a/src/3rdparty/libdbus/dbus/dbus-signature.h b/src/3rdparty/libdbus/dbus/dbus-signature.h new file mode 100644 index 00000000..a8a0486b --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-signature.h @@ -0,0 +1,97 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-signatures.h utility functions for D-Bus types + * + * Copyright (C) 2005 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_SIGNATURES_H +#define DBUS_SIGNATURES_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-errors.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusSignature + * @{ + */ + +/** + * DBusSignatureIter struct; contains no public fields + */ +typedef struct +{ + void *dummy1; /**< Don't use this */ + void *dummy2; /**< Don't use this */ + dbus_uint32_t dummy8; /**< Don't use this */ + int dummy12; /**< Don't use this */ + int dummy17; /**< Don't use this */ +} DBusSignatureIter; + +DBUS_EXPORT +void dbus_signature_iter_init (DBusSignatureIter *iter, + const char *signature); + +DBUS_EXPORT +int dbus_signature_iter_get_current_type (const DBusSignatureIter *iter); + +DBUS_EXPORT +char * dbus_signature_iter_get_signature (const DBusSignatureIter *iter); + +DBUS_EXPORT +int dbus_signature_iter_get_element_type (const DBusSignatureIter *iter); + +DBUS_EXPORT +dbus_bool_t dbus_signature_iter_next (DBusSignatureIter *iter); + +DBUS_EXPORT +void dbus_signature_iter_recurse (const DBusSignatureIter *iter, + DBusSignatureIter *subiter); + +DBUS_EXPORT +dbus_bool_t dbus_signature_validate (const char *signature, + DBusError *error); + +DBUS_EXPORT +dbus_bool_t dbus_signature_validate_single (const char *signature, + DBusError *error); + +DBUS_EXPORT +dbus_bool_t dbus_type_is_valid (int typecode); + +DBUS_EXPORT +dbus_bool_t dbus_type_is_basic (int typecode); +DBUS_EXPORT +dbus_bool_t dbus_type_is_container (int typecode); +DBUS_EXPORT +dbus_bool_t dbus_type_is_fixed (int typecode); + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_SIGNATURE_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-sockets-win.h b/src/3rdparty/libdbus/dbus/dbus-sockets-win.h new file mode 100644 index 00000000..0924179c --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-sockets-win.h @@ -0,0 +1,55 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sockets.h Wrappers around socket features (internal to D-BUS implementation) + * + * Copyright (C) 2005 Novell, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_SOCKETS_H +#define DBUS_SOCKETS_H + +#if defined(DBUS_WIN) || defined(DBUS_WINCE) + + + +#ifndef STRICT +#define STRICT +#include <winsock2.h> +#undef STRICT +#endif +#include <winsock2.h> + +#undef interface + +#if HAVE_ERRNO_H +#include <errno.h> +#endif + +#define DBUS_SOCKET_API_RETURNS_ERROR(n) ((n) == SOCKET_ERROR) +#define DBUS_SOCKET_SET_ERRNO() (_dbus_win_set_errno (WSAGetLastError())) + +#else + +#error "dbus-sockets-win.h should not be included on non-Windows" + +#endif /* !Win32 */ + +#endif /* DBUS_SOCKETS_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-string-private.h b/src/3rdparty/libdbus/dbus/dbus-string-private.h new file mode 100644 index 00000000..24e1e610 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-string-private.h @@ -0,0 +1,135 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-string-private.h String utility class (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_STRING_PRIVATE_H +#define DBUS_STRING_PRIVATE_H + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-memory.h> +#include <dbus/dbus-types.h> + +#ifndef DBUS_CAN_USE_DBUS_STRING_PRIVATE +#error "Don't go including dbus-string-private.h for no good reason" +#endif + +DBUS_BEGIN_DECLS + +/** + * @brief Internals of DBusString. + * + * DBusString internals. DBusString is an opaque objects, it must be + * used via accessor functions. + */ +typedef struct +{ + unsigned char *str; /**< String data, plus nul termination */ + int len; /**< Length without nul */ + int allocated; /**< Allocated size of data */ + unsigned int constant : 1; /**< String data is not owned by DBusString */ + unsigned int locked : 1; /**< DBusString has been locked and can't be changed */ + unsigned int valid : 1; /**< DBusString is valid (initialized and not freed) */ + unsigned int align_offset : 3; /**< str - align_offset is the actual malloc block */ +} DBusRealString; + +_DBUS_STATIC_ASSERT (sizeof (DBusRealString) == sizeof (DBusString)); + +/** + * @defgroup DBusStringInternals DBusString implementation details + * @ingroup DBusInternals + * @brief DBusString implementation details + * + * The guts of DBusString. + * + * @{ + */ + +/** + * The maximum length of a DBusString + */ +#define _DBUS_STRING_MAX_LENGTH (_DBUS_INT32_MAX - _DBUS_STRING_ALLOCATION_PADDING) + +/** + * Checks a bunch of assertions about a string object + * + * @param real the DBusRealString + */ +#define DBUS_GENERIC_STRING_PREAMBLE(real) \ + do { \ + (void) real; /* might be unused unless asserting */ \ + _dbus_assert ((real) != NULL); \ + _dbus_assert ((real)->valid); \ + _dbus_assert ((real)->len >= 0); \ + _dbus_assert ((real)->allocated >= 0); \ + _dbus_assert ((real)->len <= ((real)->allocated - _DBUS_STRING_ALLOCATION_PADDING)); \ + _dbus_assert ((real)->len <= _DBUS_STRING_MAX_LENGTH); \ + } while (0) + +/** + * Checks assertions about a string object that needs to be + * modifiable - may not be locked or const. Also declares + * the "real" variable pointing to DBusRealString. + * @param str the string + */ +#define DBUS_STRING_PREAMBLE(str) DBusRealString *real = (DBusRealString*) str; \ + DBUS_GENERIC_STRING_PREAMBLE (real); \ + _dbus_assert (!(real)->constant); \ + _dbus_assert (!(real)->locked) + +/** + * Checks assertions about a string object that may be locked but + * can't be const. i.e. a string object that we can free. Also + * declares the "real" variable pointing to DBusRealString. + * + * @param str the string + */ +#define DBUS_LOCKED_STRING_PREAMBLE(str) DBusRealString *real = (DBusRealString*) str; \ + DBUS_GENERIC_STRING_PREAMBLE (real); \ + _dbus_assert (!(real)->constant) + +/** + * Checks assertions about a string that may be const or locked. Also + * declares the "real" variable pointing to DBusRealString. + * @param str the string. + */ +#define DBUS_CONST_STRING_PREAMBLE(str) const DBusRealString *real = (DBusRealString*) str; \ + DBUS_GENERIC_STRING_PREAMBLE (real) + +/** + * Checks for ASCII blank byte + * @param c the byte + */ +#define DBUS_IS_ASCII_BLANK(c) ((c) == ' ' || (c) == '\t') + +/** + * Checks for ASCII whitespace byte + * @param c the byte + */ +#define DBUS_IS_ASCII_WHITE(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r') + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_STRING_PRIVATE_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-string.c b/src/3rdparty/libdbus/dbus/dbus-string.c new file mode 100644 index 00000000..29210de7 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-string.c @@ -0,0 +1,2816 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-string.c String utility class (internal to D-Bus implementation) + * + * Copyright 2002-2007 Red Hat, Inc. + * Copyright 2003 CodeFactory AB + * Copyright 2003 Mark McLoughlin + * Copyright 2004 Michael Meeks + * Copyright 2006-2014 Ralf Habacker <ralf.habacker@freenet.de> + * Copyright 2006-2018 Collabora Ltd. + * Copyright 2007 Allison Lortie + * Copyright 2011 Roberto Guido + * Copyright 2013 Chengwei Yang / Intel + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-string.h" +/* we allow a system header here, for speed/convenience */ +#include <string.h> +/* for vsnprintf */ +#include <stdio.h> +#define DBUS_CAN_USE_DBUS_STRING_PRIVATE 1 +#include "dbus-string-private.h" +#include "dbus-marshal-basic.h" /* probably should be removed by moving the usage of DBUS_TYPE + * into the marshaling-related files + */ + +/** + * @defgroup DBusString DBusString class + * @ingroup DBusInternals + * @brief DBusString data structure for safer string handling + * + * Types and functions related to DBusString. DBusString is intended + * to be a string class that makes it hard to mess up security issues + * (and just in general harder to write buggy code). It should be + * used (or extended and then used) rather than the libc stuff in + * string.h. The string class is a bit inconvenient at spots because + * it handles out-of-memory failures and tries to be extra-robust. + * + * A DBusString has a maximum length set at initialization time; this + * can be used to ensure that a buffer doesn't get too big. The + * _dbus_string_lengthen() method checks for overflow, and for max + * length being exceeded. + * + * Try to avoid conversion to a plain C string, i.e. add methods on + * the string object instead, only convert to C string when passing + * things out to the public API. In particular, no sprintf, strcpy, + * strcat, any of that should be used. The GString feature of + * accepting negative numbers for "length of string" is also absent, + * because it could keep us from detecting bogus huge lengths. i.e. if + * we passed in some bogus huge length it would be taken to mean + * "current length of string" instead of "broken crack" + * + * @todo #DBusString needs a lot of cleaning up; some of the + * API is no longer used, and the API is pretty inconsistent. + * In particular all the "append" APIs, especially those involving + * alignment but probably lots of them, are no longer used by the + * marshaling code which always does "inserts" now. + */ + +/** + * @addtogroup DBusString + * @{ + */ + +static void +fixup_alignment (DBusRealString *real) +{ + unsigned char *aligned; + unsigned char *real_block; + unsigned int old_align_offset; + + /* we have to have extra space in real->allocated for the align offset and nul byte */ + _dbus_assert (real->len <= real->allocated - _DBUS_STRING_ALLOCATION_PADDING); + + old_align_offset = real->align_offset; + real_block = real->str - old_align_offset; + + aligned = _DBUS_ALIGN_ADDRESS (real_block, 8); + + real->align_offset = aligned - real_block; + real->str = aligned; + + if (old_align_offset != real->align_offset) + { + /* Here comes the suck */ + memmove (real_block + real->align_offset, + real_block + old_align_offset, + real->len + 1); + } + + _dbus_assert (real->align_offset < 8); + _dbus_assert (_DBUS_ALIGN_ADDRESS (real->str, 8) == real->str); +} + +static void +undo_alignment (DBusRealString *real) +{ + if (real->align_offset != 0) + { + memmove (real->str - real->align_offset, + real->str, + real->len + 1); + + real->str = real->str - real->align_offset; + real->align_offset = 0; + } +} + +/** + * Initializes a string that can be up to the given allocation size + * before it has to realloc. The string starts life with zero length. + * The string must eventually be freed with _dbus_string_free(). + * + * @param str memory to hold the string + * @param allocate_size amount to preallocate + * @returns #TRUE on success, #FALSE if no memory + */ +dbus_bool_t +_dbus_string_init_preallocated (DBusString *str, + int allocate_size) +{ + DBusRealString *real; + + _DBUS_STATIC_ASSERT (sizeof (DBusString) == sizeof (DBusRealString)); + + _dbus_assert (str != NULL); + + real = (DBusRealString*) str; + + /* It's very important not to touch anything + * other than real->str if we're going to fail, + * since we also use this function to reset + * an existing string, e.g. in _dbus_string_steal_data() + */ + + real->str = dbus_malloc (_DBUS_STRING_ALLOCATION_PADDING + allocate_size); + if (real->str == NULL) + return FALSE; + + real->allocated = _DBUS_STRING_ALLOCATION_PADDING + allocate_size; + real->len = 0; + real->str[real->len] = '\0'; + + real->constant = FALSE; + real->locked = FALSE; + real->valid = TRUE; + real->align_offset = 0; + + fixup_alignment (real); + + return TRUE; +} + +/** + * Initializes a string. The string starts life with zero length. The + * string must eventually be freed with _dbus_string_free(). + * + * @param str memory to hold the string + * @returns #TRUE on success, #FALSE if no memory + */ +dbus_bool_t +_dbus_string_init (DBusString *str) +{ + return _dbus_string_init_preallocated (str, 0); +} + +/** + * Initializes a constant string. The value parameter is not copied + * (should be static), and the string may never be modified. + * It is safe but not necessary to call _dbus_string_free() + * on a const string. The string has a length limit of MAXINT - 8. + * + * @param str memory to use for the string + * @param value a string to be stored in str (not copied!!!) + */ +void +_dbus_string_init_const (DBusString *str, + const char *value) +{ + _dbus_assert (value != NULL); + + _dbus_string_init_const_len (str, value, + strlen (value)); +} + +/** + * Initializes a constant string with a length. The value parameter is + * not copied (should be static), and the string may never be + * modified. It is safe but not necessary to call _dbus_string_free() + * on a const string. + * + * @param str memory to use for the string + * @param value a string to be stored in str (not copied!!!) + * @param len the length to use + */ +void +_dbus_string_init_const_len (DBusString *str, + const char *value, + int len) +{ + DBusRealString *real; + + _dbus_assert (str != NULL); + _dbus_assert (len == 0 || value != NULL); + _dbus_assert (len <= _DBUS_STRING_MAX_LENGTH); + _dbus_assert (len >= 0); + + real = (DBusRealString*) str; + + real->str = (unsigned char*) value; + real->len = len; + real->allocated = real->len + _DBUS_STRING_ALLOCATION_PADDING; /* a lie, just to avoid special-case assertions... */ + real->constant = TRUE; + real->locked = TRUE; + real->valid = TRUE; + real->align_offset = 0; + + /* We don't require const strings to be 8-byte aligned as the + * memory is coming from elsewhere. + */ +} + +/** + * Initializes a string from another string + * + * The string must be freed with _dbus_string_free() in case of success. + * In case of error the string is freed by this function itself. + * + * @param str memory to hold the string + * @param from instance from which the string is initialized + * @returns #TRUE on success, #FALSE if no memory + */ +dbus_bool_t +_dbus_string_init_from_string(DBusString *str, + const DBusString *from) +{ + if (!_dbus_string_init (str)) + return FALSE; + if (!_dbus_string_append (str, _dbus_string_get_const_data (from))) + { + _dbus_string_free (str); + return FALSE; + } + return TRUE; +} + +/** + * Frees a string created by _dbus_string_init(), and fills it with the + * same contents as #_DBUS_STRING_INIT_INVALID. + * + * Unlike all other #DBusString API, it is also valid to call this function + * for a string that was filled with #_DBUS_STRING_INIT_INVALID. + * This is convenient for error rollback. + * + * @param str memory where the string is stored. + */ +void +_dbus_string_free (DBusString *str) +{ + DBusRealString *real = (DBusRealString*) str; + /* DBusRealString and DBusString have the same members in the same order, + * just differently-named */ + DBusRealString invalid = _DBUS_STRING_INIT_INVALID; + + /* Allow for the _DBUS_STRING_INIT_INVALID case */ + if (real->str == NULL && real->len == 0 && real->allocated == 0 && + !real->constant && !real->locked && !real->valid && + real->align_offset == 0) + return; + + DBUS_GENERIC_STRING_PREAMBLE (real); + + if (real->constant) + goto wipe; + + /* so it's safe if @p str returned by a failed + * _dbus_string_init call + * Bug: https://bugs.freedesktop.org/show_bug.cgi?id=65959 + */ + if (real->str == NULL) + goto wipe; + + dbus_free (real->str - real->align_offset); + +wipe: + *real = invalid; + real->valid = FALSE; +} + +static dbus_bool_t +compact (DBusRealString *real, + int max_waste) +{ + unsigned char *new_str; + int new_allocated; + int waste; + + waste = real->allocated - (real->len + _DBUS_STRING_ALLOCATION_PADDING); + + if (waste <= max_waste) + return TRUE; + + new_allocated = real->len + _DBUS_STRING_ALLOCATION_PADDING; + + new_str = dbus_realloc (real->str - real->align_offset, new_allocated); + if (_DBUS_UNLIKELY (new_str == NULL)) + return FALSE; + + real->str = new_str + real->align_offset; + real->allocated = new_allocated; + fixup_alignment (real); + + return TRUE; +} + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +/* Not using this feature at the moment, + * so marked DBUS_ENABLE_EMBEDDED_TESTS-only + */ +/** + * Locks a string such that any attempts to change the string will + * result in aborting the program. Also, if the string is wasting a + * lot of memory (allocation is sufficiently larger than what the + * string is really using), _dbus_string_lock() will realloc the + * string's data to "compact" it. + * + * @param str the string to lock. + */ +void +_dbus_string_lock (DBusString *str) +{ + DBUS_LOCKED_STRING_PREAMBLE (str); /* can lock multiple times */ + + real->locked = TRUE; + + /* Try to realloc to avoid excess memory usage, since + * we know we won't change the string further + */ +#define MAX_WASTE 48 + compact (real, MAX_WASTE); +} +#endif /* DBUS_ENABLE_EMBEDDED_TESTS */ + +static dbus_bool_t +reallocate_for_length (DBusRealString *real, + int new_length) +{ + int new_allocated; + unsigned char *new_str; + + /* at least double our old allocation to avoid O(n), avoiding + * overflow + */ + if (real->allocated > (_DBUS_STRING_MAX_LENGTH + _DBUS_STRING_ALLOCATION_PADDING) / 2) + new_allocated = _DBUS_STRING_MAX_LENGTH + _DBUS_STRING_ALLOCATION_PADDING; + else + new_allocated = real->allocated * 2; + + /* if you change the code just above here, run the tests without + * the following assert-only hack before you commit + */ + /* This is keyed off asserts in addition to tests so when you + * disable asserts to profile, you don't get this destroyer + * of profiles. + */ +#if defined (DBUS_ENABLE_EMBEDDED_TESTS) && !defined (DBUS_DISABLE_ASSERT) + new_allocated = 0; /* ensure a realloc every time so that we go + * through all malloc failure codepaths + */ +#endif + + /* But be sure we always alloc at least space for the new length */ + new_allocated = MAX (new_allocated, + new_length + _DBUS_STRING_ALLOCATION_PADDING); + + _dbus_assert (new_allocated >= real->allocated); /* code relies on this */ + new_str = dbus_realloc (real->str - real->align_offset, new_allocated); + if (_DBUS_UNLIKELY (new_str == NULL)) + return FALSE; + + real->str = new_str + real->align_offset; + real->allocated = new_allocated; + fixup_alignment (real); + + return TRUE; +} + +/** + * Compacts the string to avoid wasted memory. Wasted memory is + * memory that is allocated but not actually required to store the + * current length of the string. The compact is only done if more + * than the given amount of memory is being wasted (otherwise the + * waste is ignored and the call does nothing). + * + * @param str the string + * @param max_waste the maximum amount of waste to ignore + * @returns #FALSE if the compact failed due to realloc failure + */ +dbus_bool_t +_dbus_string_compact (DBusString *str, + int max_waste) +{ + DBUS_STRING_PREAMBLE (str); + + return compact (real, max_waste); +} + +static dbus_bool_t +set_length (DBusRealString *real, + int new_length) +{ + /* Note, we are setting the length not including nul termination */ + + /* exceeding max length is the same as failure to allocate memory */ + if (_DBUS_UNLIKELY (new_length > _DBUS_STRING_MAX_LENGTH)) + return FALSE; + else if (new_length > (real->allocated - _DBUS_STRING_ALLOCATION_PADDING) && + _DBUS_UNLIKELY (!reallocate_for_length (real, new_length))) + return FALSE; + else + { + real->len = new_length; + real->str[new_length] = '\0'; + return TRUE; + } +} + +static dbus_bool_t +open_gap (int len, + DBusRealString *dest, + int insert_at) +{ + if (len == 0) + return TRUE; + + if (len > _DBUS_STRING_MAX_LENGTH - dest->len) + return FALSE; /* detected overflow of dest->len + len below */ + + if (!set_length (dest, dest->len + len)) + return FALSE; + + memmove (dest->str + insert_at + len, + dest->str + insert_at, + dest->len - len - insert_at); + + return TRUE; +} + +/** + * Returns the allocated size of the string + * + * @param str the string + * @returns the allocated size + */ +int +_dbus_string_get_allocated_size (const DBusString *str) +{ + DBUS_CONST_STRING_PREAMBLE (str); + + return real->allocated; +} + +#ifndef _dbus_string_get_data +/** + * Gets the raw character buffer from the string. The returned buffer + * will be nul-terminated, but note that strings may contain binary + * data so there may be extra nul characters prior to the termination. + * This function should be little-used, extend DBusString or add + * stuff to dbus-sysdeps.c instead. It's an error to use this + * function on a const string. + * + * @param str the string + * @returns the data + */ +char* +_dbus_string_get_data (DBusString *str) +{ + DBUS_STRING_PREAMBLE (str); + + return (char*) real->str; +} +#endif /* _dbus_string_get_data */ + +/* only do the function if we don't have the macro */ +#ifndef _dbus_string_get_const_data +/** + * Gets the raw character buffer from a const string. + * + * @param str the string + * @returns the string data + */ +const char* +_dbus_string_get_const_data (const DBusString *str) +{ + DBUS_CONST_STRING_PREAMBLE (str); + + return (const char*) real->str; +} +#endif /* _dbus_string_get_const_data */ + +/** + * Gets a sub-portion of the raw character buffer from the + * string. The "len" field is required simply for error + * checking, to be sure you don't try to use more + * string than exists. The nul termination of the + * returned buffer remains at the end of the entire + * string, not at start + len. + * + * @param str the string + * @param start byte offset to return + * @param len length of segment to return + * @returns the string data + */ +char* +_dbus_string_get_data_len (DBusString *str, + int start, + int len) +{ + DBUS_STRING_PREAMBLE (str); + _dbus_assert (start >= 0); + _dbus_assert (len >= 0); + _dbus_assert (start <= real->len); + _dbus_assert (len <= real->len - start); + + return (char*) real->str + start; +} + +/* only do the function if we don't have the macro */ +#ifndef _dbus_string_get_const_data_len +/** + * const version of _dbus_string_get_data_len(). + * + * @param str the string + * @param start byte offset to return + * @param len length of segment to return + * @returns the string data + */ +const char* +_dbus_string_get_const_data_len (const DBusString *str, + int start, + int len) +{ + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (start >= 0); + _dbus_assert (len >= 0); + _dbus_assert (start <= real->len); + _dbus_assert (len <= real->len - start); + + return (const char*) real->str + start; +} +#endif /* _dbus_string_get_const_data_len */ + +/* only do the function if we don't have the macro */ +#ifndef _dbus_string_set_byte +/** + * Sets the value of the byte at the given position. + * + * @param str the string + * @param i the position + * @param byte the new value + */ +void +_dbus_string_set_byte (DBusString *str, + int i, + unsigned char byte) +{ + DBUS_STRING_PREAMBLE (str); + _dbus_assert (i < real->len); + _dbus_assert (i >= 0); + + real->str[i] = byte; +} +#endif /* _dbus_string_set_byte */ + +/* only have the function if we didn't create a macro */ +#ifndef _dbus_string_get_byte +/** + * Gets the byte at the given position. It is + * allowed to ask for the nul byte at the end of + * the string. + * + * @param str the string + * @param start the position + * @returns the byte at that position + */ +unsigned char +_dbus_string_get_byte (const DBusString *str, + int start) +{ + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (start <= real->len); + _dbus_assert (start >= 0); + + return real->str[start]; +} +#endif /* _dbus_string_get_byte */ + +/** + * Inserts a number of bytes of a given value at the + * given position. + * + * @param str the string + * @param i the position + * @param n_bytes number of bytes + * @param byte the value to insert + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_string_insert_bytes (DBusString *str, + int i, + int n_bytes, + unsigned char byte) +{ + DBUS_STRING_PREAMBLE (str); + _dbus_assert (i <= real->len); + _dbus_assert (i >= 0); + _dbus_assert (n_bytes >= 0); + + if (n_bytes == 0) + return TRUE; + + if (!open_gap (n_bytes, real, i)) + return FALSE; + + memset (real->str + i, byte, n_bytes); + + return TRUE; +} + +/** + * Inserts a single byte at the given position. + * + * @param str the string + * @param i the position + * @param byte the value to insert + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_string_insert_byte (DBusString *str, + int i, + unsigned char byte) +{ + DBUS_STRING_PREAMBLE (str); + _dbus_assert (i <= real->len); + _dbus_assert (i >= 0); + + if (!open_gap (1, real, i)) + return FALSE; + + real->str[i] = byte; + + return TRUE; +} + +/** + * Like _dbus_string_get_data(), but removes the + * gotten data from the original string. The caller + * must free the data returned. This function may + * fail due to lack of memory, and return #FALSE. + * + * @param str the string + * @param data_return location to return the buffer + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_string_steal_data (DBusString *str, + char **data_return) +{ + DBUS_STRING_PREAMBLE (str); + _dbus_assert (data_return != NULL); + + undo_alignment (real); + + *data_return = (char*) real->str; + + /* reset the string */ + if (!_dbus_string_init (str)) + { + /* hrm, put it back then */ + real->str = (unsigned char*) *data_return; + *data_return = NULL; + fixup_alignment (real); + return FALSE; + } + + return TRUE; +} + +/** + * Copies the data from the string into a char* + * + * @param str the string + * @param data_return place to return the data + * @returns #TRUE on success, #FALSE on no memory + */ +dbus_bool_t +_dbus_string_copy_data (const DBusString *str, + char **data_return) +{ + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (data_return != NULL); + + *data_return = dbus_malloc (real->len + 1); + if (*data_return == NULL) + return FALSE; + + memcpy (*data_return, real->str, real->len + 1); + + return TRUE; +} + +/** + * Copies the contents of a DBusString into a different buffer. It is + * a bug if avail_len is too short to hold the string contents. nul + * termination is not copied, just the supplied bytes. + * + * @param str a string + * @param buffer a C buffer to copy data to + * @param avail_len maximum length of C buffer + */ +void +_dbus_string_copy_to_buffer (const DBusString *str, + char *buffer, + int avail_len) +{ + DBUS_CONST_STRING_PREAMBLE (str); + + _dbus_assert (avail_len >= 0); + _dbus_assert (avail_len >= real->len); + + memcpy (buffer, real->str, real->len); +} + +/** + * Copies the contents of a DBusString into a different buffer. It is + * a bug if avail_len is too short to hold the string contents plus a + * nul byte. + * + * @param str a string + * @param buffer a C buffer to copy data to + * @param avail_len maximum length of C buffer + */ +void +_dbus_string_copy_to_buffer_with_nul (const DBusString *str, + char *buffer, + int avail_len) +{ + DBUS_CONST_STRING_PREAMBLE (str); + + _dbus_assert (avail_len >= 0); + _dbus_assert (avail_len > real->len); + + memcpy (buffer, real->str, real->len+1); +} + +/* Only have the function if we don't have the macro */ +#ifndef _dbus_string_get_length +/** + * Gets the length of a string (not including nul termination). + * + * @returns the length. + */ +int +_dbus_string_get_length (const DBusString *str) +{ + DBUS_CONST_STRING_PREAMBLE (str); + + return real->len; +} +#endif /* !_dbus_string_get_length */ + +/** + * Makes a string longer by the given number of bytes. Checks whether + * adding additional_length to the current length would overflow an + * integer, and checks for exceeding a string's max length. + * The new bytes are not initialized, other than nul-terminating + * the end of the string. The uninitialized bytes may contain + * nul bytes or other junk. + * + * @param str a string + * @param additional_length length to add to the string. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_string_lengthen (DBusString *str, + int additional_length) +{ + DBUS_STRING_PREAMBLE (str); + _dbus_assert (additional_length >= 0); + + if (_DBUS_UNLIKELY (additional_length > _DBUS_STRING_MAX_LENGTH - real->len)) + return FALSE; /* would overflow */ + + return set_length (real, + real->len + additional_length); +} + +/** + * Makes a string shorter by the given number of bytes. + * + * @param str a string + * @param length_to_remove length to remove from the string. + */ +void +_dbus_string_shorten (DBusString *str, + int length_to_remove) +{ + DBUS_STRING_PREAMBLE (str); + _dbus_assert (length_to_remove >= 0); + _dbus_assert (length_to_remove <= real->len); + + set_length (real, + real->len - length_to_remove); +} + +/** + * Sets the length of a string. Can be used to truncate or lengthen + * the string. If the string is lengthened, the function may fail and + * return #FALSE. Newly-added bytes are not initialized, as with + * _dbus_string_lengthen(). + * + * @param str a string + * @param length new length of the string. + * @returns #FALSE on failure. + */ +dbus_bool_t +_dbus_string_set_length (DBusString *str, + int length) +{ + DBUS_STRING_PREAMBLE (str); + _dbus_assert (length >= 0); + + return set_length (real, length); +} + +static dbus_bool_t +align_insert_point_then_open_gap (DBusString *str, + int *insert_at_p, + int alignment, + int gap_size) +{ + unsigned long new_len; /* ulong to avoid _DBUS_ALIGN_VALUE overflow */ + unsigned long gap_pos; + int insert_at; + int delta; + DBUS_STRING_PREAMBLE (str); + _dbus_assert (alignment >= 1); + _dbus_assert (alignment <= 8); /* it has to be a bug if > 8 */ + + insert_at = *insert_at_p; + + _dbus_assert (insert_at <= real->len); + + gap_pos = _DBUS_ALIGN_VALUE (insert_at, alignment); + new_len = real->len + (gap_pos - insert_at) + gap_size; + + if (_DBUS_UNLIKELY (new_len > (unsigned long) _DBUS_STRING_MAX_LENGTH)) + return FALSE; + + delta = new_len - real->len; + _dbus_assert (delta >= 0); + + if (delta == 0) /* only happens if gap_size == 0 and insert_at is aligned already */ + { + _dbus_assert (((unsigned long) *insert_at_p) == gap_pos); + return TRUE; + } + + if (_DBUS_UNLIKELY (!open_gap (new_len - real->len, + real, insert_at))) + return FALSE; + + /* nul the padding if we had to add any padding */ + if (gap_size < delta) + { + memset (&real->str[insert_at], '\0', + gap_pos - insert_at); + } + + *insert_at_p = gap_pos; + + return TRUE; +} + +static dbus_bool_t +align_length_then_lengthen (DBusString *str, + int alignment, + int then_lengthen_by) +{ + int insert_at; + + insert_at = _dbus_string_get_length (str); + + return align_insert_point_then_open_gap (str, + &insert_at, + alignment, then_lengthen_by); +} + +/** + * Align the length of a string to a specific alignment (typically 4 or 8) + * by appending nul bytes to the string. + * + * @param str a string + * @param alignment the alignment + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_string_align_length (DBusString *str, + int alignment) +{ + return align_length_then_lengthen (str, alignment, 0); +} + +/** + * Preallocate extra_bytes such that a future lengthening of the + * string by extra_bytes is guaranteed to succeed without an out of + * memory error. + * + * @param str a string + * @param extra_bytes bytes to alloc + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_string_alloc_space (DBusString *str, + int extra_bytes) +{ + if (!_dbus_string_lengthen (str, extra_bytes)) + return FALSE; + _dbus_string_shorten (str, extra_bytes); + + return TRUE; +} + +static dbus_bool_t +append (DBusRealString *real, + const char *buffer, + int buffer_len) +{ + if (buffer_len == 0) + return TRUE; + + if (!_dbus_string_lengthen ((DBusString*)real, buffer_len)) + return FALSE; + + memcpy (real->str + (real->len - buffer_len), + buffer, + buffer_len); + + return TRUE; +} + +/** + * Appends a nul-terminated C-style string to a DBusString. + * + * @param str the DBusString + * @param buffer the nul-terminated characters to append + * @returns #FALSE if not enough memory. + */ +dbus_bool_t +_dbus_string_append (DBusString *str, + const char *buffer) +{ + unsigned long buffer_len; + + DBUS_STRING_PREAMBLE (str); + _dbus_assert (buffer != NULL); + + buffer_len = strlen (buffer); + if (buffer_len > (unsigned long) _DBUS_STRING_MAX_LENGTH) + return FALSE; + + return append (real, buffer, buffer_len); +} + +/** + * Inserts 2 bytes aligned on a 2 byte boundary + * with any alignment padding initialized to 0. + * + * @param str the DBusString + * @param insert_at where to insert + * @param octets 2 bytes to insert + * @returns #FALSE if not enough memory. + */ +dbus_bool_t +_dbus_string_insert_2_aligned (DBusString *str, + int insert_at, + const unsigned char octets[2]) +{ + DBUS_STRING_PREAMBLE (str); + + if (!align_insert_point_then_open_gap (str, &insert_at, 2, 2)) + return FALSE; + + memcpy (real->str + insert_at, octets, 2); + + return TRUE; +} + +/** + * Inserts 4 bytes aligned on a 4 byte boundary + * with any alignment padding initialized to 0. + * + * @param str the DBusString + * @param insert_at where to insert + * @param octets 4 bytes to insert + * @returns #FALSE if not enough memory. + */ +dbus_bool_t +_dbus_string_insert_4_aligned (DBusString *str, + int insert_at, + const unsigned char octets[4]) +{ + DBUS_STRING_PREAMBLE (str); + + if (!align_insert_point_then_open_gap (str, &insert_at, 4, 4)) + return FALSE; + + memcpy (real->str + insert_at, octets, 4); + + return TRUE; +} + +/** + * Inserts 8 bytes aligned on an 8 byte boundary + * with any alignment padding initialized to 0. + * + * @param str the DBusString + * @param insert_at where to insert + * @param octets 8 bytes to insert + * @returns #FALSE if not enough memory. + */ +dbus_bool_t +_dbus_string_insert_8_aligned (DBusString *str, + int insert_at, + const unsigned char octets[8]) +{ + DBUS_STRING_PREAMBLE (str); + + if (!align_insert_point_then_open_gap (str, &insert_at, 8, 8)) + return FALSE; + + _dbus_assert (_DBUS_ALIGN_VALUE (insert_at, 8) == (unsigned) insert_at); + + memcpy (real->str + insert_at, octets, 8); + + return TRUE; +} + + +/** + * Inserts padding at *insert_at such to align it to the given + * boundary. Initializes the padding to nul bytes. Sets *insert_at + * to the aligned position. + * + * @param str the DBusString + * @param insert_at location to be aligned + * @param alignment alignment boundary (1, 2, 4, or 8) + * @returns #FALSE if not enough memory. + */ +dbus_bool_t +_dbus_string_insert_alignment (DBusString *str, + int *insert_at, + int alignment) +{ + DBUS_STRING_PREAMBLE (str); + + if (!align_insert_point_then_open_gap (str, insert_at, alignment, 0)) + return FALSE; + + _dbus_assert (_DBUS_ALIGN_VALUE (*insert_at, alignment) == (unsigned) *insert_at); + + return TRUE; +} + +/** + * Appends a printf-style formatted string + * to the #DBusString. + * + * @param str the string + * @param format printf format + * @param args variable argument list + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_string_append_printf_valist (DBusString *str, + const char *format, + va_list args) +{ + dbus_bool_t ret = FALSE; + int len; + va_list args_copy; + + DBUS_STRING_PREAMBLE (str); + + va_copy (args_copy, args); + + /* Measure the message length without terminating nul */ + len = _dbus_printf_string_upper_bound (format, args); + + if (len < 0) + goto out; + + if (!_dbus_string_lengthen (str, len)) + { + goto out; + } + + vsprintf ((char*) (real->str + (real->len - len)), + format, args_copy); + ret = TRUE; + +out: + va_end (args_copy); + + return ret; +} + +/** + * Appends a printf-style formatted string + * to the #DBusString. + * + * @param str the string + * @param format printf format + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_string_append_printf (DBusString *str, + const char *format, + ...) +{ + va_list args; + dbus_bool_t retval; + + va_start (args, format); + retval = _dbus_string_append_printf_valist (str, format, args); + va_end (args); + + return retval; +} + +/** + * Appends block of bytes with the given length to a DBusString. + * + * @param str the DBusString + * @param buffer the bytes to append + * @param len the number of bytes to append + * @returns #FALSE if not enough memory. + */ +dbus_bool_t +_dbus_string_append_len (DBusString *str, + const char *buffer, + int len) +{ + DBUS_STRING_PREAMBLE (str); + _dbus_assert (buffer != NULL); + _dbus_assert (len >= 0); + + return append (real, buffer, len); +} + +/** + * Appends a single byte to the string, returning #FALSE + * if not enough memory. + * + * @param str the string + * @param byte the byte to append + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_string_append_byte (DBusString *str, + unsigned char byte) +{ + DBUS_STRING_PREAMBLE (str); + + if (!set_length (real, real->len + 1)) + return FALSE; + + real->str[real->len-1] = byte; + + return TRUE; +} + +/** + * Append vector with \p strings connected by \p separator + * + * @param str the string + * @param strings vector with char* pointer for merging + * @param separator separator to merge the vector + * @return #FALSE if not enough memory + * @return #TRUE success or empty string vector + */ +dbus_bool_t +_dbus_string_append_strings (DBusString *str, char **strings, char separator) +{ + int i; + + if (strings == NULL) + return TRUE; + + for (i = 0; strings[i]; i++) + { + if (i > 0 && !_dbus_string_append_byte (str, (unsigned char) separator)) + return FALSE; + + if (!_dbus_string_append (str, strings[i])) + return FALSE; + } + + return TRUE; +} + +static void +delete (DBusRealString *real, + int start, + int len) +{ + if (len == 0) + return; + + memmove (real->str + start, real->str + start + len, real->len - (start + len)); + real->len -= len; + real->str[real->len] = '\0'; +} + +/** + * Deletes a segment of a DBusString with length len starting at + * start. (Hint: to clear an entire string, setting length to 0 + * with _dbus_string_set_length() is easier.) + * + * @param str the DBusString + * @param start where to start deleting + * @param len the number of bytes to delete + */ +void +_dbus_string_delete (DBusString *str, + int start, + int len) +{ + DBUS_STRING_PREAMBLE (str); + _dbus_assert (start >= 0); + _dbus_assert (len >= 0); + _dbus_assert (start <= real->len); + _dbus_assert (len <= real->len - start); + + delete (real, start, len); +} + +static dbus_bool_t +copy (DBusRealString *source, + int start, + int len, + DBusRealString *dest, + int insert_at) +{ + if (len == 0) + return TRUE; + + if (!open_gap (len, dest, insert_at)) + return FALSE; + + memmove (dest->str + insert_at, + source->str + start, + len); + + return TRUE; +} + +/** + * Checks assertions for two strings we're copying a segment between, + * and declares real_source/real_dest variables. + * + * @param source the source string + * @param start the starting offset + * @param dest the dest string + * @param insert_at where the copied segment is inserted + */ +#define DBUS_STRING_COPY_PREAMBLE(source, start, dest, insert_at) \ + DBusRealString *real_source = (DBusRealString*) source; \ + DBusRealString *real_dest = (DBusRealString*) dest; \ + _dbus_assert ((source) != (dest)); \ + DBUS_GENERIC_STRING_PREAMBLE (real_source); \ + DBUS_GENERIC_STRING_PREAMBLE (real_dest); \ + _dbus_assert (!real_dest->constant); \ + _dbus_assert (!real_dest->locked); \ + _dbus_assert ((start) >= 0); \ + _dbus_assert ((start) <= real_source->len); \ + _dbus_assert ((insert_at) >= 0); \ + _dbus_assert ((insert_at) <= real_dest->len) + +/** + * Moves the end of one string into another string. Both strings + * must be initialized, valid strings. + * + * @param source the source string + * @param start where to chop off the source string + * @param dest the destination string + * @param insert_at where to move the chopped-off part of source string + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_string_move (DBusString *source, + int start, + DBusString *dest, + int insert_at) +{ + DBusRealString *real_source = (DBusRealString*) source; + _dbus_assert (start <= real_source->len); + + return _dbus_string_move_len (source, start, + real_source->len - start, + dest, insert_at); +} + +/** + * Like _dbus_string_move(), but does not delete the section + * of the source string that's copied to the dest string. + * + * @param source the source string + * @param start where to start copying the source string + * @param dest the destination string + * @param insert_at where to place the copied part of source string + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_string_copy (const DBusString *source, + int start, + DBusString *dest, + int insert_at) +{ + DBUS_STRING_COPY_PREAMBLE (source, start, dest, insert_at); + + return copy (real_source, start, + real_source->len - start, + real_dest, + insert_at); +} + +/** + * Like _dbus_string_move(), but can move a segment from + * the middle of the source string. + * + * @param source the source string + * @param start first byte of source string to move + * @param len length of segment to move + * @param dest the destination string + * @param insert_at where to move the bytes from the source string + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_string_move_len (DBusString *source, + int start, + int len, + DBusString *dest, + int insert_at) + +{ + DBUS_STRING_COPY_PREAMBLE (source, start, dest, insert_at); + _dbus_assert (len >= 0); + _dbus_assert ((start + len) <= real_source->len); + + + if (len == 0) + { + return TRUE; + } + else if (start == 0 && + len == real_source->len && + real_dest->len == 0) + { + /* Short-circuit moving an entire existing string to an empty string + * by just swapping the buffers. + */ + /* we assume ->constant doesn't matter as you can't have + * a constant string involved in a move. + */ +#define ASSIGN_DATA(a, b) do { \ + (a)->str = (b)->str; \ + (a)->len = (b)->len; \ + (a)->allocated = (b)->allocated; \ + (a)->align_offset = (b)->align_offset; \ + } while (0) + + DBusRealString tmp; + + ASSIGN_DATA (&tmp, real_source); + ASSIGN_DATA (real_source, real_dest); + ASSIGN_DATA (real_dest, &tmp); + + return TRUE; + } + else + { + if (!copy (real_source, start, len, + real_dest, + insert_at)) + return FALSE; + + delete (real_source, start, + len); + + return TRUE; + } +} + +/** + * Like _dbus_string_copy(), but can copy a segment from the middle of + * the source string. + * + * @param source the source string + * @param start where to start copying the source string + * @param len length of segment to copy + * @param dest the destination string + * @param insert_at where to place the copied segment of source string + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_string_copy_len (const DBusString *source, + int start, + int len, + DBusString *dest, + int insert_at) +{ + DBUS_STRING_COPY_PREAMBLE (source, start, dest, insert_at); + _dbus_assert (len >= 0); + _dbus_assert (start <= real_source->len); + _dbus_assert (len <= real_source->len - start); + + return copy (real_source, start, len, + real_dest, + insert_at); +} + +/** + * Replaces a segment of dest string with a segment of source string. + * + * @param source the source string + * @param start where to start copying the source string + * @param len length of segment to copy + * @param dest the destination string + * @param replace_at start of segment of dest string to replace + * @param replace_len length of segment of dest string to replace + * @returns #FALSE if not enough memory + * + */ +dbus_bool_t +_dbus_string_replace_len (const DBusString *source, + int start, + int len, + DBusString *dest, + int replace_at, + int replace_len) +{ + DBUS_STRING_COPY_PREAMBLE (source, start, dest, replace_at); + _dbus_assert (len >= 0); + _dbus_assert (start <= real_source->len); + _dbus_assert (len <= real_source->len - start); + _dbus_assert (replace_at >= 0); + _dbus_assert (replace_at <= real_dest->len); + _dbus_assert (replace_len <= real_dest->len - replace_at); + + if (len == replace_len) + { + memmove (real_dest->str + replace_at, + real_source->str + start, len); + } + else if (len < replace_len) + { + memmove (real_dest->str + replace_at, + real_source->str + start, len); + delete (real_dest, replace_at + len, + replace_len - len); + } + else + { + int diff; + + _dbus_assert (len > replace_len); + + diff = len - replace_len; + + /* First of all we check if destination string can be enlarged as + * required, then we overwrite previous bytes + */ + + if (!copy (real_source, start + replace_len, diff, + real_dest, replace_at + replace_len)) + return FALSE; + + memmove (real_dest->str + replace_at, + real_source->str + start, replace_len); + } + + return TRUE; +} + +/** + * Looks for the first occurance of a byte, deletes that byte, + * and moves everything after the byte to the beginning of a + * separate string. Both strings must be initialized, valid + * strings. + * + * @param source the source string + * @param byte the byte to remove and split the string at + * @param tail the split off string + * @returns #FALSE if not enough memory or if byte could not be found + * + */ +dbus_bool_t +_dbus_string_split_on_byte (DBusString *source, + unsigned char byte, + DBusString *tail) +{ + int byte_position; + char byte_string[2] = ""; + int head_length; + int tail_length; + + byte_string[0] = (char) byte; + + if (!_dbus_string_find (source, 0, byte_string, &byte_position)) + return FALSE; + + head_length = byte_position; + tail_length = _dbus_string_get_length (source) - head_length - 1; + + if (!_dbus_string_move_len (source, byte_position + 1, tail_length, + tail, 0)) + return FALSE; + + /* remove the trailing delimiter byte from the head now. + */ + if (!_dbus_string_set_length (source, head_length)) + return FALSE; + + return TRUE; +} + +/* Unicode macros and utf8_validate() from GLib Owen Taylor, Havoc + * Pennington, and Tom Tromey are the authors and authorized relicense. + */ + +/** computes length and mask of a unicode character + * @param Char the char + * @param Mask the mask variable to assign to + * @param Len the length variable to assign to + */ +#define UTF8_COMPUTE(Char, Mask, Len) \ + if (Char < 128) \ + { \ + Len = 1; \ + Mask = 0x7f; \ + } \ + else if ((Char & 0xe0) == 0xc0) \ + { \ + Len = 2; \ + Mask = 0x1f; \ + } \ + else if ((Char & 0xf0) == 0xe0) \ + { \ + Len = 3; \ + Mask = 0x0f; \ + } \ + else if ((Char & 0xf8) == 0xf0) \ + { \ + Len = 4; \ + Mask = 0x07; \ + } \ + else if ((Char & 0xfc) == 0xf8) \ + { \ + Len = 5; \ + Mask = 0x03; \ + } \ + else if ((Char & 0xfe) == 0xfc) \ + { \ + Len = 6; \ + Mask = 0x01; \ + } \ + else \ + { \ + Len = 0; \ + Mask = 0; \ + } + +/** + * computes length of a unicode character in UTF-8 + * @param Char the char + */ +#define UTF8_LENGTH(Char) \ + ((Char) < 0x80 ? 1 : \ + ((Char) < 0x800 ? 2 : \ + ((Char) < 0x10000 ? 3 : \ + ((Char) < 0x200000 ? 4 : \ + ((Char) < 0x4000000 ? 5 : 6))))) + +/** + * Gets a UTF-8 value. + * + * @param Result variable for extracted unicode char. + * @param Chars the bytes to decode + * @param Count counter variable + * @param Mask mask for this char + * @param Len length for this char in bytes + */ +#define UTF8_GET(Result, Chars, Count, Mask, Len) \ + (Result) = (Chars)[0] & (Mask); \ + for ((Count) = 1; (Count) < (Len); ++(Count)) \ + { \ + if (((Chars)[(Count)] & 0xc0) != 0x80) \ + { \ + (Result) = -1; \ + break; \ + } \ + (Result) <<= 6; \ + (Result) |= ((Chars)[(Count)] & 0x3f); \ + } + +/** + * Check whether a Unicode (5.2) char is in a valid range. + * + * The first check comes from the Unicode guarantee to never encode + * a point above 0x0010ffff, since UTF-16 couldn't represent it. + * + * The second check covers surrogate pairs (category Cs). + * + * @param Char the character + */ +#define UNICODE_VALID(Char) \ + ((Char) < 0x110000 && \ + (((Char) & 0xFFFFF800) != 0xD800)) + +/** + * Finds the given substring in the string, + * returning #TRUE and filling in the byte index + * where the substring was found, if it was found. + * Returns #FALSE if the substring wasn't found. + * Sets *start to the length of the string if the substring + * is not found. + * + * @param str the string + * @param start where to start looking + * @param substr the substring + * @param found return location for where it was found, or #NULL + * @returns #TRUE if found + */ +dbus_bool_t +_dbus_string_find (const DBusString *str, + int start, + const char *substr, + int *found) +{ + return _dbus_string_find_to (str, start, + ((const DBusRealString*)str)->len, + substr, found); +} + +/** + * Finds end of line ("\r\n" or "\n") in the string, + * returning #TRUE and filling in the byte index + * where the eol string was found, if it was found. + * Returns #FALSE if eol wasn't found. + * + * @param str the string + * @param start where to start looking + * @param found return location for where eol was found or string length otherwise + * @param found_len return length of found eol string or zero otherwise + * @returns #TRUE if found + */ +dbus_bool_t +_dbus_string_find_eol (const DBusString *str, + int start, + int *found, + int *found_len) +{ + int i; + + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (start <= real->len); + _dbus_assert (start >= 0); + + i = start; + while (i < real->len) + { + if (real->str[i] == '\r') + { + if ((i+1) < real->len && real->str[i+1] == '\n') /* "\r\n" */ + { + if (found) + *found = i; + if (found_len) + *found_len = 2; + return TRUE; + } + else /* only "\r" */ + { + if (found) + *found = i; + if (found_len) + *found_len = 1; + return TRUE; + } + } + else if (real->str[i] == '\n') /* only "\n" */ + { + if (found) + *found = i; + if (found_len) + *found_len = 1; + return TRUE; + } + ++i; + } + + if (found) + *found = real->len; + + if (found_len) + *found_len = 0; + + return FALSE; +} + +/** + * Finds the given substring in the string, + * up to a certain position, + * returning #TRUE and filling in the byte index + * where the substring was found, if it was found. + * Returns #FALSE if the substring wasn't found. + * Sets *start to the length of the string if the substring + * is not found. + * + * @param str the string + * @param start where to start looking + * @param end where to stop looking + * @param substr the substring + * @param found return location for where it was found, or #NULL + * @returns #TRUE if found + */ +dbus_bool_t +_dbus_string_find_to (const DBusString *str, + int start, + int end, + const char *substr, + int *found) +{ + int i; + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (substr != NULL); + _dbus_assert (start <= real->len); + _dbus_assert (start >= 0); + _dbus_assert (substr != NULL); + _dbus_assert (end <= real->len); + _dbus_assert (start <= end); + + /* we always "find" an empty string */ + if (*substr == '\0') + { + if (found) + *found = start; + return TRUE; + } + + i = start; + while (i < end) + { + if (real->str[i] == substr[0]) + { + int j = i + 1; + + while (j < end) + { + if (substr[j - i] == '\0') + break; + else if (real->str[j] != substr[j - i]) + break; + + ++j; + } + + if (substr[j - i] == '\0') + { + if (found) + *found = i; + return TRUE; + } + } + + ++i; + } + + if (found) + *found = end; + + return FALSE; +} + +/** + * Finds a blank (space or tab) in the string. Returns #TRUE + * if found, #FALSE otherwise. If a blank is not found sets + * *found to the length of the string. + * + * @param str the string + * @param start byte index to start looking + * @param found place to store the location of the first blank + * @returns #TRUE if a blank was found + */ +dbus_bool_t +_dbus_string_find_blank (const DBusString *str, + int start, + int *found) +{ + int i; + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (start <= real->len); + _dbus_assert (start >= 0); + + i = start; + while (i < real->len) + { + if (real->str[i] == ' ' || + real->str[i] == '\t') + { + if (found) + *found = i; + return TRUE; + } + + ++i; + } + + if (found) + *found = real->len; + + return FALSE; +} + +/** + * Skips blanks from start, storing the first non-blank in *end + * (blank is space or tab). + * + * @param str the string + * @param start where to start + * @param end where to store the first non-blank byte index + */ +void +_dbus_string_skip_blank (const DBusString *str, + int start, + int *end) +{ + int i; + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (start <= real->len); + _dbus_assert (start >= 0); + + i = start; + while (i < real->len) + { + if (!DBUS_IS_ASCII_BLANK (real->str[i])) + break; + + ++i; + } + + _dbus_assert (i == real->len || !DBUS_IS_ASCII_BLANK (real->str[i])); + + if (end) + *end = i; +} + + +/** + * Skips whitespace from start, storing the first non-whitespace in *end. + * (whitespace is space, tab, newline, CR). + * + * @param str the string + * @param start where to start + * @param end where to store the first non-whitespace byte index + */ +void +_dbus_string_skip_white (const DBusString *str, + int start, + int *end) +{ + int i; + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (start <= real->len); + _dbus_assert (start >= 0); + + i = start; + while (i < real->len) + { + if (!DBUS_IS_ASCII_WHITE (real->str[i])) + break; + + ++i; + } + + _dbus_assert (i == real->len || !(DBUS_IS_ASCII_WHITE (real->str[i]))); + + if (end) + *end = i; +} + +/** + * Skips whitespace from end, storing the start index of the trailing + * whitespace in *start. (whitespace is space, tab, newline, CR). + * + * @param str the string + * @param end where to start scanning backward + * @param start where to store the start of whitespace chars + */ +void +_dbus_string_skip_white_reverse (const DBusString *str, + int end, + int *start) +{ + int i; + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (end <= real->len); + _dbus_assert (end >= 0); + + i = end; + while (i > 0) + { + if (!DBUS_IS_ASCII_WHITE (real->str[i-1])) + break; + --i; + } + + _dbus_assert (i >= 0 && (i == 0 || !(DBUS_IS_ASCII_WHITE (real->str[i-1])))); + + if (start) + *start = i; +} + +/** + * Assigns a newline-terminated or \\r\\n-terminated line from the front + * of the string to the given dest string. The dest string's previous + * contents are deleted. If the source string contains no newline, + * moves the entire source string to the dest string. + * + * @todo owen correctly notes that this is a stupid function (it was + * written purely for test code, + * e.g. dbus-message-builder.c). Probably should be enforced as test + * code only with ifdef DBUS_ENABLE_EMBEDDED_TESTS + * + * @param source the source string + * @param dest the destination string (contents are replaced) + * @returns #FALSE if no memory, or source has length 0 + */ +dbus_bool_t +_dbus_string_pop_line (DBusString *source, + DBusString *dest) +{ + int eol, eol_len; + + _dbus_string_set_length (dest, 0); + + eol = 0; + eol_len = 0; + if (!_dbus_string_find_eol (source, 0, &eol, &eol_len)) + { + _dbus_assert (eol == _dbus_string_get_length (source)); + if (eol == 0) + { + /* If there's no newline and source has zero length, we're done */ + return FALSE; + } + /* otherwise, the last line of the file has no eol characters */ + } + + /* remember eol can be 0 if it's an empty line, but eol_len should not be zero also + * since find_eol returned TRUE + */ + + if (!_dbus_string_move_len (source, 0, eol + eol_len, dest, 0)) + return FALSE; + + /* remove line ending */ + if (!_dbus_string_set_length (dest, eol)) + { + _dbus_assert_not_reached ("out of memory when shortening a string"); + return FALSE; + } + + return TRUE; +} + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +/** + * Deletes up to and including the first blank space + * in the string. + * + * @param str the string + */ +void +_dbus_string_delete_first_word (DBusString *str) +{ + int i; + + if (_dbus_string_find_blank (str, 0, &i)) + _dbus_string_skip_blank (str, i, &i); + + _dbus_string_delete (str, 0, i); +} +#endif + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +/** + * Deletes any leading blanks in the string + * + * @param str the string + */ +void +_dbus_string_delete_leading_blanks (DBusString *str) +{ + int i; + + _dbus_string_skip_blank (str, 0, &i); + + if (i > 0) + _dbus_string_delete (str, 0, i); +} +#endif + +/** + * Deletes leading and trailing whitespace + * + * @param str the string + */ +void +_dbus_string_chop_white(DBusString *str) +{ + int i; + + _dbus_string_skip_white (str, 0, &i); + + if (i > 0) + _dbus_string_delete (str, 0, i); + + _dbus_string_skip_white_reverse (str, _dbus_string_get_length (str), &i); + + _dbus_string_set_length (str, i); +} + +/** + * Tests two DBusString for equality. + * + * @todo memcmp is probably faster + * + * @param a first string + * @param b second string + * @returns #TRUE if equal + */ +dbus_bool_t +_dbus_string_equal (const DBusString *a, + const DBusString *b) +{ + const unsigned char *ap; + const unsigned char *bp; + const unsigned char *a_end; + const DBusRealString *real_a = (const DBusRealString*) a; + const DBusRealString *real_b = (const DBusRealString*) b; + DBUS_GENERIC_STRING_PREAMBLE (real_a); + DBUS_GENERIC_STRING_PREAMBLE (real_b); + + if (real_a->len != real_b->len) + return FALSE; + + ap = real_a->str; + bp = real_b->str; + a_end = real_a->str + real_a->len; + while (ap != a_end) + { + if (*ap != *bp) + return FALSE; + + ++ap; + ++bp; + } + + return TRUE; +} + +/** + * Tests two DBusString for equality up to the given length. + * The strings may be shorter than the given length. + * + * @todo write a unit test + * + * @todo memcmp is probably faster + * + * @param a first string + * @param b second string + * @param len the maximum length to look at + * @returns #TRUE if equal for the given number of bytes + */ +dbus_bool_t +_dbus_string_equal_len (const DBusString *a, + const DBusString *b, + int len) +{ + const unsigned char *ap; + const unsigned char *bp; + const unsigned char *a_end; + const DBusRealString *real_a = (const DBusRealString*) a; + const DBusRealString *real_b = (const DBusRealString*) b; + DBUS_GENERIC_STRING_PREAMBLE (real_a); + DBUS_GENERIC_STRING_PREAMBLE (real_b); + + if (real_a->len != real_b->len && + (real_a->len < len || real_b->len < len)) + return FALSE; + + ap = real_a->str; + bp = real_b->str; + a_end = real_a->str + MIN (real_a->len, len); + while (ap != a_end) + { + if (*ap != *bp) + return FALSE; + + ++ap; + ++bp; + } + + return TRUE; +} + +/** + * Tests two sub-parts of two DBusString for equality. The specified + * range of the first string must exist; the specified start position + * of the second string must exist. + * + * @todo write a unit test + * + * @todo memcmp is probably faster + * + * @param a first string + * @param a_start where to start substring in first string + * @param a_len length of substring in first string + * @param b second string + * @param b_start where to start substring in second string + * @returns #TRUE if the two substrings are equal + */ +dbus_bool_t +_dbus_string_equal_substring (const DBusString *a, + int a_start, + int a_len, + const DBusString *b, + int b_start) +{ + const unsigned char *ap; + const unsigned char *bp; + const unsigned char *a_end; + const DBusRealString *real_a = (const DBusRealString*) a; + const DBusRealString *real_b = (const DBusRealString*) b; + DBUS_GENERIC_STRING_PREAMBLE (real_a); + DBUS_GENERIC_STRING_PREAMBLE (real_b); + _dbus_assert (a_start >= 0); + _dbus_assert (a_len >= 0); + _dbus_assert (a_start <= real_a->len); + _dbus_assert (a_len <= real_a->len - a_start); + _dbus_assert (b_start >= 0); + _dbus_assert (b_start <= real_b->len); + + if (a_len > real_b->len - b_start) + return FALSE; + + ap = real_a->str + a_start; + bp = real_b->str + b_start; + a_end = ap + a_len; + while (ap != a_end) + { + if (*ap != *bp) + return FALSE; + + ++ap; + ++bp; + } + + _dbus_assert (bp <= (real_b->str + real_b->len)); + + return TRUE; +} + +/** + * Checks whether a string is equal to a C string. + * + * @param a the string + * @param c_str the C string + * @returns #TRUE if equal + */ +dbus_bool_t +_dbus_string_equal_c_str (const DBusString *a, + const char *c_str) +{ + const unsigned char *ap; + const unsigned char *bp; + const unsigned char *a_end; + const DBusRealString *real_a = (const DBusRealString*) a; + DBUS_GENERIC_STRING_PREAMBLE (real_a); + _dbus_assert (c_str != NULL); + + ap = real_a->str; + bp = (const unsigned char*) c_str; + a_end = real_a->str + real_a->len; + while (ap != a_end && *bp) + { + if (*ap != *bp) + return FALSE; + + ++ap; + ++bp; + } + + if (ap != a_end || *bp) + return FALSE; + + return TRUE; +} + +/** + * Checks whether a string starts with the given C string. + * + * @param a the string + * @param c_str the C string + * @returns #TRUE if string starts with it + */ +dbus_bool_t +_dbus_string_starts_with_c_str (const DBusString *a, + const char *c_str) +{ + const unsigned char *ap; + const unsigned char *bp; + const unsigned char *a_end; + const DBusRealString *real_a = (const DBusRealString*) a; + DBUS_GENERIC_STRING_PREAMBLE (real_a); + _dbus_assert (c_str != NULL); + + ap = real_a->str; + bp = (const unsigned char*) c_str; + a_end = real_a->str + real_a->len; + while (ap != a_end && *bp) + { + if (*ap != *bp) + return FALSE; + + ++ap; + ++bp; + } + + if (*bp == '\0') + return TRUE; + else + return FALSE; +} + +/** + * Checks whether a string starts with the given C string, after which it ends or is separated from + * the rest by a given separator character. + * + * @param a the string + * @param c_str the C string + * @param word_separator the separator + * @returns #TRUE if string starts with it + */ +dbus_bool_t +_dbus_string_starts_with_words_c_str (const DBusString *a, + const char *c_str, + char word_separator) +{ + char next_char; + const char *data; + _dbus_assert (c_str != NULL); + + if (!_dbus_string_starts_with_c_str (a, c_str)) + return FALSE; + + data = _dbus_string_get_const_data (a); + next_char = data[strlen (c_str)]; + return next_char == '\0' || next_char == word_separator; +} + +/** + * Appends a two-character hex digit to a string, where the hex digit + * has the value of the given byte. + * + * @param str the string + * @param byte the byte + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_string_append_byte_as_hex (DBusString *str, + unsigned char byte) +{ + const char hexdigits[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f' + }; + + if (!_dbus_string_append_byte (str, + hexdigits[(byte >> 4)])) + return FALSE; + + if (!_dbus_string_append_byte (str, + hexdigits[(byte & 0x0f)])) + { + _dbus_string_set_length (str, + _dbus_string_get_length (str) - 1); + return FALSE; + } + + return TRUE; +} + +/* Currently only used when embedded tests are enabled */ +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +/** + * Appends \p size bytes from the buffer \p buf as hex digits to the string \p str + * + * If \p size is nonzero, then \p buf must be non-NULL. + * + * @param str the string + * @param buf the buffer to add bytes from + * @param size the number of bytes to add + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_string_append_buffer_as_hex (DBusString *str, + void *buf, + int size) +{ + unsigned char *p; + int i; + + _dbus_assert (size >= 0); + _dbus_assert (size == 0 || buf != NULL); + + p = (unsigned char *) buf; + + for (i = 0; i < size; i++) + { + if (!_dbus_string_append_byte_as_hex (str, p[i])) + return FALSE; + } + + return TRUE; +} +#endif + +/** + * Encodes a string in hex, the way MD5 and SHA-1 are usually + * encoded. (Each byte is two hex digits.) + * + * @param source the string to encode + * @param start byte index to start encoding + * @param dest string where encoded data should be placed + * @param insert_at where to place encoded data + * @returns #TRUE if encoding was successful, #FALSE if no memory etc. + */ +dbus_bool_t +_dbus_string_hex_encode (const DBusString *source, + int start, + DBusString *dest, + int insert_at) +{ + DBusString result; + const unsigned char *p; + const unsigned char *end; + dbus_bool_t retval; + + _dbus_assert (start <= _dbus_string_get_length (source)); + + if (!_dbus_string_init (&result)) + return FALSE; + + retval = FALSE; + + p = (const unsigned char*) _dbus_string_get_const_data (source); + end = p + _dbus_string_get_length (source); + p += start; + + while (p != end) + { + if (!_dbus_string_append_byte_as_hex (&result, *p)) + goto out; + + ++p; + } + + if (!_dbus_string_move (&result, 0, dest, insert_at)) + goto out; + + retval = TRUE; + + out: + _dbus_string_free (&result); + return retval; +} + +/** + * Decodes a string from hex encoding. + * + * @param source the string to decode + * @param start byte index to start decode + * @param end_return return location of the end of the hex data, or #NULL + * @param dest string where decoded data should be placed + * @param insert_at where to place decoded data + * @returns #TRUE if decoding was successful, #FALSE if no memory. + */ +dbus_bool_t +_dbus_string_hex_decode (const DBusString *source, + int start, + int *end_return, + DBusString *dest, + int insert_at) +{ + DBusString result; + const unsigned char *p; + const unsigned char *end; + dbus_bool_t retval; + dbus_bool_t high_bits; + + _dbus_assert (start <= _dbus_string_get_length (source)); + + if (!_dbus_string_init (&result)) + return FALSE; + + retval = FALSE; + + high_bits = TRUE; + p = (const unsigned char*) _dbus_string_get_const_data (source); + end = p + _dbus_string_get_length (source); + p += start; + + while (p != end) + { + unsigned int val; + + switch (*p) + { + case '0': + val = 0; + break; + case '1': + val = 1; + break; + case '2': + val = 2; + break; + case '3': + val = 3; + break; + case '4': + val = 4; + break; + case '5': + val = 5; + break; + case '6': + val = 6; + break; + case '7': + val = 7; + break; + case '8': + val = 8; + break; + case '9': + val = 9; + break; + case 'a': + case 'A': + val = 10; + break; + case 'b': + case 'B': + val = 11; + break; + case 'c': + case 'C': + val = 12; + break; + case 'd': + case 'D': + val = 13; + break; + case 'e': + case 'E': + val = 14; + break; + case 'f': + case 'F': + val = 15; + break; + default: + goto done; + } + + if (high_bits) + { + if (!_dbus_string_append_byte (&result, + val << 4)) + goto out; + } + else + { + int len; + unsigned char b; + + len = _dbus_string_get_length (&result); + + b = _dbus_string_get_byte (&result, len - 1); + + b |= val; + + _dbus_string_set_byte (&result, len - 1, b); + } + + high_bits = !high_bits; + + ++p; + } + + done: + if (!_dbus_string_move (&result, 0, dest, insert_at)) + goto out; + + if (end_return) + *end_return = p - (const unsigned char*) _dbus_string_get_const_data (source); + + retval = TRUE; + + out: + _dbus_string_free (&result); + return retval; +} + +/** + * Checks that the given range of the string is valid ASCII with no + * nul bytes. If the given range is not entirely contained in the + * string, returns #FALSE. + * + * @todo this is inconsistent with most of DBusString in that + * it allows a start,len range that extends past the string end. + * + * @param str the string + * @param start first byte index to check + * @param len number of bytes to check + * @returns #TRUE if the byte range exists and is all valid ASCII + */ +dbus_bool_t +_dbus_string_validate_ascii (const DBusString *str, + int start, + int len) +{ + const unsigned char *s; + const unsigned char *end; + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (start >= 0); + _dbus_assert (start <= real->len); + _dbus_assert (len >= 0); + + if (len > real->len - start) + return FALSE; + + s = real->str + start; + end = s + len; + while (s != end) + { + if (_DBUS_UNLIKELY (!_DBUS_ISASCII (*s))) + return FALSE; + + ++s; + } + + return TRUE; +} + +/** + * Converts the given range of the string to lower case. + * + * @param str the string + * @param start first byte index to convert + * @param len number of bytes to convert + */ +void +_dbus_string_tolower_ascii (const DBusString *str, + int start, + int len) +{ + unsigned char *s; + unsigned char *end; + DBUS_STRING_PREAMBLE (str); + _dbus_assert (start >= 0); + _dbus_assert (start <= real->len); + _dbus_assert (len >= 0); + _dbus_assert (len <= real->len - start); + + s = real->str + start; + end = s + len; + + while (s != end) + { + if (*s >= 'A' && *s <= 'Z') + *s += 'a' - 'A'; + ++s; + } +} + +/** + * Converts the given range of the string to upper case. + * + * @param str the string + * @param start first byte index to convert + * @param len number of bytes to convert + */ +void +_dbus_string_toupper_ascii (const DBusString *str, + int start, + int len) +{ + unsigned char *s; + unsigned char *end; + DBUS_STRING_PREAMBLE (str); + _dbus_assert (start >= 0); + _dbus_assert (start <= real->len); + _dbus_assert (len >= 0); + _dbus_assert (len <= real->len - start); + + s = real->str + start; + end = s + len; + + while (s != end) + { + if (*s >= 'a' && *s <= 'z') + *s += 'A' - 'a'; + ++s; + } +} + +/** + * Checks that the given range of the string is valid UTF-8. If the + * given range is not entirely contained in the string, returns + * #FALSE. If the string contains any nul bytes in the given range, + * returns #FALSE. If the start and start+len are not on character + * boundaries, returns #FALSE. + * + * @todo this is inconsistent with most of DBusString in that + * it allows a start,len range that extends past the string end. + * + * @param str the string + * @param start first byte index to check + * @param len number of bytes to check + * @returns #TRUE if the byte range exists and is all valid UTF-8 + */ +dbus_bool_t +_dbus_string_validate_utf8 (const DBusString *str, + int start, + int len) +{ + const unsigned char *p; + const unsigned char *end; + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (start >= 0); + _dbus_assert (start <= real->len); + _dbus_assert (len >= 0); + + /* we are doing _DBUS_UNLIKELY() here which might be + * dubious in a generic library like GLib, but in D-Bus + * we know we're validating messages and that it would + * only be evil/broken apps that would have invalid + * UTF-8. Also, this function seems to be a performance + * bottleneck in profiles. + */ + + if (_DBUS_UNLIKELY (len > real->len - start)) + return FALSE; + + p = real->str + start; + end = p + len; + + while (p < end) + { + int i, mask, char_len; + dbus_unichar_t result; + + /* nul bytes considered invalid */ + if (*p == '\0') + break; + + /* Special-case ASCII; this makes us go a lot faster in + * D-Bus profiles where we are typically validating + * function names and such. We have to know that + * all following checks will pass for ASCII though, + * comments follow ... + */ + if (*p < 128) + { + ++p; + continue; + } + + UTF8_COMPUTE (*p, mask, char_len); + + if (_DBUS_UNLIKELY (char_len == 0)) /* ASCII: char_len == 1 */ + break; + + /* check that the expected number of bytes exists in the remaining length */ + if (_DBUS_UNLIKELY ((end - p) < char_len)) /* ASCII: p < end and char_len == 1 */ + break; + + UTF8_GET (result, p, i, mask, char_len); + + /* Check for overlong UTF-8 */ + if (_DBUS_UNLIKELY (UTF8_LENGTH (result) != char_len)) /* ASCII: UTF8_LENGTH == 1 */ + break; +#if 0 + /* The UNICODE_VALID check below will catch this */ + if (_DBUS_UNLIKELY (result == (dbus_unichar_t)-1)) /* ASCII: result = ascii value */ + break; +#endif + + if (_DBUS_UNLIKELY (!UNICODE_VALID (result))) /* ASCII: always valid */ + break; + + /* UNICODE_VALID should have caught it */ + _dbus_assert (result != (dbus_unichar_t)-1); + + p += char_len; + } + + /* See that we covered the entire length if a length was + * passed in + */ + if (_DBUS_UNLIKELY (p != end)) + return FALSE; + else + return TRUE; +} + +/** + * Checks that the given range of the string is all nul bytes. If the + * given range is not entirely contained in the string, returns + * #FALSE. + * + * @todo this is inconsistent with most of DBusString in that + * it allows a start,len range that extends past the string end. + * + * @param str the string + * @param start first byte index to check + * @param len number of bytes to check + * @returns #TRUE if the byte range exists and is all nul bytes + */ +dbus_bool_t +_dbus_string_validate_nul (const DBusString *str, + int start, + int len) +{ + const unsigned char *s; + const unsigned char *end; + DBUS_CONST_STRING_PREAMBLE (str); + _dbus_assert (start >= 0); + _dbus_assert (len >= 0); + _dbus_assert (start <= real->len); + + if (len > real->len - start) + return FALSE; + + s = real->str + start; + end = s + len; + while (s != end) + { + if (_DBUS_UNLIKELY (*s != '\0')) + return FALSE; + ++s; + } + + return TRUE; +} + +/** + * Clears all allocated bytes in the string to zero. + * + * @param str the string + */ +void +_dbus_string_zero (DBusString *str) +{ + DBUS_STRING_PREAMBLE (str); + + memset (real->str - real->align_offset, '\0', real->allocated); +} +/** @} */ + +/* tests are in dbus-string-util.c */ diff --git a/src/3rdparty/libdbus/dbus/dbus-string.h b/src/3rdparty/libdbus/dbus/dbus-string.h new file mode 100644 index 00000000..be2434db --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-string.h @@ -0,0 +1,454 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-string.h String utility class (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2006 Ralf Habacker <ralf.habacker@freenet.de> + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_STRING_H +#define DBUS_STRING_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-memory.h> + +#include <stdarg.h> + +#include <dbus/dbus-macros-internal.h> + +DBUS_BEGIN_DECLS + +/** + * DBusString object + */ + +typedef struct DBusString DBusString; + +struct DBusString +{ +#if defined(DBUS_WIN) && defined(_DEBUG) + const char *dummy1; /**< placeholder */ +#else + const void *dummy1; /**< placeholder */ +#endif + int dummy2; /**< placeholder */ + int dummy3; /**< placeholder */ + unsigned int dummy_bit1 : 1; /**< placeholder */ + unsigned int dummy_bit2 : 1; /**< placeholder */ + unsigned int dummy_bit3 : 1; /**< placeholder */ + unsigned int dummy_bits : 3; /**< placeholder */ +}; + +/** + * Content for a DBusString that is considered invalid for all + * operations, except that it is valid to call _dbus_string_free() + * during error handling. + */ +#define _DBUS_STRING_INIT_INVALID \ +{ \ + NULL, /* dummy1 */ \ + 0, /* dummy2 */ \ + 0, /* dummy3 */ \ + 0, /* dummy_bit1 */ \ + 0, /* dummy_bit2 */ \ + 0, /* dummy_bit3 */ \ + 0 /* dummy_bits */ \ +} + +#ifdef DBUS_DISABLE_ASSERT +/* Some simple inlining hacks; the current linker is not smart enough + * to inline non-exported symbols across files in the library. + * Note that these break type safety (due to the casts) + */ +#define _dbus_string_get_data(s) ((char*)(((DBusString*)(s))->dummy1)) +#define _dbus_string_get_length(s) (((DBusString*)(s))->dummy2) +#define _dbus_string_set_byte(s, i, b) ((((unsigned char*)(((DBusString*)(s))->dummy1))[(i)]) = (unsigned char) (b)) +#define _dbus_string_get_byte(s, i) (((const unsigned char*)(((DBusString*)(s))->dummy1))[(i)]) +#define _dbus_string_get_const_data(s) ((const char*)(((DBusString*)(s))->dummy1)) +#define _dbus_string_get_const_data_len(s,start,len) (((const char*)(((DBusString*)(s))->dummy1)) + (start)) +#endif + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_init (DBusString *str); +DBUS_PRIVATE_EXPORT +void _dbus_string_init_const (DBusString *str, + const char *value); +DBUS_PRIVATE_EXPORT +void _dbus_string_init_const_len (DBusString *str, + const char *value, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_init_preallocated (DBusString *str, + int allocate_size); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_init_from_string (DBusString *str, + const DBusString *from); +DBUS_PRIVATE_EXPORT +void _dbus_string_free (DBusString *str); +void _dbus_string_lock (DBusString *str); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_compact (DBusString *str, + int max_waste); +DBUS_PRIVATE_EXPORT +int _dbus_string_get_allocated_size (const DBusString *str); +#ifndef _dbus_string_get_data +DBUS_PRIVATE_EXPORT +char* _dbus_string_get_data (DBusString *str); +#endif /* _dbus_string_get_data */ +#ifndef _dbus_string_get_const_data +DBUS_PRIVATE_EXPORT +const char* _dbus_string_get_const_data (const DBusString *str); +#endif /* _dbus_string_get_const_data */ +DBUS_PRIVATE_EXPORT +char* _dbus_string_get_data_len (DBusString *str, + int start, + int len); +#ifndef _dbus_string_get_const_data_len +DBUS_PRIVATE_EXPORT +const char* _dbus_string_get_const_data_len (const DBusString *str, + int start, + int len); +#endif +#ifndef _dbus_string_set_byte +DBUS_PRIVATE_EXPORT +void _dbus_string_set_byte (DBusString *str, + int i, + unsigned char byte); +#endif +#ifndef _dbus_string_get_byte +DBUS_PRIVATE_EXPORT +unsigned char _dbus_string_get_byte (const DBusString *str, + int start); +#endif /* _dbus_string_get_byte */ +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_insert_bytes (DBusString *str, + int i, + int n_bytes, + unsigned char byte); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_insert_byte (DBusString *str, + int i, + unsigned char byte); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_steal_data (DBusString *str, + char **data_return); +dbus_bool_t _dbus_string_steal_data_len (DBusString *str, + char **data_return, + int start, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_copy_data (const DBusString *str, + char **data_return); +dbus_bool_t _dbus_string_copy_data_len (const DBusString *str, + char **data_return, + int start, + int len); +void _dbus_string_copy_to_buffer (const DBusString *str, + char *buffer, + int len); +DBUS_PRIVATE_EXPORT +void _dbus_string_copy_to_buffer_with_nul (const DBusString *str, + char *buffer, + int avail_len); +#ifndef _dbus_string_get_length +DBUS_PRIVATE_EXPORT +int _dbus_string_get_length (const DBusString *str); +#endif /* !_dbus_string_get_length */ + +/** + * Get the string's length as an unsigned integer, for comparison with + * size_t and similar unsigned types that does not trigger compiler + * warnings about potential value changes during conversion. + * + * DBusString lengths are signed for historical reasons, but we know that + * the length is always >= 0 (and DBUS_GENERIC_STRING_PREAMBLE asserts + * that this is the case) so we know that this cast does not change the + * value. + */ +static inline unsigned int +_dbus_string_get_length_uint (const DBusString *str) +{ + return (unsigned int) _dbus_string_get_length (str); +} + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_lengthen (DBusString *str, + int additional_length); +DBUS_PRIVATE_EXPORT +void _dbus_string_shorten (DBusString *str, + int length_to_remove); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_set_length (DBusString *str, + int length); +dbus_bool_t _dbus_string_align_length (DBusString *str, + int alignment); +dbus_bool_t _dbus_string_alloc_space (DBusString *str, + int extra_bytes); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_append (DBusString *str, + const char *buffer); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_append_len (DBusString *str, + const char *buffer, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_append_int (DBusString *str, + long value); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_append_uint (DBusString *str, + unsigned long value); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_append_byte (DBusString *str, + unsigned char byte); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_append_strings (DBusString *str, + char **strings, + char separator); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_append_printf (DBusString *str, + const char *format, + ...) _DBUS_GNUC_PRINTF (2, 3); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_append_printf_valist (DBusString *str, + const char *format, + va_list args) _DBUS_GNUC_PRINTF (2, 0); +dbus_bool_t _dbus_string_insert_2_aligned (DBusString *str, + int insert_at, + const unsigned char octets[2]); +dbus_bool_t _dbus_string_insert_4_aligned (DBusString *str, + int insert_at, + const unsigned char octets[4]); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_insert_8_aligned (DBusString *str, + int insert_at, + const unsigned char octets[8]); +dbus_bool_t _dbus_string_insert_alignment (DBusString *str, + int *insert_at, + int alignment); +DBUS_PRIVATE_EXPORT +void _dbus_string_delete (DBusString *str, + int start, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_move (DBusString *source, + int start, + DBusString *dest, + int insert_at); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_copy (const DBusString *source, + int start, + DBusString *dest, + int insert_at); +dbus_bool_t _dbus_string_move_len (DBusString *source, + int start, + int len, + DBusString *dest, + int insert_at); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_copy_len (const DBusString *source, + int start, + int len, + DBusString *dest, + int insert_at); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_replace_len (const DBusString *source, + int start, + int len, + DBusString *dest, + int replace_at, + int replace_len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_split_on_byte (DBusString *source, + unsigned char byte, + DBusString *tail); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_parse_int (const DBusString *str, + int start, + long *value_return, + int *end_return); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_parse_uint (const DBusString *str, + int start, + unsigned long *value_return, + int *end_return); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_parse_int64 (const DBusString *str, + int start, + dbus_int64_t *value_return, + int *end_return); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_find (const DBusString *str, + int start, + const char *substr, + int *found); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_find_eol (const DBusString *str, + int start, + int *found, + int *found_len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_find_to (const DBusString *str, + int start, + int end, + const char *substr, + int *found); +dbus_bool_t _dbus_string_find_byte_backward (const DBusString *str, + int start, + unsigned char byte, + int *found); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_find_blank (const DBusString *str, + int start, + int *found); +DBUS_PRIVATE_EXPORT +void _dbus_string_skip_blank (const DBusString *str, + int start, + int *end); +DBUS_PRIVATE_EXPORT +void _dbus_string_skip_white (const DBusString *str, + int start, + int *end); +void _dbus_string_skip_white_reverse (const DBusString *str, + int end, + int *start); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_equal (const DBusString *a, + const DBusString *b); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_equal_c_str (const DBusString *a, + const char *c_str); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_equal_len (const DBusString *a, + const DBusString *b, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_equal_substring (const DBusString *a, + int a_start, + int a_len, + const DBusString *b, + int b_start); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_starts_with_c_str (const DBusString *a, + const char *c_str); +dbus_bool_t _dbus_string_ends_with_c_str (const DBusString *a, + const char *c_str); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_starts_with_words_c_str (const DBusString *a, + const char *c_str, + char word_separator); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_pop_line (DBusString *source, + DBusString *dest); +DBUS_PRIVATE_EXPORT +void _dbus_string_delete_first_word (DBusString *str); +DBUS_PRIVATE_EXPORT +void _dbus_string_delete_leading_blanks (DBusString *str); +DBUS_PRIVATE_EXPORT +void _dbus_string_chop_white (DBusString *str); +dbus_bool_t _dbus_string_append_byte_as_hex (DBusString *str, + unsigned char byte); +DBUS_EMBEDDED_TESTS_EXPORT +dbus_bool_t _dbus_string_append_buffer_as_hex (DBusString *str, + void *buf, + int size); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_hex_encode (const DBusString *source, + int start, + DBusString *dest, + int insert_at); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_hex_decode (const DBusString *source, + int start, + int *end_return, + DBusString *dest, + int insert_at); +DBUS_PRIVATE_EXPORT +void _dbus_string_tolower_ascii (const DBusString *str, + int start, + int len); +DBUS_PRIVATE_EXPORT +void _dbus_string_toupper_ascii (const DBusString *str, + int start, + int len); +dbus_bool_t _dbus_string_validate_ascii (const DBusString *str, + int start, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_validate_utf8 (const DBusString *str, + int start, + int len); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_string_validate_nul (const DBusString *str, + int start, + int len); +void _dbus_string_zero (DBusString *str); + +static inline unsigned char * +_dbus_string_get_udata (DBusString *str) +{ + return (unsigned char *) _dbus_string_get_data (str); +} + +static inline unsigned char * +_dbus_string_get_udata_len (DBusString *str, int start, int len) +{ + return (unsigned char *) _dbus_string_get_data_len (str, start, len); +} + +static inline const unsigned char * +_dbus_string_get_const_udata (const DBusString *str) +{ + return (const unsigned char *) _dbus_string_get_const_data (str); +} + +static inline const unsigned char * +_dbus_string_get_const_udata_len (const DBusString *str, int start, int len) +{ + return (const unsigned char *) _dbus_string_get_const_data_len (str, start, len); +} + +/** + * We allocate 1 byte for nul termination, plus 7 bytes for possible + * align_offset, so we always need 8 bytes on top of the string's + * length to be in the allocated block. + */ +#define _DBUS_STRING_ALLOCATION_PADDING 8 + +/** + * Defines a static const variable with type #DBusString called "name" + * containing the given string literal. + * + * @param name the name of the variable + * @param str the string value + */ +#define _DBUS_STRING_DEFINE_STATIC(name, str) \ + static const char _dbus_static_string_##name[] = str; \ + static const DBusString name = { _dbus_static_string_##name, \ + sizeof(_dbus_static_string_##name) - 1, \ + sizeof(_dbus_static_string_##name) + \ + _DBUS_STRING_ALLOCATION_PADDING, \ + TRUE, TRUE, TRUE, 0 } + +DBUS_END_DECLS + +#endif /* DBUS_STRING_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-syntax.c b/src/3rdparty/libdbus/dbus/dbus-syntax.c new file mode 100644 index 00000000..288824fe --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-syntax.c @@ -0,0 +1,311 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-syntax.c - utility functions for strings with special syntax + * + * Author: Simon McVittie <simon.mcvittie@collabora.co.uk> + * Copyright © 2011 Nokia Corporation + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-syntax.h" + +#include "dbus-internals.h" +#include "dbus-marshal-validate.h" +#include "dbus-shared.h" + +/** + * @defgroup DBusSyntax Utility functions for strings with special syntax + * @ingroup DBus + * @brief Parsing D-Bus type signatures + * @{ + */ + +/** + * Check an object path for validity. Remember that #NULL can always + * be passed instead of a DBusError *, if you don't care about having + * an error name and message. + * + * This function is suitable for validating C strings, but is not suitable + * for validating untrusted data from a network unless the string's length + * is also checked, since it assumes that the string ends at the first zero + * byte according to normal C conventions. + * + * @param path a potentially invalid object path, which must not be #NULL + * @param error error return + * @returns #TRUE if path is valid + */ +dbus_bool_t +dbus_validate_path (const char *path, + DBusError *error) +{ + DBusString str; + int len; + + _dbus_return_val_if_fail (path != NULL, FALSE); + + _dbus_string_init_const (&str, path); + len = _dbus_string_get_length (&str); + + /* In general, it ought to be valid... */ + if (_DBUS_LIKELY (_dbus_validate_path (&str, 0, len))) + return TRUE; + + /* slow path: string is invalid, find out why */ + + if (!_dbus_string_validate_utf8 (&str, 0, len)) + { + /* don't quote the actual string here, since a DBusError also needs to + * be valid UTF-8 */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Object path was not valid UTF-8"); + return FALSE; + } + + /* FIXME: later, diagnose exactly how it was invalid */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Object path was not valid: '%s'", path); + return FALSE; +} + +/** + * Check an interface name for validity. Remember that #NULL can always + * be passed instead of a DBusError *, if you don't care about having + * an error name and message. + * + * This function is suitable for validating C strings, but is not suitable + * for validating untrusted data from a network unless the string's length + * is also checked, since it assumes that the string ends at the first zero + * byte according to normal C conventions. + * + * @param name a potentially invalid interface name, which must not be #NULL + * @param error error return + * @returns #TRUE if name is valid + */ +dbus_bool_t +dbus_validate_interface (const char *name, + DBusError *error) +{ + DBusString str; + int len; + + _dbus_return_val_if_fail (name != NULL, FALSE); + + _dbus_string_init_const (&str, name); + len = _dbus_string_get_length (&str); + + /* In general, it ought to be valid... */ + if (_DBUS_LIKELY (_dbus_validate_interface (&str, 0, len))) + return TRUE; + + /* slow path: string is invalid, find out why */ + + if (!_dbus_string_validate_utf8 (&str, 0, len)) + { + /* don't quote the actual string here, since a DBusError also needs to + * be valid UTF-8 */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Interface name was not valid UTF-8"); + return FALSE; + } + + /* FIXME: later, diagnose exactly how it was invalid */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Interface name was not valid: '%s'", name); + return FALSE; +} + +/** + * Check a member (method/signal) name for validity. Remember that #NULL + * can always be passed instead of a DBusError *, if you don't care about + * having an error name and message. + * + * This function is suitable for validating C strings, but is not suitable + * for validating untrusted data from a network unless the string's length + * is also checked, since it assumes that the string ends at the first zero + * byte according to normal C conventions. + * + * @param name a potentially invalid member name, which must not be #NULL + * @param error error return + * @returns #TRUE if name is valid + */ +dbus_bool_t +dbus_validate_member (const char *name, + DBusError *error) +{ + DBusString str; + int len; + + _dbus_return_val_if_fail (name != NULL, FALSE); + + _dbus_string_init_const (&str, name); + len = _dbus_string_get_length (&str); + + /* In general, it ought to be valid... */ + if (_DBUS_LIKELY (_dbus_validate_member (&str, 0, len))) + return TRUE; + + /* slow path: string is invalid, find out why */ + + if (!_dbus_string_validate_utf8 (&str, 0, len)) + { + /* don't quote the actual string here, since a DBusError also needs to + * be valid UTF-8 */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Member name was not valid UTF-8"); + return FALSE; + } + + /* FIXME: later, diagnose exactly how it was invalid */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Member name was not valid: '%s'", name); + return FALSE; +} + +/** + * Check an error name for validity. Remember that #NULL + * can always be passed instead of a DBusError *, if you don't care about + * having an error name and message. + * + * This function is suitable for validating C strings, but is not suitable + * for validating untrusted data from a network unless the string's length + * is also checked, since it assumes that the string ends at the first zero + * byte according to normal C conventions. + * + * @param name a potentially invalid error name, which must not be #NULL + * @param error error return + * @returns #TRUE if name is valid + */ +dbus_bool_t +dbus_validate_error_name (const char *name, + DBusError *error) +{ + DBusString str; + int len; + + _dbus_return_val_if_fail (name != NULL, FALSE); + + _dbus_string_init_const (&str, name); + len = _dbus_string_get_length (&str); + + /* In general, it ought to be valid... */ + if (_DBUS_LIKELY (_dbus_validate_error_name (&str, 0, len))) + return TRUE; + + /* slow path: string is invalid, find out why */ + + if (!_dbus_string_validate_utf8 (&str, 0, len)) + { + /* don't quote the actual string here, since a DBusError also needs to + * be valid UTF-8 */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Error name was not valid UTF-8"); + return FALSE; + } + + /* FIXME: later, diagnose exactly how it was invalid */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Error name was not valid: '%s'", name); + return FALSE; +} + +/** + * Check a bus name for validity. Remember that #NULL + * can always be passed instead of a DBusError *, if you don't care about + * having an error name and message. + * + * This function is suitable for validating C strings, but is not suitable + * for validating untrusted data from a network unless the string's length + * is also checked, since it assumes that the string ends at the first zero + * byte according to normal C conventions. + * + * @param name a potentially invalid bus name, which must not be #NULL + * @param error error return + * @returns #TRUE if name is valid + */ +dbus_bool_t +dbus_validate_bus_name (const char *name, + DBusError *error) +{ + DBusString str; + int len; + + _dbus_return_val_if_fail (name != NULL, FALSE); + + _dbus_string_init_const (&str, name); + len = _dbus_string_get_length (&str); + + /* In general, it ought to be valid... */ + if (_DBUS_LIKELY (_dbus_validate_bus_name (&str, 0, len))) + return TRUE; + + /* slow path: string is invalid, find out why */ + + if (!_dbus_string_validate_utf8 (&str, 0, len)) + { + /* don't quote the actual string here, since a DBusError also needs to + * be valid UTF-8 */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Bus name was not valid UTF-8"); + return FALSE; + } + + /* FIXME: later, diagnose exactly how it was invalid */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Bus name was not valid: '%s'", name); + return FALSE; +} + +/** + * Check a string for validity. Strings on D-Bus must be valid UTF-8. + * Remember that #NULL can always be passed instead of a DBusError *, + * if you don't care about having an error name and message. + * + * This function is suitable for validating C strings, but is not suitable + * for validating untrusted data from a network unless the string's length + * is also checked, since it assumes that the string ends at the first zero + * byte according to normal C conventions. + * + * @param alleged_utf8 a string to be checked, which must not be #NULL + * @param error error return + * @returns #TRUE if alleged_utf8 is valid UTF-8 + */ +dbus_bool_t +dbus_validate_utf8 (const char *alleged_utf8, + DBusError *error) +{ + DBusString str; + + _dbus_return_val_if_fail (alleged_utf8 != NULL, FALSE); + + _dbus_string_init_const (&str, alleged_utf8); + + if (_DBUS_LIKELY (_dbus_string_validate_utf8 (&str, 0, + _dbus_string_get_length (&str)))) + return TRUE; + + /* don't quote the actual string here, since a DBusError also needs to + * be valid UTF-8 */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "String was not valid UTF-8"); + return FALSE; +} + +/** @} */ /* end of group */ diff --git a/src/3rdparty/libdbus/dbus/dbus-syntax.h b/src/3rdparty/libdbus/dbus/dbus-syntax.h new file mode 100644 index 00000000..57459b17 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-syntax.h @@ -0,0 +1,60 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-syntax.h - utility functions for strings with special syntax + * + * Author: Simon McVittie <simon.mcvittie@collabora.co.uk> + * Copyright © 2011 Nokia Corporation + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_SYNTAX_H +#define DBUS_SYNTAX_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-errors.h> + +DBUS_BEGIN_DECLS + +DBUS_EXPORT +dbus_bool_t dbus_validate_path (const char *path, + DBusError *error); +DBUS_EXPORT +dbus_bool_t dbus_validate_interface (const char *name, + DBusError *error); +DBUS_EXPORT +dbus_bool_t dbus_validate_member (const char *name, + DBusError *error); +DBUS_EXPORT +dbus_bool_t dbus_validate_error_name (const char *name, + DBusError *error); +DBUS_EXPORT +dbus_bool_t dbus_validate_bus_name (const char *name, + DBusError *error); +DBUS_EXPORT +dbus_bool_t dbus_validate_utf8 (const char *alleged_utf8, + DBusError *error); + +DBUS_END_DECLS + +#endif /* multiple-inclusion guard */ diff --git a/src/3rdparty/libdbus/dbus/dbus-sysdeps-pthread.c b/src/3rdparty/libdbus/dbus/dbus-sysdeps-pthread.c new file mode 100644 index 00000000..f9c25604 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-sysdeps-pthread.c @@ -0,0 +1,322 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sysdeps-pthread.c Implements threads using pthreads (internal to libdbus) + * + * Copyright (C) 2002, 2003, 2006 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-sysdeps.h" +#include "dbus-threads.h" + +#include <sys/time.h> +#include <pthread.h> +#include <stdio.h> +#include <string.h> + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif + +#include <config.h> + +#ifdef HAVE_MONOTONIC_CLOCK +/* Whether we have a "monotonic" clock; i.e. a clock not affected by + * changes in system time. + * This is initialized once in check_monotonic_clock below. + * https://bugs.freedesktop.org/show_bug.cgi?id=18121 + */ +static dbus_bool_t have_monotonic_clock = 0; +#endif + +struct DBusRMutex { + pthread_mutex_t lock; /**< the lock */ +}; + +struct DBusCMutex { + pthread_mutex_t lock; /**< the lock */ +}; + +struct DBusCondVar { + pthread_cond_t cond; /**< the condition */ +}; + +#define DBUS_MUTEX(m) ((DBusMutex*) m) +#define DBUS_MUTEX_PTHREAD(m) ((DBusMutexPThread*) m) + +#define DBUS_COND_VAR(c) ((DBusCondVar*) c) +#define DBUS_COND_VAR_PTHREAD(c) ((DBusCondVarPThread*) c) + + +#ifdef DBUS_DISABLE_ASSERT +/* (tmp != 0) is a no-op usage to silence compiler */ +#define PTHREAD_CHECK(func_name, result_or_call) \ + do { int tmp = (result_or_call); if (tmp != 0) {;} } while (0) +#else +#define PTHREAD_CHECK(func_name, result_or_call) do { \ + int tmp = (result_or_call); \ + if (tmp != 0) { \ + _dbus_warn_check_failed ("pthread function %s failed with %d %s in %s", \ + func_name, tmp, strerror(tmp), _DBUS_FUNCTION_NAME); \ + } \ +} while (0) +#endif /* !DBUS_DISABLE_ASSERT */ + +DBusCMutex * +_dbus_platform_cmutex_new (void) +{ + DBusCMutex *pmutex; + int result; + + pmutex = dbus_new (DBusCMutex, 1); + if (pmutex == NULL) + return NULL; + + result = pthread_mutex_init (&pmutex->lock, NULL); + + if (result == ENOMEM || result == EAGAIN) + { + dbus_free (pmutex); + return NULL; + } + else + { + PTHREAD_CHECK ("pthread_mutex_init", result); + } + + return pmutex; +} + +DBusRMutex * +_dbus_platform_rmutex_new (void) +{ + DBusRMutex *pmutex; + pthread_mutexattr_t mutexattr; + int result; + + pmutex = dbus_new (DBusRMutex, 1); + if (pmutex == NULL) + return NULL; + + pthread_mutexattr_init (&mutexattr); + pthread_mutexattr_settype (&mutexattr, PTHREAD_MUTEX_RECURSIVE); + result = pthread_mutex_init (&pmutex->lock, &mutexattr); + pthread_mutexattr_destroy (&mutexattr); + + if (result == ENOMEM || result == EAGAIN) + { + dbus_free (pmutex); + return NULL; + } + else + { + PTHREAD_CHECK ("pthread_mutex_init", result); + } + + return pmutex; +} + +void +_dbus_platform_cmutex_free (DBusCMutex *mutex) +{ + PTHREAD_CHECK ("pthread_mutex_destroy", pthread_mutex_destroy (&mutex->lock)); + dbus_free (mutex); +} + +void +_dbus_platform_rmutex_free (DBusRMutex *mutex) +{ + PTHREAD_CHECK ("pthread_mutex_destroy", pthread_mutex_destroy (&mutex->lock)); + dbus_free (mutex); +} + +void +_dbus_platform_cmutex_lock (DBusCMutex *mutex) +{ + PTHREAD_CHECK ("pthread_mutex_lock", pthread_mutex_lock (&mutex->lock)); +} + +void +_dbus_platform_rmutex_lock (DBusRMutex *mutex) +{ + PTHREAD_CHECK ("pthread_mutex_lock", pthread_mutex_lock (&mutex->lock)); +} + +void +_dbus_platform_cmutex_unlock (DBusCMutex *mutex) +{ + PTHREAD_CHECK ("pthread_mutex_unlock", pthread_mutex_unlock (&mutex->lock)); +} + +void +_dbus_platform_rmutex_unlock (DBusRMutex *mutex) +{ + PTHREAD_CHECK ("pthread_mutex_unlock", pthread_mutex_unlock (&mutex->lock)); +} + +DBusCondVar * +_dbus_platform_condvar_new (void) +{ + DBusCondVar *pcond; + pthread_condattr_t attr; + int result; + + pcond = dbus_new (DBusCondVar, 1); + if (pcond == NULL) + return NULL; + + pthread_condattr_init (&attr); +#ifdef HAVE_MONOTONIC_CLOCK + if (have_monotonic_clock) + pthread_condattr_setclock (&attr, CLOCK_MONOTONIC); +#endif + + result = pthread_cond_init (&pcond->cond, &attr); + pthread_condattr_destroy (&attr); + + if (result == EAGAIN || result == ENOMEM) + { + dbus_free (pcond); + return NULL; + } + else + { + PTHREAD_CHECK ("pthread_cond_init", result); + } + + return pcond; +} + +void +_dbus_platform_condvar_free (DBusCondVar *cond) +{ + PTHREAD_CHECK ("pthread_cond_destroy", pthread_cond_destroy (&cond->cond)); + dbus_free (cond); +} + +void +_dbus_platform_condvar_wait (DBusCondVar *cond, + DBusCMutex *mutex) +{ + PTHREAD_CHECK ("pthread_cond_wait", pthread_cond_wait (&cond->cond, &mutex->lock)); +} + +dbus_bool_t +_dbus_platform_condvar_wait_timeout (DBusCondVar *cond, + DBusCMutex *mutex, + int timeout_milliseconds) +{ + struct timeval time_now; + struct timespec end_time; + int result; + +#ifdef HAVE_MONOTONIC_CLOCK + if (have_monotonic_clock) + { + struct timespec monotonic_timer; + clock_gettime (CLOCK_MONOTONIC,&monotonic_timer); + time_now.tv_sec = monotonic_timer.tv_sec; + time_now.tv_usec = monotonic_timer.tv_nsec / 1000; + } + else + /* This else falls through to gettimeofday */ +#endif + gettimeofday (&time_now, NULL); + + end_time.tv_sec = time_now.tv_sec + timeout_milliseconds / 1000; + end_time.tv_nsec = (time_now.tv_usec + (timeout_milliseconds % 1000) * 1000) * 1000; + if (end_time.tv_nsec > 1000*1000*1000) + { + end_time.tv_sec += 1; + end_time.tv_nsec -= 1000*1000*1000; + } + + result = pthread_cond_timedwait (&cond->cond, &mutex->lock, &end_time); + + if (result != ETIMEDOUT) + { + PTHREAD_CHECK ("pthread_cond_timedwait", result); + } + + /* return true if we did not time out */ + return result != ETIMEDOUT; +} + +void +_dbus_platform_condvar_wake_one (DBusCondVar *cond) +{ + PTHREAD_CHECK ("pthread_cond_signal", pthread_cond_signal (&cond->cond)); +} + +static void +check_monotonic_clock (void) +{ +#ifdef HAVE_MONOTONIC_CLOCK + struct timespec dummy; + if (clock_getres (CLOCK_MONOTONIC, &dummy) == 0) + have_monotonic_clock = TRUE; +#endif +} + +dbus_bool_t +_dbus_threads_init_platform_specific (void) +{ + /* These have static variables, and we need to handle both the case + * where dbus_threads_init() has been called and when it hasn't; + * so initialize them before any threads are allowed to enter. + */ + check_monotonic_clock (); + (void) _dbus_check_setuid (); + + return TRUE; +} + +static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; + +void +_dbus_threads_lock_platform_specific (void) +{ + pthread_mutex_lock (&init_mutex); +} + +void +_dbus_threads_unlock_platform_specific (void) +{ + pthread_mutex_unlock (&init_mutex); +} + +#ifdef DBUS_ENABLE_VERBOSE_MODE +/* + * If we can identify the current process and/or thread, print them to stderr followed by a colon. + */ +void +_dbus_print_thread (void) +{ +#ifdef __linux__ + /* we know a pthread_t is numeric on Linux */ + fprintf (stderr, "%lu: 0x%lx: ", _dbus_pid_for_log (), (unsigned long) pthread_self ()); +#else + /* in principle pthread_t isn't required to be printable */ + fprintf (stderr, "%lu: ", _dbus_pid_for_log ()); +#endif +} +#endif diff --git a/src/3rdparty/libdbus/dbus/dbus-sysdeps-thread-win.c b/src/3rdparty/libdbus/dbus/dbus-sysdeps-thread-win.c new file mode 100644 index 00000000..9f1818f9 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-sysdeps-thread-win.c @@ -0,0 +1,339 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sysdeps-pthread.c Implements threads using Windows threads (internal to libdbus) + * + * Copyright (C) 2006 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-init-win.h" +#include "dbus-internals.h" +#include "dbus-sysdeps.h" +#include "dbus-sysdeps-win.h" +#include "dbus-threads.h" +#include "dbus-list.h" + +#include <stdio.h> + +#include <windows.h> + +#ifdef DBUS_DISABLE_ASSERT +#define THREAD_CHECK_TRUE(func_name, result_or_call) \ + do { if (!(result_or_call)) { /* ignore */ } } while (0) +#else +#define THREAD_CHECK_TRUE(func_name, result_or_call) do { \ + if (!(result_or_call)) { \ + _dbus_warn_check_failed ("thread function %s failed (windows error code=%ld) in %s", \ + func_name, GetLastError (), _DBUS_FUNCTION_NAME); \ + } \ +} while (0) +#endif /* !DBUS_DISABLE_ASSERT */ + +/* Protected by DllMain lock, effectively */ +static dbus_bool_t global_init_done = FALSE; +static CRITICAL_SECTION init_lock; + +/* Called from C++ code in dbus-init-win.cpp. */ +void +_dbus_threads_windows_init_global (void) +{ + /* this ensures that the object that acts as our global constructor + * actually gets linked in when we're linked statically */ + _dbus_threads_windows_ensure_ctor_linked (); + + InitializeCriticalSection (&init_lock); + global_init_done = TRUE; +} + +struct DBusCondVar { + DBusList *list; /**< list thread-local-stored events waiting on the cond variable */ + CRITICAL_SECTION lock; /**< lock protecting the list */ +}; + +static DWORD dbus_cond_event_tls = TLS_OUT_OF_INDEXES; + +/* Protected by DllMain lock, effectively */ +static HMODULE dbus_dll_hmodule; + +void * +_dbus_win_get_dll_hmodule (void) +{ + return dbus_dll_hmodule; +} + +#ifdef DBUS_WINCE +#define hinst_t HANDLE +#else +#define hinst_t HINSTANCE +#endif + +BOOL WINAPI DllMain (hinst_t, DWORD, LPVOID); + +/* We need this to free the TLS events on thread exit */ +BOOL WINAPI +DllMain (hinst_t hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + HANDLE event; + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + dbus_dll_hmodule = hinstDLL; + break; + case DLL_THREAD_DETACH: + if (dbus_cond_event_tls != TLS_OUT_OF_INDEXES) + { + event = TlsGetValue(dbus_cond_event_tls); + CloseHandle (event); + TlsSetValue(dbus_cond_event_tls, NULL); + } + break; + case DLL_PROCESS_DETACH: + if (dbus_cond_event_tls != TLS_OUT_OF_INDEXES) + { + event = TlsGetValue(dbus_cond_event_tls); + CloseHandle (event); + TlsSetValue(dbus_cond_event_tls, NULL); + + TlsFree(dbus_cond_event_tls); + } + break; + default: + break; + } + return TRUE; +} + +DBusCMutex * +_dbus_platform_cmutex_new (void) +{ + HANDLE handle; + handle = CreateMutex (NULL, FALSE, NULL); + THREAD_CHECK_TRUE ("CreateMutex", handle); + return (DBusCMutex *) handle; +} + +DBusRMutex * +_dbus_platform_rmutex_new (void) +{ + HANDLE handle; + handle = CreateMutex (NULL, FALSE, NULL); + THREAD_CHECK_TRUE ("CreateMutex", handle); + return (DBusRMutex *) handle; +} + +DBusRMutex * +_dbus_win_rmutex_named_new (const char *name) +{ + HANDLE handle; + handle = CreateMutex (NULL, FALSE, name); + THREAD_CHECK_TRUE ("CreateMutex", handle); + return (DBusRMutex *) handle; +} + +void +_dbus_platform_cmutex_free (DBusCMutex *mutex) +{ + THREAD_CHECK_TRUE ("CloseHandle", CloseHandle ((HANDLE *) mutex)); +} + +void +_dbus_platform_rmutex_free (DBusRMutex *mutex) +{ + THREAD_CHECK_TRUE ("CloseHandle", CloseHandle ((HANDLE *) mutex)); +} + +void +_dbus_platform_cmutex_lock (DBusCMutex *mutex) +{ + THREAD_CHECK_TRUE ("WaitForSingleObject", WaitForSingleObject ((HANDLE *) mutex, INFINITE) == WAIT_OBJECT_0); +} + +void +_dbus_platform_rmutex_lock (DBusRMutex *mutex) +{ + THREAD_CHECK_TRUE ("WaitForSingleObject", WaitForSingleObject ((HANDLE *) mutex, INFINITE) == WAIT_OBJECT_0); +} + +void +_dbus_platform_cmutex_unlock (DBusCMutex *mutex) +{ + THREAD_CHECK_TRUE ("ReleaseMutex", ReleaseMutex ((HANDLE *) mutex)); +} + +void +_dbus_platform_rmutex_unlock (DBusRMutex *mutex) +{ + THREAD_CHECK_TRUE ("ReleaseMutex", ReleaseMutex ((HANDLE *) mutex)); +} + +DBusCondVar * +_dbus_platform_condvar_new (void) +{ + DBusCondVar *cond; + + cond = dbus_new (DBusCondVar, 1); + if (cond == NULL) + return NULL; + + cond->list = NULL; + + InitializeCriticalSection (&cond->lock); + return cond; +} + +void +_dbus_platform_condvar_free (DBusCondVar *cond) +{ + DeleteCriticalSection (&cond->lock); + _dbus_list_clear (&cond->list); + dbus_free (cond); +} + +static dbus_bool_t +_dbus_condvar_wait_win32 (DBusCondVar *cond, + DBusCMutex *mutex, + int milliseconds) +{ + DWORD retval; + dbus_bool_t ret; + HANDLE event = TlsGetValue (dbus_cond_event_tls); + + if (!event) + { + event = CreateEvent (0, FALSE, FALSE, NULL); + if (event == 0) + return FALSE; + TlsSetValue (dbus_cond_event_tls, event); + } + + EnterCriticalSection (&cond->lock); + + /* The event must not be signaled. Check this */ + _dbus_assert (WaitForSingleObject (event, 0) == WAIT_TIMEOUT); + + ret = _dbus_list_append (&cond->list, event); + + LeaveCriticalSection (&cond->lock); + + if (!ret) + return FALSE; /* Prepend failed */ + + _dbus_platform_cmutex_unlock (mutex); + retval = WaitForSingleObject (event, milliseconds); + _dbus_platform_cmutex_lock (mutex); + + if (retval == WAIT_TIMEOUT) + { + EnterCriticalSection (&cond->lock); + _dbus_list_remove (&cond->list, event); + + /* In the meantime we could have been signaled, so we must again + * wait for the signal, this time with no timeout, to reset + * it. retval is set again to honour the late arrival of the + * signal */ + retval = WaitForSingleObject (event, 0); + + LeaveCriticalSection (&cond->lock); + } + +#ifndef DBUS_DISABLE_ASSERT + EnterCriticalSection (&cond->lock); + + /* Now event must not be inside the array, check this */ + _dbus_assert (_dbus_list_remove (&cond->list, event) == FALSE); + + LeaveCriticalSection (&cond->lock); +#endif /* !G_DISABLE_ASSERT */ + + return retval != WAIT_TIMEOUT; +} + +void +_dbus_platform_condvar_wait (DBusCondVar *cond, + DBusCMutex *mutex) +{ + _dbus_condvar_wait_win32 (cond, mutex, INFINITE); +} + +dbus_bool_t +_dbus_platform_condvar_wait_timeout (DBusCondVar *cond, + DBusCMutex *mutex, + int timeout_milliseconds) +{ + return _dbus_condvar_wait_win32 (cond, mutex, timeout_milliseconds); +} + +void +_dbus_platform_condvar_wake_one (DBusCondVar *cond) +{ + EnterCriticalSection (&cond->lock); + + if (cond->list != NULL) + { + SetEvent (_dbus_list_pop_first (&cond->list)); + /* Avoid live lock by pushing the waiter to the mutex lock + instruction, which is fair. If we don't do this, we could + acquire the condition variable again before the waiter has a + chance itself, leading to starvation. */ + Sleep (0); + } + LeaveCriticalSection (&cond->lock); +} + +dbus_bool_t +_dbus_threads_init_platform_specific (void) +{ + /* We reuse this over several generations, because we can't + * free the events once they are in use + */ + if (dbus_cond_event_tls == TLS_OUT_OF_INDEXES) + { + dbus_cond_event_tls = TlsAlloc (); + if (dbus_cond_event_tls == TLS_OUT_OF_INDEXES) + return FALSE; + } + + return TRUE; +} + +void +_dbus_threads_lock_platform_specific (void) +{ + _dbus_assert (global_init_done); + EnterCriticalSection (&init_lock); +} + +void +_dbus_threads_unlock_platform_specific (void) +{ + _dbus_assert (global_init_done); + LeaveCriticalSection (&init_lock); +} + +#ifdef DBUS_ENABLE_VERBOSE_MODE +void +_dbus_print_thread (void) +{ + fprintf (stderr, "%lu: 0x%04lx: ", _dbus_pid_for_log (), GetCurrentThreadId ()); +} +#endif diff --git a/src/3rdparty/libdbus/dbus/dbus-sysdeps-unix.c b/src/3rdparty/libdbus/dbus/dbus-sysdeps-unix.c new file mode 100644 index 00000000..cf99616d --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-sysdeps-unix.c @@ -0,0 +1,5273 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sysdeps-unix.c Wrappers around UNIX system/libc features (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003, 2006 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#include "dbus-internals.h" +#include "dbus-sysdeps.h" +#include "dbus-sysdeps-unix.h" +#include "dbus-threads.h" +#include "dbus-protocol.h" +#include "dbus-file.h" +#include "dbus-transport.h" +#include "dbus-string.h" +#include "dbus-userdb.h" +#include "dbus-list.h" +#include "dbus-credentials.h" +#include "dbus-nonce.h" + +#include <limits.h> +#include <sys/types.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> +#include <stdio.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <dirent.h> +#include <sys/un.h> +#include <pwd.h> +#include <time.h> +#include <locale.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <netdb.h> +#include <grp.h> +#include <arpa/inet.h> + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_LINUX_CLOSE_RANGE_H +#include <linux/close_range.h> +#endif +#ifdef HAVE_SYSLOG_H +#include <syslog.h> +#endif +#ifdef HAVE_WRITEV +#include <sys/uio.h> +#endif +#ifdef HAVE_BACKTRACE +#include <execinfo.h> +#endif +#ifdef HAVE_GETPEERUCRED +#include <ucred.h> +#endif +#ifdef HAVE_ALLOCA_H +#include <alloca.h> +#endif +#ifdef HAVE_SYS_RANDOM_H +#include <sys/random.h> +#endif +#ifdef HAVE_SYS_SYSCALL_H +#include <sys/syscall.h> +#endif + +#ifdef HAVE_ADT +#include <bsm/adt.h> +#endif + +#ifdef HAVE_SYSTEMD +#include <systemd/sd-daemon.h> +#endif + +#if !defined(HAVE_STDATOMIC_H) && !DBUS_USE_SYNC +#include <pthread.h> +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#ifndef AI_ADDRCONFIG +#define AI_ADDRCONFIG 0 +#endif + +#ifndef HAVE_SOCKLEN_T +#define socklen_t int +#endif + +#if defined (__sun) || defined (__sun__) +/* + * CMS_SPACE etc. definitions for Solaris < 10, based on + * http://mailman.videolan.org/pipermail/vlc-devel/2006-May/024402.html + * via + * http://wiki.opencsw.org/porting-faq#toc10 + * + * These are only redefined for Solaris, for now: if your OS needs these too, + * please file a bug. (Or preferably, improve your OS so they're not needed.) + */ + +# ifndef CMSG_ALIGN +# ifdef __sun__ +# define CMSG_ALIGN(len) _CMSG_DATA_ALIGN (len) +# else + /* aligning to sizeof (long) is assumed to be portable (fd.o#40235) */ +# define CMSG_ALIGN(len) (((len) + sizeof (long) - 1) & \ + ~(sizeof (long) - 1)) +# endif +# endif + +# ifndef CMSG_SPACE +# define CMSG_SPACE(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + \ + CMSG_ALIGN (len)) +# endif + +# ifndef CMSG_LEN +# define CMSG_LEN(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len)) +# endif + +#endif /* Solaris */ + +#if defined(__linux__) && defined(__NR_close_range) && !defined(HAVE_CLOSE_RANGE) +/* The kernel headers are new enough to have the close_range syscall, + * but glibc isn't new enough to have the syscall wrapper, so call the + * syscall directly. */ +static inline int +close_range (unsigned int first, + unsigned int last, + int flags) +{ + return syscall (__NR_close_range, first, last, flags); +} +/* Now we can call that inline wrapper as though it was provided by glibc. */ +#define HAVE_CLOSE_RANGE +#endif + +/** + * Ensure that the standard file descriptors stdin, stdout and stderr + * are open, by opening /dev/null if necessary. + * + * This function does not use DBusError, to avoid calling malloc(), so + * that it can be used in contexts where an async-signal-safe function + * is required (for example after fork()). Instead, on failure it sets + * errno and returns something like "Failed to open /dev/null" in + * *error_str_p. Callers are expected to combine *error_str_p + * with _dbus_strerror (errno) to get a full error report. + * + * This function can only be called while single-threaded: either during + * startup of an executable, or after fork(). + */ +dbus_bool_t +_dbus_ensure_standard_fds (DBusEnsureStandardFdsFlags flags, + const char **error_str_p) +{ + static int const relevant_flag[] = { DBUS_FORCE_STDIN_NULL, + DBUS_FORCE_STDOUT_NULL, + DBUS_FORCE_STDERR_NULL }; + /* Should always get replaced with the real error before use */ + const char *error_str = "Failed mysteriously"; + int devnull = -1; + int saved_errno; + /* This function relies on the standard fds having their POSIX values. */ + _DBUS_STATIC_ASSERT (STDIN_FILENO == 0); + _DBUS_STATIC_ASSERT (STDOUT_FILENO == 1); + _DBUS_STATIC_ASSERT (STDERR_FILENO == 2); + int i; + + for (i = STDIN_FILENO; i <= STDERR_FILENO; i++) + { + /* Because we rely on being single-threaded, and we want the + * standard fds to not be close-on-exec, we don't set it + * close-on-exec. */ + if (devnull < i) + devnull = open ("/dev/null", O_RDWR); + + if (devnull < 0) + { + error_str = "Failed to open /dev/null"; + goto out; + } + + /* We already opened all fds < i, so the only way this assertion + * could fail is if another thread closed one, and we document + * this function as not safe for multi-threading. */ + _dbus_assert (devnull >= i); + + if (devnull != i && (flags & relevant_flag[i]) != 0) + { + if (dup2 (devnull, i) < 0) + { + error_str = "Failed to dup2 /dev/null onto a standard fd"; + goto out; + } + } + } + + error_str = NULL; + +out: + saved_errno = errno; + + if (devnull > STDERR_FILENO) + close (devnull); + + if (error_str_p != NULL) + *error_str_p = error_str; + + errno = saved_errno; + return (error_str == NULL); +} + +static dbus_bool_t _dbus_set_fd_nonblocking (int fd, + DBusError *error); + +static dbus_bool_t +_dbus_open_socket (int *fd_p, + int domain, + int type, + int protocol, + DBusError *error) +{ +#ifdef SOCK_CLOEXEC + dbus_bool_t cloexec_done; + + *fd_p = socket (domain, type | SOCK_CLOEXEC, protocol); + cloexec_done = *fd_p >= 0; + + /* Check if kernel seems to be too old to know SOCK_CLOEXEC */ + if (*fd_p < 0 && (errno == EINVAL || errno == EPROTOTYPE)) +#endif + { + *fd_p = socket (domain, type, protocol); + } + + if (*fd_p >= 0) + { +#ifdef SOCK_CLOEXEC + if (!cloexec_done) +#endif + { + _dbus_fd_set_close_on_exec(*fd_p); + } + + _dbus_verbose ("socket fd %d opened\n", *fd_p); + return TRUE; + } + else + { + dbus_set_error(error, + _dbus_error_from_errno (errno), + "Failed to open socket: %s", + _dbus_strerror (errno)); + return FALSE; + } +} + +/** + * Opens a UNIX domain socket (as in the socket() call). + * Does not bind the socket. + * + * This will set FD_CLOEXEC for the socket returned + * + * @param fd return location for socket descriptor + * @param error return location for an error + * @returns #FALSE if error is set + */ +static dbus_bool_t +_dbus_open_unix_socket (int *fd, + DBusError *error) +{ + return _dbus_open_socket(fd, AF_UNIX, SOCK_STREAM, 0, error); +} + +/** + * Closes a socket and invalidates it. Should not be used on non-socket file + * descriptors or handles. + * + * If an error is detected, this function returns #FALSE and sets the error, but + * the socket is still closed and invalidated. Callers can use the error in a + * diagnostic message, but should not retry closing the socket. + * + * @param fd the socket + * @param error return location for an error + * @returns #FALSE if error is set + */ +dbus_bool_t +_dbus_close_socket (DBusSocket *fd, + DBusError *error) +{ + dbus_bool_t rv; + + _dbus_assert (fd != NULL); + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + rv = _dbus_close (fd->fd, error); + _dbus_socket_invalidate (fd); + + return rv; +} + +/** + * Like _dbus_read(), but only works on sockets so is + * available on Windows. + * + * @param fd the socket + * @param buffer string to append data to + * @param count max amount of data to read + * @returns number of bytes appended to the string + */ +int +_dbus_read_socket (DBusSocket fd, + DBusString *buffer, + int count) +{ + return _dbus_read (fd.fd, buffer, count); +} + +/** + * Like _dbus_write(), but only supports sockets + * and is thus available on Windows. + * + * @param fd the file descriptor to write + * @param buffer the buffer to write data from + * @param start the first byte in the buffer to write + * @param len the number of bytes to try to write + * @returns the number of bytes written or -1 on error + */ +int +_dbus_write_socket (DBusSocket fd, + const DBusString *buffer, + int start, + int len) +{ +#if HAVE_DECL_MSG_NOSIGNAL + const char *data; + int bytes_written; + + data = _dbus_string_get_const_data_len (buffer, start, len); + + again: + + bytes_written = send (fd.fd, data, len, MSG_NOSIGNAL); + + if (bytes_written < 0 && errno == EINTR) + goto again; + + return bytes_written; + +#else + return _dbus_write (fd.fd, buffer, start, len); +#endif +} + +/** + * Like _dbus_read_socket() but also tries to read unix fds from the + * socket. When there are more fds to read than space in the array + * passed this function will fail with ENOSPC. + * + * @param fd the socket + * @param buffer string to append data to + * @param count max amount of data to read + * @param fds array to place read file descriptors in + * @param n_fds on input space in fds array, on output how many fds actually got read + * @returns number of bytes appended to string + */ +int +_dbus_read_socket_with_unix_fds (DBusSocket fd, + DBusString *buffer, + int count, + int *fds, + unsigned int *n_fds) { +#ifndef HAVE_UNIX_FD_PASSING + int r; + + if ((r = _dbus_read_socket(fd, buffer, count)) < 0) + return r; + + *n_fds = 0; + return r; + +#else + int bytes_read; + int start; + struct msghdr m; + struct iovec iov; + + _dbus_assert (count >= 0); + _dbus_assert (*n_fds <= DBUS_MAXIMUM_MESSAGE_UNIX_FDS); + + start = _dbus_string_get_length (buffer); + + if (!_dbus_string_lengthen (buffer, count)) + { + errno = ENOMEM; + return -1; + } + + _DBUS_ZERO(iov); + iov.iov_base = _dbus_string_get_data_len (buffer, start, count); + iov.iov_len = count; + + _DBUS_ZERO(m); + m.msg_iov = &iov; + m.msg_iovlen = 1; + + /* Hmm, we have no clue how long the control data will actually be + that is queued for us. The least we can do is assume that the + caller knows. Hence let's make space for the number of fds that + we shall read at max plus the cmsg header. */ + m.msg_controllen = CMSG_SPACE(*n_fds * sizeof(int)); + + /* It's probably safe to assume that systems with SCM_RIGHTS also + know alloca() */ + m.msg_control = alloca(m.msg_controllen); + memset(m.msg_control, 0, m.msg_controllen); + + /* Do not include the padding at the end when we tell the kernel + * how much we're willing to receive. This avoids getting + * the padding filled with additional fds that we weren't expecting, + * if a (potentially malicious) sender included them. (fd.o #83622) */ + m.msg_controllen = CMSG_LEN (*n_fds * sizeof(int)); + + again: + + bytes_read = recvmsg (fd.fd, &m, 0 +#ifdef MSG_CMSG_CLOEXEC + |MSG_CMSG_CLOEXEC +#endif + ); + + if (bytes_read < 0) + { + if (errno == EINTR) + goto again; + else + { + /* put length back (note that this doesn't actually realloc anything) */ + _dbus_string_set_length (buffer, start); + return -1; + } + } + else + { + struct cmsghdr *cm; + dbus_bool_t found = FALSE; + + for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) + if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_RIGHTS) + { + size_t i; + int *payload = (int *) (void *) CMSG_DATA (cm); + size_t payload_len_bytes = (cm->cmsg_len - CMSG_LEN (0)); + size_t payload_len_fds; + size_t fds_to_use; + + /* Every unsigned int fits in a size_t without truncation, so + * casting (size_t) *n_fds is OK */ + _DBUS_STATIC_ASSERT (sizeof (size_t) >= sizeof (unsigned int)); + + if ((m.msg_flags & MSG_CTRUNC) && CMSG_NXTHDR(&m, cm) == NULL && + (char *) payload + payload_len_bytes > + (char *) m.msg_control + m.msg_controllen) + { + /* This is the last cmsg in a truncated message and using + * cmsg_len would apparently overrun the allocated buffer. + * Some operating systems (illumos and Solaris are known) do + * not adjust cmsg_len in the last cmsg when truncation occurs. + * Adjust the payload length here. The calculation for + * payload_len_fds below will discard any trailing bytes that + * belong to an incomplete file descriptor - the kernel will + * have already closed that (at least for illumos and Solaris) + */ + payload_len_bytes = m.msg_controllen - + ((char *) payload - (char *) m.msg_control); + } + + payload_len_fds = payload_len_bytes / sizeof (int); + + if (_DBUS_LIKELY (payload_len_fds <= (size_t) *n_fds)) + { + /* The fds in the payload will fit in our buffer */ + fds_to_use = payload_len_fds; + } + else + { + /* Too many fds in the payload. This shouldn't happen + * any more because we're setting m.msg_controllen to + * the exact number we can accept, but be safe and + * truncate. */ + fds_to_use = (size_t) *n_fds; + + /* Close the excess fds to avoid DoS: if they stayed open, + * someone could send us an extra fd per message + * and we'd eventually run out. */ + for (i = fds_to_use; i < payload_len_fds; i++) + { + close (payload[i]); + } + } + + memcpy (fds, payload, fds_to_use * sizeof (int)); + found = TRUE; + /* This narrowing cast from size_t to unsigned int cannot + * overflow because we have chosen fds_to_use + * to be <= *n_fds */ + *n_fds = (unsigned int) fds_to_use; + + /* Linux doesn't tell us whether MSG_CMSG_CLOEXEC actually + worked, hence we need to go through this list and set + CLOEXEC everywhere in any case */ + for (i = 0; i < fds_to_use; i++) + _dbus_fd_set_close_on_exec(fds[i]); + + break; + } + + if (!found) + *n_fds = 0; + + if (m.msg_flags & MSG_CTRUNC) + { + unsigned int i; + + /* Hmm, apparently the control data was truncated. The bad + thing is that we might have completely lost a couple of fds + without chance to recover them. Hence let's treat this as a + serious error. */ + + /* We still need to close whatever fds we *did* receive, + * otherwise they'll never get closed. (CVE-2020-12049) */ + for (i = 0; i < *n_fds; i++) + close (fds[i]); + + *n_fds = 0; + errno = ENOSPC; + _dbus_string_set_length (buffer, start); + return -1; + } + + /* put length back (doesn't actually realloc) */ + _dbus_string_set_length (buffer, start + bytes_read); + +#if 0 + if (bytes_read > 0) + _dbus_verbose_bytes_of_string (buffer, start, bytes_read); +#endif + + return bytes_read; + } +#endif +} + +int +_dbus_write_socket_with_unix_fds(DBusSocket fd, + const DBusString *buffer, + int start, + int len, + const int *fds, + int n_fds) { + +#ifndef HAVE_UNIX_FD_PASSING + + if (n_fds > 0) { + errno = ENOTSUP; + return -1; + } + + return _dbus_write_socket(fd, buffer, start, len); +#else + return _dbus_write_socket_with_unix_fds_two(fd, buffer, start, len, NULL, 0, 0, fds, n_fds); +#endif +} + +int +_dbus_write_socket_with_unix_fds_two(DBusSocket fd, + const DBusString *buffer1, + int start1, + int len1, + const DBusString *buffer2, + int start2, + int len2, + const int *fds, + int n_fds) { + +#ifndef HAVE_UNIX_FD_PASSING + + if (n_fds > 0) { + errno = ENOTSUP; + return -1; + } + + return _dbus_write_socket_two(fd, + buffer1, start1, len1, + buffer2, start2, len2); +#else + + struct msghdr m; + struct cmsghdr *cm; + struct iovec iov[2]; + int bytes_written; + + _dbus_assert (len1 >= 0); + _dbus_assert (len2 >= 0); + _dbus_assert (n_fds >= 0); + + _DBUS_ZERO(iov); + iov[0].iov_base = (char*) _dbus_string_get_const_data_len (buffer1, start1, len1); + iov[0].iov_len = len1; + + if (buffer2) + { + iov[1].iov_base = (char*) _dbus_string_get_const_data_len (buffer2, start2, len2); + iov[1].iov_len = len2; + } + + _DBUS_ZERO(m); + m.msg_iov = iov; + m.msg_iovlen = buffer2 ? 2 : 1; + + if (n_fds > 0) + { + m.msg_controllen = CMSG_SPACE(n_fds * sizeof(int)); + m.msg_control = alloca(m.msg_controllen); + memset(m.msg_control, 0, m.msg_controllen); + + cm = CMSG_FIRSTHDR(&m); + cm->cmsg_level = SOL_SOCKET; + cm->cmsg_type = SCM_RIGHTS; + cm->cmsg_len = CMSG_LEN(n_fds * sizeof(int)); + memcpy(CMSG_DATA(cm), fds, n_fds * sizeof(int)); + } + + again: + + bytes_written = sendmsg (fd.fd, &m, 0 +#if HAVE_DECL_MSG_NOSIGNAL + |MSG_NOSIGNAL +#endif + ); + + if (bytes_written < 0 && errno == EINTR) + goto again; + +#if 0 + if (bytes_written > 0) + _dbus_verbose_bytes_of_string (buffer, start, bytes_written); +#endif + + return bytes_written; +#endif +} + +/** + * Like _dbus_write_two() but only works on sockets and is thus + * available on Windows. + * + * @param fd the file descriptor + * @param buffer1 first buffer + * @param start1 first byte to write in first buffer + * @param len1 number of bytes to write from first buffer + * @param buffer2 second buffer, or #NULL + * @param start2 first byte to write in second buffer + * @param len2 number of bytes to write in second buffer + * @returns total bytes written from both buffers, or -1 on error + */ +int +_dbus_write_socket_two (DBusSocket fd, + const DBusString *buffer1, + int start1, + int len1, + const DBusString *buffer2, + int start2, + int len2) +{ +#if HAVE_DECL_MSG_NOSIGNAL + struct iovec vectors[2]; + const char *data1; + const char *data2; + int bytes_written; + struct msghdr m; + + _dbus_assert (buffer1 != NULL); + _dbus_assert (start1 >= 0); + _dbus_assert (start2 >= 0); + _dbus_assert (len1 >= 0); + _dbus_assert (len2 >= 0); + + data1 = _dbus_string_get_const_data_len (buffer1, start1, len1); + + if (buffer2 != NULL) + data2 = _dbus_string_get_const_data_len (buffer2, start2, len2); + else + { + data2 = NULL; + start2 = 0; + len2 = 0; + } + + vectors[0].iov_base = (char*) data1; + vectors[0].iov_len = len1; + vectors[1].iov_base = (char*) data2; + vectors[1].iov_len = len2; + + _DBUS_ZERO(m); + m.msg_iov = vectors; + m.msg_iovlen = data2 ? 2 : 1; + + again: + + bytes_written = sendmsg (fd.fd, &m, MSG_NOSIGNAL); + + if (bytes_written < 0 && errno == EINTR) + goto again; + + return bytes_written; + +#else + return _dbus_write_two (fd.fd, buffer1, start1, len1, + buffer2, start2, len2); +#endif +} + +/** + * Thin wrapper around the read() system call that appends + * the data it reads to the DBusString buffer. It appends + * up to the given count, and returns the same value + * and same errno as read(). The only exception is that + * _dbus_read() handles EINTR for you. Also, _dbus_read() can + * return ENOMEM, even though regular UNIX read doesn't. + * + * Unlike _dbus_read_socket(), _dbus_read() is not available + * on Windows. + * + * @param fd the file descriptor to read from + * @param buffer the buffer to append data to + * @param count the amount of data to read + * @returns the number of bytes read or -1 + */ +int +_dbus_read (int fd, + DBusString *buffer, + int count) +{ + int bytes_read; + int start; + char *data; + + _dbus_assert (count >= 0); + + start = _dbus_string_get_length (buffer); + + if (!_dbus_string_lengthen (buffer, count)) + { + errno = ENOMEM; + return -1; + } + + data = _dbus_string_get_data_len (buffer, start, count); + + again: + + bytes_read = read (fd, data, count); + + if (bytes_read < 0) + { + if (errno == EINTR) + goto again; + else + { + /* put length back (note that this doesn't actually realloc anything) */ + _dbus_string_set_length (buffer, start); + return -1; + } + } + else + { + /* put length back (doesn't actually realloc) */ + _dbus_string_set_length (buffer, start + bytes_read); + +#if 0 + if (bytes_read > 0) + _dbus_verbose_bytes_of_string (buffer, start, bytes_read); +#endif + + return bytes_read; + } +} + +/** + * Thin wrapper around the write() system call that writes a part of a + * DBusString and handles EINTR for you. + * + * @param fd the file descriptor to write + * @param buffer the buffer to write data from + * @param start the first byte in the buffer to write + * @param len the number of bytes to try to write + * @returns the number of bytes written or -1 on error + */ +int +_dbus_write (int fd, + const DBusString *buffer, + int start, + int len) +{ + const char *data; + int bytes_written; + + data = _dbus_string_get_const_data_len (buffer, start, len); + + again: + + bytes_written = write (fd, data, len); + + if (bytes_written < 0 && errno == EINTR) + goto again; + +#if 0 + if (bytes_written > 0) + _dbus_verbose_bytes_of_string (buffer, start, bytes_written); +#endif + + return bytes_written; +} + +/** + * Like _dbus_write() but will use writev() if possible + * to write both buffers in sequence. The return value + * is the number of bytes written in the first buffer, + * plus the number written in the second. If the first + * buffer is written successfully and an error occurs + * writing the second, the number of bytes in the first + * is returned (i.e. the error is ignored), on systems that + * don't have writev. Handles EINTR for you. + * The second buffer may be #NULL. + * + * @param fd the file descriptor + * @param buffer1 first buffer + * @param start1 first byte to write in first buffer + * @param len1 number of bytes to write from first buffer + * @param buffer2 second buffer, or #NULL + * @param start2 first byte to write in second buffer + * @param len2 number of bytes to write in second buffer + * @returns total bytes written from both buffers, or -1 on error + */ +int +_dbus_write_two (int fd, + const DBusString *buffer1, + int start1, + int len1, + const DBusString *buffer2, + int start2, + int len2) +{ + _dbus_assert (buffer1 != NULL); + _dbus_assert (start1 >= 0); + _dbus_assert (start2 >= 0); + _dbus_assert (len1 >= 0); + _dbus_assert (len2 >= 0); + +#ifdef HAVE_WRITEV + { + struct iovec vectors[2]; + const char *data1; + const char *data2; + int bytes_written; + + data1 = _dbus_string_get_const_data_len (buffer1, start1, len1); + + if (buffer2 != NULL) + data2 = _dbus_string_get_const_data_len (buffer2, start2, len2); + else + { + data2 = NULL; + start2 = 0; + len2 = 0; + } + + vectors[0].iov_base = (char*) data1; + vectors[0].iov_len = len1; + vectors[1].iov_base = (char*) data2; + vectors[1].iov_len = len2; + + again: + + bytes_written = writev (fd, + vectors, + data2 ? 2 : 1); + + if (bytes_written < 0 && errno == EINTR) + goto again; + + return bytes_written; + } +#else /* HAVE_WRITEV */ + { + int ret1, ret2; + + ret1 = _dbus_write (fd, buffer1, start1, len1); + if (ret1 == len1 && buffer2 != NULL) + { + ret2 = _dbus_write (fd, buffer2, start2, len2); + if (ret2 < 0) + ret2 = 0; /* we can't report an error as the first write was OK */ + + return ret1 + ret2; + } + else + return ret1; + } +#endif /* !HAVE_WRITEV */ +} + +/** + * Creates a socket and connects it to the UNIX domain socket at the + * given path. The connection fd is returned, and is set up as + * nonblocking. + * + * Uses abstract sockets instead of filesystem-linked sockets if + * requested (it's possible only on Linux; see "man 7 unix" on Linux). + * On non-Linux abstract socket usage always fails. + * + * This will set FD_CLOEXEC for the socket returned. + * + * @param path the path to UNIX domain socket + * @param abstract #TRUE to use abstract namespace + * @param error return location for error code + * @returns a valid socket on success or an invalid socket on error + */ +DBusSocket +_dbus_connect_unix_socket (const char *path, + dbus_bool_t abstract, + DBusError *error) +{ + DBusSocket fd = DBUS_SOCKET_INIT; + size_t path_len; + struct sockaddr_un addr; + _DBUS_STATIC_ASSERT (sizeof (addr.sun_path) > _DBUS_MAX_SUN_PATH_LENGTH); + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_verbose ("connecting to unix socket %s abstract=%d\n", + path, abstract); + + + if (!_dbus_open_unix_socket (&fd.fd, error)) + { + _DBUS_ASSERT_ERROR_IS_SET(error); + return fd; + } + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + + _DBUS_ZERO (addr); + addr.sun_family = AF_UNIX; + path_len = strlen (path); + + if (abstract) + { +#ifdef __linux__ + addr.sun_path[0] = '\0'; /* this is what says "use abstract" */ + path_len++; /* Account for the extra nul byte added to the start of sun_path */ + + if (path_len > _DBUS_MAX_SUN_PATH_LENGTH) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "Abstract socket name too long\n"); + _dbus_close_socket (&fd, NULL); + return fd; + } + + strncpy (&addr.sun_path[1], path, sizeof (addr.sun_path) - 2); + /* _dbus_verbose_bytes (addr.sun_path, sizeof (addr.sun_path)); */ +#else /* !__linux__ */ + dbus_set_error (error, DBUS_ERROR_NOT_SUPPORTED, + "Operating system does not support abstract socket namespace\n"); + _dbus_close_socket (&fd, NULL); + return fd; +#endif /* !__linux__ */ + } + else + { + if (path_len > _DBUS_MAX_SUN_PATH_LENGTH) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "Socket name too long\n"); + _dbus_close_socket (&fd, NULL); + return fd; + } + + strncpy (addr.sun_path, path, sizeof (addr.sun_path) - 1); + } + + if (connect (fd.fd, (struct sockaddr*) &addr, _DBUS_STRUCT_OFFSET (struct sockaddr_un, sun_path) + path_len) < 0) + { + dbus_set_error (error, + _dbus_error_from_errno (errno), + "Failed to connect to socket %s: %s", + path, _dbus_strerror (errno)); + + _dbus_close_socket (&fd, NULL); + return fd; + } + + if (!_dbus_set_fd_nonblocking (fd.fd, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + + _dbus_close_socket (&fd, NULL); + return fd; + } + + return fd; +} + +/** + * Creates a UNIX domain socket and connects it to the specified + * process to execute. + * + * This will set FD_CLOEXEC for the socket returned. + * + * @param path the path to the executable + * @param argv the argument list for the process to execute. + * argv[0] typically is identical to the path of the executable + * @param error return location for error code + * @returns a valid socket on success or an invalid socket on error + */ +DBusSocket +_dbus_connect_exec (const char *path, + char *const argv[], + DBusError *error) +{ + DBusSocket s = DBUS_SOCKET_INIT; + int fds[2]; + pid_t pid; + int retval; + dbus_bool_t cloexec_done = 0; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_verbose ("connecting to process %s\n", path); + +#ifdef SOCK_CLOEXEC + retval = socketpair (AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, fds); + cloexec_done = (retval >= 0); + + if (retval < 0 && (errno == EINVAL || errno == EPROTOTYPE)) +#endif + { + retval = socketpair (AF_UNIX, SOCK_STREAM, 0, fds); + } + + if (retval < 0) + { + dbus_set_error (error, + _dbus_error_from_errno (errno), + "Failed to create socket pair: %s", + _dbus_strerror (errno)); + _dbus_assert (!_dbus_socket_is_valid (s)); + return s; + } + + if (!cloexec_done) + { + _dbus_fd_set_close_on_exec (fds[0]); + _dbus_fd_set_close_on_exec (fds[1]); + } + + /* Make sure our output buffers aren't redundantly printed by both the + * parent and the child */ + fflush (stdout); + fflush (stderr); + + pid = fork (); + if (pid < 0) + { + dbus_set_error (error, + _dbus_error_from_errno (errno), + "Failed to fork() to call %s: %s", + path, _dbus_strerror (errno)); + close (fds[0]); + close (fds[1]); + _dbus_assert (!_dbus_socket_is_valid (s)); + return s; + } + + if (pid == 0) + { + /* child */ + close (fds[0]); + + dup2 (fds[1], STDIN_FILENO); + dup2 (fds[1], STDOUT_FILENO); + + if (fds[1] != STDIN_FILENO && + fds[1] != STDOUT_FILENO) + close (fds[1]); + + /* Inherit STDERR and the controlling terminal from the + parent */ + + _dbus_close_all (); + + execvp (path, (char * const *) argv); + + fprintf (stderr, "Failed to execute process %s: %s\n", path, _dbus_strerror (errno)); + + _exit(1); + } + + /* parent */ + close (fds[1]); + + if (!_dbus_set_fd_nonblocking (fds[0], error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + + close (fds[0]); + _dbus_assert (!_dbus_socket_is_valid (s)); + return s; + } + + s.fd = fds[0]; + return s; +} + +/** + * Creates a socket and binds it to the given path, + * then listens on the socket. The socket is + * set to be nonblocking. + * + * Uses abstract sockets instead of filesystem-linked + * sockets if requested (it's possible only on Linux; + * see "man 7 unix" on Linux). + * On non-Linux abstract socket usage always fails. + * + * This will set FD_CLOEXEC for the socket returned + * + * @param path the socket name + * @param abstract #TRUE to use abstract namespace + * @param error return location for errors + * @returns a valid socket on success or an invalid socket on error + */ +DBusSocket +_dbus_listen_unix_socket (const char *path, + dbus_bool_t abstract, + DBusError *error) +{ + DBusSocket s = DBUS_SOCKET_INIT; + int listen_fd; + struct sockaddr_un addr; + size_t path_len; + _DBUS_STATIC_ASSERT (sizeof (addr.sun_path) > _DBUS_MAX_SUN_PATH_LENGTH); + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_verbose ("listening on unix socket %s abstract=%d\n", + path, abstract); + + if (!_dbus_open_unix_socket (&listen_fd, error)) + { + _DBUS_ASSERT_ERROR_IS_SET(error); + return s; + } + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + + _DBUS_ZERO (addr); + addr.sun_family = AF_UNIX; + path_len = strlen (path); + + if (abstract) + { +#ifdef __linux__ + /* remember that abstract names aren't nul-terminated so we rely + * on sun_path being filled in with zeroes above. + */ + addr.sun_path[0] = '\0'; /* this is what says "use abstract" */ + path_len++; /* Account for the extra nul byte added to the start of sun_path */ + + if (path_len > _DBUS_MAX_SUN_PATH_LENGTH) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "Abstract socket name too long\n"); + _dbus_close (listen_fd, NULL); + return s; + } + + strncpy (&addr.sun_path[1], path, sizeof (addr.sun_path) - 2); + /* _dbus_verbose_bytes (addr.sun_path, sizeof (addr.sun_path)); */ +#else /* !__linux__ */ + dbus_set_error (error, DBUS_ERROR_NOT_SUPPORTED, + "Operating system does not support abstract socket namespace\n"); + _dbus_close (listen_fd, NULL); + return s; +#endif /* !__linux__ */ + } + else + { + /* Discussed security implications of this with Nalin, + * and we couldn't think of where it would kick our ass, but + * it still seems a bit sucky. It also has non-security suckage; + * really we'd prefer to exit if the socket is already in use. + * But there doesn't seem to be a good way to do this. + * + * Just to be extra careful, I threw in the stat() - clearly + * the stat() can't *fix* any security issue, but it at least + * avoids inadvertent/accidental data loss. + */ + { + struct stat sb; + + if (stat (path, &sb) == 0 && + S_ISSOCK (sb.st_mode)) + unlink (path); + } + + if (path_len > _DBUS_MAX_SUN_PATH_LENGTH) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "Socket name too long\n"); + _dbus_close (listen_fd, NULL); + return s; + } + + strncpy (addr.sun_path, path, sizeof (addr.sun_path) - 1); + } + + if (bind (listen_fd, (struct sockaddr*) &addr, _DBUS_STRUCT_OFFSET (struct sockaddr_un, sun_path) + path_len) < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to bind socket \"%s\": %s", + path, _dbus_strerror (errno)); + _dbus_close (listen_fd, NULL); + return s; + } + + if (listen (listen_fd, SOMAXCONN /* backlog */) < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to listen on socket \"%s\": %s", + path, _dbus_strerror (errno)); + _dbus_close (listen_fd, NULL); + return s; + } + + if (!_dbus_set_fd_nonblocking (listen_fd, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_close (listen_fd, NULL); + return s; + } + + /* Try opening up the permissions, but if we can't, just go ahead + * and continue, maybe it will be good enough. + */ + if (!abstract && chmod (path, 0777) < 0) + _dbus_warn ("Could not set mode 0777 on socket %s", path); + + s.fd = listen_fd; + return s; +} + +/** + * Acquires one or more sockets passed in from systemd. The sockets + * are set to be nonblocking. + * + * This will set FD_CLOEXEC for the sockets returned. + * + * @param fds the file descriptors + * @param error return location for errors + * @returns the number of file descriptors + */ +int +_dbus_listen_systemd_sockets (DBusSocket **fds, + DBusError *error) +{ +#ifdef HAVE_SYSTEMD + int r, n; + int fd; + DBusSocket *new_fds; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + n = sd_listen_fds (TRUE); + if (n < 0) + { + dbus_set_error (error, _dbus_error_from_errno (-n), + "Failed to acquire systemd socket: %s", + _dbus_strerror (-n)); + return -1; + } + + if (n <= 0) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "No socket received."); + return -1; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) + { + r = sd_is_socket (fd, AF_UNSPEC, SOCK_STREAM, 1); + if (r < 0) + { + dbus_set_error (error, _dbus_error_from_errno (-r), + "Failed to verify systemd socket type: %s", + _dbus_strerror (-r)); + return -1; + } + + if (!r) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "Passed socket has wrong type."); + return -1; + } + } + + /* OK, the file descriptors are all good, so let's take posession of + them then. */ + + new_fds = dbus_new (DBusSocket, n); + if (!new_fds) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, + "Failed to allocate file handle array."); + goto fail; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) + { + if (!_dbus_set_fd_nonblocking (fd, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto fail; + } + + new_fds[fd - SD_LISTEN_FDS_START].fd = fd; + } + + *fds = new_fds; + return n; + + fail: + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) + { + _dbus_close (fd, NULL); + } + + dbus_free (new_fds); + return -1; +#else + dbus_set_error_const (error, DBUS_ERROR_NOT_SUPPORTED, + "dbus was compiled without systemd support"); + return -1; +#endif +} + +/* Convert an error code from getaddrinfo() or getnameinfo() into + * a D-Bus error name. */ +static const char * +_dbus_error_from_gai (int gai_res, + int saved_errno) +{ + switch (gai_res) + { +#ifdef EAI_FAMILY + case EAI_FAMILY: + /* ai_family not supported (at all) */ + return DBUS_ERROR_NOT_SUPPORTED; +#endif + +#ifdef EAI_SOCKTYPE + case EAI_SOCKTYPE: + /* ai_socktype not supported (at all) */ + return DBUS_ERROR_NOT_SUPPORTED; +#endif + +#ifdef EAI_MEMORY + case EAI_MEMORY: + /* Out of memory */ + return DBUS_ERROR_NO_MEMORY; +#endif + +#ifdef EAI_SYSTEM + case EAI_SYSTEM: + /* Unspecified system error, details in errno */ + return _dbus_error_from_errno (saved_errno); +#endif + + case 0: + /* It succeeded, but we didn't get any addresses? */ + return DBUS_ERROR_FAILED; + + /* EAI_AGAIN: Transient failure */ + /* EAI_BADFLAGS: invalid ai_flags (programming error) */ + /* EAI_FAIL: Non-recoverable failure */ + /* EAI_NODATA: host exists but has no addresses */ + /* EAI_NONAME: host does not exist */ + /* EAI_OVERFLOW: argument buffer overflow */ + /* EAI_SERVICE: service not available for specified socket + * type (we should never see this because we use numeric + * ports) */ + default: + return DBUS_ERROR_FAILED; + } +} + +/** + * Creates a socket and connects to a socket at the given host + * and port. The connection fd is returned, and is set up as + * nonblocking. + * + * This will set FD_CLOEXEC for the socket returned + * + * @param host the host name to connect to + * @param port the port to connect to + * @param family the address family to listen on, NULL for all + * @param error return location for error code + * @returns connection file descriptor or -1 on error + */ +DBusSocket +_dbus_connect_tcp_socket (const char *host, + const char *port, + const char *family, + DBusError *error) +{ + return _dbus_connect_tcp_socket_with_nonce (host, port, family, (const char*)NULL, error); +} + +DBusSocket +_dbus_connect_tcp_socket_with_nonce (const char *host, + const char *port, + const char *family, + const char *noncefile, + DBusError *error) +{ + int saved_errno = 0; + DBusList *connect_errors = NULL; + DBusSocket fd = DBUS_SOCKET_INIT; + int res; + struct addrinfo hints; + struct addrinfo *ai = NULL; + const struct addrinfo *tmp; + DBusError *connect_error; + + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + + _DBUS_ZERO (hints); + + if (!family) + hints.ai_family = AF_UNSPEC; + else if (!strcmp(family, "ipv4")) + hints.ai_family = AF_INET; + else if (!strcmp(family, "ipv6")) + hints.ai_family = AF_INET6; + else + { + dbus_set_error (error, + DBUS_ERROR_BAD_ADDRESS, + "Unknown address family %s", family); + return _dbus_socket_get_invalid (); + } + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + + if ((res = getaddrinfo(host, port, &hints, &ai)) != 0) + { + dbus_set_error (error, + _dbus_error_from_gai (res, errno), + "Failed to lookup host/port: \"%s:%s\": %s (%d)", + host, port, gai_strerror(res), res); + goto out; + } + + tmp = ai; + while (tmp) + { + if (!_dbus_open_socket (&fd.fd, tmp->ai_family, SOCK_STREAM, 0, error)) + { + _DBUS_ASSERT_ERROR_IS_SET(error); + _dbus_socket_invalidate (&fd); + goto out; + } + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + + if (connect (fd.fd, (struct sockaddr*) tmp->ai_addr, tmp->ai_addrlen) < 0) + { + saved_errno = errno; + _dbus_close_socket (&fd, NULL); + + connect_error = dbus_new0 (DBusError, 1); + + if (connect_error == NULL) + { + _DBUS_SET_OOM (error); + goto out; + } + + dbus_error_init (connect_error); + _dbus_set_error_with_inet_sockaddr (connect_error, + tmp->ai_addr, tmp->ai_addrlen, + "Failed to connect to socket", + saved_errno); + + if (!_dbus_list_append (&connect_errors, connect_error)) + { + dbus_error_free (connect_error); + dbus_free (connect_error); + _DBUS_SET_OOM (error); + goto out; + } + + tmp = tmp->ai_next; + continue; + } + + break; + } + + if (!_dbus_socket_is_valid (fd)) + { + _dbus_combine_tcp_errors (&connect_errors, "Failed to connect", + host, port, error); + goto out; + } + + if (noncefile != NULL) + { + DBusString noncefileStr; + dbus_bool_t ret; + _dbus_string_init_const (&noncefileStr, noncefile); + ret = _dbus_send_nonce (fd, &noncefileStr, error); + + if (!ret) + { + _dbus_close_socket (&fd, NULL); + goto out; + } + } + + if (!_dbus_set_fd_nonblocking (fd.fd, error)) + { + _dbus_close_socket (&fd, NULL); + goto out; + } + +out: + if (ai != NULL) + freeaddrinfo (ai); + + while ((connect_error = _dbus_list_pop_first (&connect_errors))) + { + dbus_error_free (connect_error); + dbus_free (connect_error); + } + + return fd; +} + +/** + * Creates a socket and binds it to the given path, then listens on + * the socket. The socket is set to be nonblocking. In case of port=0 + * a random free port is used and returned in the port parameter. + * If inaddr_any is specified, the hostname is ignored. + * + * This will set FD_CLOEXEC for the socket returned + * + * @param host the host name to listen on + * @param port the port to listen on, if zero a free port will be used + * @param family the address family to listen on, NULL for all + * @param retport string to return the actual port listened on + * @param retfamily string to return the actual family listened on + * @param fds_p location to store returned file descriptors + * @param error return location for errors + * @returns the number of listening file descriptors or -1 on error + */ +int +_dbus_listen_tcp_socket (const char *host, + const char *port, + const char *family, + DBusString *retport, + const char **retfamily, + DBusSocket **fds_p, + DBusError *error) +{ + int saved_errno; + int nlisten_fd = 0, res, i; + DBusList *bind_errors = NULL; + DBusError *bind_error = NULL; + DBusSocket *listen_fd = NULL; + struct addrinfo hints; + struct addrinfo *ai, *tmp; + unsigned int reuseaddr; + dbus_bool_t have_ipv4 = FALSE; + dbus_bool_t have_ipv6 = FALSE; + + *fds_p = NULL; + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _DBUS_ZERO (hints); + + if (!family) + hints.ai_family = AF_UNSPEC; + else if (!strcmp(family, "ipv4")) + hints.ai_family = AF_INET; + else if (!strcmp(family, "ipv6")) + hints.ai_family = AF_INET6; + else + { + dbus_set_error (error, + DBUS_ERROR_BAD_ADDRESS, + "Unknown address family %s", family); + return -1; + } + + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE; + + redo_lookup_with_port: + ai = NULL; + if ((res = getaddrinfo(host, port, &hints, &ai)) != 0 || !ai) + { + dbus_set_error (error, + _dbus_error_from_gai (res, errno), + "Failed to lookup host/port: \"%s:%s\": %s (%d)", + host ? host : "*", port, gai_strerror(res), res); + goto failed; + } + + tmp = ai; + while (tmp) + { + int fd = -1, tcp_nodelay_on; + DBusSocket *newlisten_fd; + + if (!_dbus_open_socket (&fd, tmp->ai_family, SOCK_STREAM, 0, error)) + { + _DBUS_ASSERT_ERROR_IS_SET(error); + goto failed; + } + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + + reuseaddr = 1; + if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr))==-1) + { + _dbus_warn ("Failed to set socket option \"%s:%s\": %s", + host ? host : "*", port, _dbus_strerror (errno)); + } + + /* Nagle's algorithm imposes a huge delay on the initial messages + going over TCP. */ + tcp_nodelay_on = 1; + if (setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay_on, sizeof (tcp_nodelay_on)) == -1) + { + _dbus_warn ("Failed to set TCP_NODELAY socket option \"%s:%s\": %s", + host ? host : "*", port, _dbus_strerror (errno)); + } + + if (bind (fd, (struct sockaddr*) tmp->ai_addr, tmp->ai_addrlen) < 0) + { + saved_errno = errno; + _dbus_close(fd, NULL); + + /* + * We don't treat this as a fatal error, because there might be + * other addresses that we can listen on. In particular: + * + * - If saved_errno is EADDRINUSE after we + * "goto redo_lookup_with_port" after binding a port on one of the + * possible addresses, we will try to bind that same port on + * every address, including the same address again for a second + * time, which will fail with EADDRINUSE. + * + * - If saved_errno is EADDRINUSE, it might be because binding to + * an IPv6 address implicitly binds to a corresponding IPv4 + * address or vice versa (e.g. Linux with bindv6only=0). + * + * - If saved_errno is EADDRNOTAVAIL when we asked for family + * AF_UNSPEC, it might be because IPv6 is disabled for this + * particular interface (e.g. Linux with + * /proc/sys/net/ipv6/conf/lo/disable_ipv6). + */ + bind_error = dbus_new0 (DBusError, 1); + + if (bind_error == NULL) + { + _DBUS_SET_OOM (error); + goto failed; + } + + dbus_error_init (bind_error); + _dbus_set_error_with_inet_sockaddr (bind_error, tmp->ai_addr, tmp->ai_addrlen, + "Failed to bind socket", + saved_errno); + + if (!_dbus_list_append (&bind_errors, bind_error)) + { + dbus_error_free (bind_error); + dbus_free (bind_error); + _DBUS_SET_OOM (error); + goto failed; + } + + /* Try the next address, maybe it will work better */ + tmp = tmp->ai_next; + continue; + } + + if (listen (fd, 30 /* backlog */) < 0) + { + saved_errno = errno; + _dbus_close (fd, NULL); + _dbus_set_error_with_inet_sockaddr (error, tmp->ai_addr, tmp->ai_addrlen, + "Failed to listen on socket", + saved_errno); + goto failed; + } + + newlisten_fd = dbus_realloc(listen_fd, sizeof(DBusSocket)*(nlisten_fd+1)); + if (!newlisten_fd) + { + _dbus_close (fd, NULL); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, + "Failed to allocate file handle array"); + goto failed; + } + listen_fd = newlisten_fd; + listen_fd[nlisten_fd].fd = fd; + nlisten_fd++; + + if (tmp->ai_addr->sa_family == AF_INET) + have_ipv4 = TRUE; + else if (tmp->ai_addr->sa_family == AF_INET6) + have_ipv6 = TRUE; + + if (!_dbus_string_get_length(retport)) + { + /* If the user didn't specify a port, or used 0, then + the kernel chooses a port. After the first address + is bound to, we need to force all remaining addresses + to use the same port */ + if (!port || !strcmp(port, "0")) + { + int result; + struct sockaddr_storage addr; + socklen_t addrlen; + char portbuf[50]; + + addrlen = sizeof(addr); + result = getsockname(fd, (struct sockaddr*) &addr, &addrlen); + + if (result == -1) + { + saved_errno = errno; + dbus_set_error (error, _dbus_error_from_errno (saved_errno), + "Failed to retrieve socket name for \"%s:%s\": %s", + host ? host : "*", port, _dbus_strerror (saved_errno)); + goto failed; + } + + if ((res = getnameinfo ((struct sockaddr*)&addr, addrlen, NULL, 0, + portbuf, sizeof(portbuf), + NI_NUMERICHOST | NI_NUMERICSERV)) != 0) + { + saved_errno = errno; + dbus_set_error (error, _dbus_error_from_gai (res, saved_errno), + "Failed to resolve port \"%s:%s\": %s (%d)", + host ? host : "*", port, gai_strerror(res), res); + goto failed; + } + + if (!_dbus_string_append(retport, portbuf)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + /* Release current address list & redo lookup */ + port = _dbus_string_get_const_data(retport); + freeaddrinfo(ai); + goto redo_lookup_with_port; + } + else + { + if (!_dbus_string_append(retport, port)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + } + } + + tmp = tmp->ai_next; + } + freeaddrinfo(ai); + ai = NULL; + + if (!nlisten_fd) + { + _dbus_combine_tcp_errors (&bind_errors, "Failed to bind", host, + port, error); + goto failed; + } + + if (have_ipv4 && !have_ipv6) + *retfamily = "ipv4"; + else if (!have_ipv4 && have_ipv6) + *retfamily = "ipv6"; + + for (i = 0 ; i < nlisten_fd ; i++) + { + if (!_dbus_set_fd_nonblocking (listen_fd[i].fd, error)) + { + goto failed; + } + } + + *fds_p = listen_fd; + + /* This list might be non-empty even on success, because we might be + * ignoring EADDRINUSE or EADDRNOTAVAIL */ + while ((bind_error = _dbus_list_pop_first (&bind_errors))) + { + dbus_error_free (bind_error); + dbus_free (bind_error); + } + + return nlisten_fd; + + failed: + if (ai) + freeaddrinfo(ai); + for (i = 0 ; i < nlisten_fd ; i++) + _dbus_close(listen_fd[i].fd, NULL); + + while ((bind_error = _dbus_list_pop_first (&bind_errors))) + { + dbus_error_free (bind_error); + dbus_free (bind_error); + } + + dbus_free(listen_fd); + return -1; +} + +static dbus_bool_t +write_credentials_byte (int server_fd, + DBusError *error) +{ + int bytes_written; + char buf[1] = { '\0' }; +#if defined(HAVE_CMSGCRED) + union { + struct cmsghdr hdr; + char cred[CMSG_SPACE (sizeof (struct cmsgcred))]; + } cmsg; + struct iovec iov; + struct msghdr msg; + iov.iov_base = buf; + iov.iov_len = 1; + + _DBUS_ZERO(msg); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_control = (caddr_t) &cmsg; + msg.msg_controllen = CMSG_SPACE (sizeof (struct cmsgcred)); + _DBUS_ZERO(cmsg); + cmsg.hdr.cmsg_len = CMSG_LEN (sizeof (struct cmsgcred)); + cmsg.hdr.cmsg_level = SOL_SOCKET; + cmsg.hdr.cmsg_type = SCM_CREDS; +#endif + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + again: + +#if defined(HAVE_CMSGCRED) + bytes_written = sendmsg (server_fd, &msg, 0 +#if HAVE_DECL_MSG_NOSIGNAL + |MSG_NOSIGNAL +#endif + ); + + /* If we HAVE_CMSGCRED, the OS still might not let us sendmsg() + * with a SOL_SOCKET/SCM_CREDS message - for instance, FreeBSD + * only allows that on AF_UNIX. Try just doing a send() instead. */ + if (bytes_written < 0 && errno == EINVAL) +#endif + { + bytes_written = send (server_fd, buf, 1, 0 +#if HAVE_DECL_MSG_NOSIGNAL + |MSG_NOSIGNAL +#endif + ); + } + + if (bytes_written < 0 && errno == EINTR) + goto again; + + if (bytes_written < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to write credentials byte: %s", + _dbus_strerror (errno)); + return FALSE; + } + else if (bytes_written == 0) + { + dbus_set_error (error, DBUS_ERROR_IO_ERROR, + "wrote zero bytes writing credentials byte"); + return FALSE; + } + else + { + _dbus_assert (bytes_written == 1); + _dbus_verbose ("wrote credentials byte\n"); + return TRUE; + } +} + +/* return FALSE on OOM, TRUE otherwise, even if no groups were found */ +static dbus_bool_t +add_groups_to_credentials (int client_fd, + DBusCredentials *credentials, + dbus_gid_t primary) +{ +#if defined(__linux__) && defined(SO_PEERGROUPS) + _DBUS_STATIC_ASSERT (sizeof (gid_t) <= sizeof (dbus_gid_t)); + /* This function assumes socklen_t is unsigned, which is true on Linux */ + _DBUS_STATIC_ASSERT (((socklen_t) -1) > 0); + gid_t *buf = NULL; + socklen_t len = 1024; + dbus_bool_t oom = FALSE; + /* libdbus has a different representation of group IDs just to annoy you */ + dbus_gid_t *converted_gids = NULL; + dbus_bool_t need_primary = TRUE; + size_t n_gids; + size_t i; + + n_gids = ((size_t) len) / sizeof (gid_t); + buf = dbus_new (gid_t, n_gids); + + if (buf == NULL) + return FALSE; + + while (getsockopt (client_fd, SOL_SOCKET, SO_PEERGROUPS, buf, &len) < 0) + { + int e = errno; + gid_t *replacement; + + _dbus_verbose ("getsockopt failed with %s, len now %lu\n", + _dbus_strerror (e), (unsigned long) len); + + if (e != ERANGE || (size_t) len <= n_gids * sizeof (gid_t)) + { + _dbus_verbose ("Failed to getsockopt(SO_PEERGROUPS): %s\n", + _dbus_strerror (e)); + goto out; + } + + /* If not enough space, len is updated to be enough. + * Try again with a large enough buffer. */ + n_gids = ((size_t) len) / sizeof (gid_t); + replacement = dbus_realloc (buf, len); + + if (replacement == NULL) + { + oom = TRUE; + goto out; + } + + buf = replacement; + _dbus_verbose ("will try again with %lu\n", (unsigned long) len); + } + + if (len > n_gids * sizeof (gid_t)) + { + _dbus_verbose ("%lu > %zu", (unsigned long) len, n_gids * sizeof (gid_t)); + _dbus_assert_not_reached ("getsockopt(SO_PEERGROUPS) overflowed"); + } + + if (len % sizeof (gid_t) != 0) + { + _dbus_verbose ("getsockopt(SO_PEERGROUPS) did not return an " + "integer multiple of sizeof(gid_t): %lu should be " + "divisible by %zu", + (unsigned long) len, sizeof (gid_t)); + goto out; + } + + /* Allocate an extra space for the primary group ID */ + n_gids = ((size_t) len) / sizeof (gid_t); + + /* If n_gids is less than this, then (n_gids + 1) certainly doesn't + * overflow, and neither does multiplying that by sizeof(dbus_gid_t). + * This is using _DBUS_INT32_MAX as a conservative lower bound for + * the maximum size_t. */ + if (n_gids >= (_DBUS_INT32_MAX / sizeof (dbus_gid_t)) - 1) + { + _dbus_verbose ("getsockopt(SO_PEERGROUPS) returned a huge number " + "of groups (%lu bytes), ignoring", + (unsigned long) len); + goto out; + } + + converted_gids = dbus_new (dbus_gid_t, n_gids + 1); + + if (converted_gids == NULL) + { + oom = TRUE; + goto out; + } + + for (i = 0; i < n_gids; i++) + { + converted_gids[i] = (dbus_gid_t) buf[i]; + + if (converted_gids[i] == primary) + need_primary = FALSE; + } + + if (need_primary && primary != DBUS_GID_UNSET) + { + converted_gids[n_gids] = primary; + n_gids++; + } + + _dbus_credentials_take_unix_gids (credentials, converted_gids, n_gids); + +out: + dbus_free (buf); + return !oom; +#else + /* no error */ + return TRUE; +#endif +} + +/* return FALSE on OOM, TRUE otherwise, even if no credentials were found */ +static dbus_bool_t +add_linux_security_label_to_credentials (int client_fd, + DBusCredentials *credentials) +{ +#if defined(__linux__) && defined(SO_PEERSEC) + DBusString buf; + socklen_t len = 1024; + dbus_bool_t oom = FALSE; + + if (!_dbus_string_init_preallocated (&buf, len) || + !_dbus_string_set_length (&buf, len)) + return FALSE; + + while (getsockopt (client_fd, SOL_SOCKET, SO_PEERSEC, + _dbus_string_get_data (&buf), &len) < 0) + { + int e = errno; + + _dbus_verbose ("getsockopt failed with %s, len now %lu\n", + _dbus_strerror (e), (unsigned long) len); + + if (e != ERANGE || len <= _dbus_string_get_length_uint (&buf)) + { + _dbus_verbose ("Failed to getsockopt(SO_PEERSEC): %s\n", + _dbus_strerror (e)); + goto out; + } + + /* If not enough space, len is updated to be enough. + * Try again with a large enough buffer. */ + if (!_dbus_string_set_length (&buf, len)) + { + oom = TRUE; + goto out; + } + + _dbus_verbose ("will try again with %lu\n", (unsigned long) len); + } + + if (len <= 0) + { + _dbus_verbose ("getsockopt(SO_PEERSEC) yielded <= 0 bytes: %lu\n", + (unsigned long) len); + goto out; + } + + if (len > _dbus_string_get_length_uint (&buf)) + { + _dbus_verbose ("%lu > %u", (unsigned long) len, + _dbus_string_get_length_uint (&buf)); + _dbus_assert_not_reached ("getsockopt(SO_PEERSEC) overflowed"); + } + + if (_dbus_string_get_byte (&buf, len - 1) == 0) + { + /* the kernel included the trailing \0 in its count, + * but DBusString always has an extra \0 after the data anyway */ + _dbus_verbose ("subtracting trailing \\0\n"); + len--; + } + + if (!_dbus_string_set_length (&buf, len)) + { + _dbus_assert_not_reached ("shortening string should not lead to OOM"); + oom = TRUE; + goto out; + } + + if (strlen (_dbus_string_get_const_data (&buf)) != len) + { + /* LSM people on the linux-security-module@ mailing list say this + * should never happen: the label should be a bytestring with + * an optional trailing \0 */ + _dbus_verbose ("security label from kernel had an embedded \\0, " + "ignoring it\n"); + goto out; + } + + _dbus_verbose ("getsockopt(SO_PEERSEC): %lu bytes excluding \\0: %s\n", + (unsigned long) len, + _dbus_string_get_const_data (&buf)); + + if (!_dbus_credentials_add_linux_security_label (credentials, + _dbus_string_get_const_data (&buf))) + { + oom = TRUE; + goto out; + } + +out: + _dbus_string_free (&buf); + return !oom; +#else + /* no error */ + return TRUE; +#endif +} + +/** + * Reads a single byte which must be nul (an error occurs otherwise), + * and reads unix credentials if available. Clears the credentials + * object, then adds pid/uid if available, so any previous credentials + * stored in the object are lost. + * + * DBusServer makes the security assumption that the credentials + * returned by this method are the credentials that were active + * at the time the socket was opened. Do not add APIs to this + * method that would break that assumption. + * + * In particular, it is incorrect to use any API of the form + * "get the process ID at the other end of the connection, then + * determine its uid, gid, or other credentials from the pid" + * (e.g. looking in /proc on Linux). If we did that, we would + * be vulnerable to several attacks. A malicious process could + * queue up the rest of the authentication handshake and a malicious + * message that it should not be allowed to send, then race with + * the DBusServer to exec() a more privileged (e.g. setuid) binary that + * would have been allowed to send that message; or it could exit, + * and arrange for enough setuid processes to be started that its + * pid would be recycled for one of those processes with high + * probability; or it could fd-pass the connection to a more + * privileged process. + * + * Return value indicates whether a byte was read, not whether + * we got valid credentials. On some systems, such as Linux, + * reading/writing the byte isn't actually required, but we do it + * anyway just to avoid multiple codepaths. + * + * Fails if no byte is available, so you must select() first. + * + * The point of the byte is that on some systems we have to + * use sendmsg()/recvmsg() to transmit credentials. + * + * @param client_fd the client file descriptor + * @param credentials object to add client credentials to + * @param error location to store error code + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_read_credentials_socket (DBusSocket client_fd, + DBusCredentials *credentials, + DBusError *error) +{ + struct msghdr msg; + struct iovec iov; + char buf; + dbus_uid_t uid_read; + dbus_gid_t primary_gid_read; + dbus_pid_t pid_read; + int bytes_read; + int pid_fd_read; + +#ifdef HAVE_CMSGCRED + union { + struct cmsghdr hdr; + char cred[CMSG_SPACE (sizeof (struct cmsgcred))]; + } cmsg; +#endif + + /* The POSIX spec certainly doesn't promise this, but + * we need these assertions to fail as soon as we're wrong about + * it so we can do the porting fixups + */ + _DBUS_STATIC_ASSERT (sizeof (pid_t) <= sizeof (dbus_pid_t)); + _DBUS_STATIC_ASSERT (sizeof (uid_t) <= sizeof (dbus_uid_t)); + _DBUS_STATIC_ASSERT (sizeof (gid_t) <= sizeof (dbus_gid_t)); + + uid_read = DBUS_UID_UNSET; + primary_gid_read = DBUS_GID_UNSET; + pid_read = DBUS_PID_UNSET; + pid_fd_read = -1; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_credentials_clear (credentials); + + iov.iov_base = &buf; + iov.iov_len = 1; + + _DBUS_ZERO(msg); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + +#if defined(HAVE_CMSGCRED) + _DBUS_ZERO(cmsg); + msg.msg_control = (caddr_t) &cmsg; + msg.msg_controllen = CMSG_SPACE (sizeof (struct cmsgcred)); +#endif + + again: + bytes_read = recvmsg (client_fd.fd, &msg, 0); + + if (bytes_read < 0) + { + if (errno == EINTR) + goto again; + + /* EAGAIN or EWOULDBLOCK would be unexpected here since we would + * normally only call read_credentials if the socket was ready + * for reading + */ + + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to read credentials byte: %s", + _dbus_strerror (errno)); + return FALSE; + } + else if (bytes_read == 0) + { + /* this should not happen unless we are using recvmsg wrong, + * so is essentially here for paranoia + */ + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to read credentials byte (zero-length read)"); + return FALSE; + } + else if (buf != '\0') + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Credentials byte was not nul"); + return FALSE; + } + + _dbus_verbose ("read credentials byte\n"); + + { +#ifdef SO_PEERCRED + /* Supported by at least Linux and OpenBSD, with minor differences. + * + * This mechanism passes the process ID through and does not require + * the peer's cooperation, so we prefer it over all others. Notably, + * Linux also supports SCM_CREDENTIALS, which is similar to FreeBSD + * SCM_CREDS; it's implemented in GIO, but we don't use it in dbus at all, + * because this is much less fragile. + */ +#ifdef __OpenBSD__ + struct sockpeercred cr; +#else + struct ucred cr; +#endif + socklen_t cr_len = sizeof (cr); + + if (getsockopt (client_fd.fd, SOL_SOCKET, SO_PEERCRED, &cr, &cr_len) != 0) + { + _dbus_verbose ("Failed to getsockopt(SO_PEERCRED): %s\n", + _dbus_strerror (errno)); + } + else if (cr_len != sizeof (cr)) + { + _dbus_verbose ("Failed to getsockopt(SO_PEERCRED), returned %d bytes, expected %d\n", + cr_len, (int) sizeof (cr)); + } + else + { + pid_read = cr.pid; + uid_read = cr.uid; +#ifdef __linux__ + /* Do other platforms have cr.gid? (Not that it really matters, + * because the gid is useless to us unless we know the complete + * group vector, which we only know on Linux.) */ + primary_gid_read = cr.gid; +#endif + } + +#ifdef SO_PEERPIDFD + /* If we have SO_PEERCRED we might also have SO_PEERPIDFD, which + * allows to pin the process ID, and is available on Linux since v6.5. */ + cr_len = sizeof (int); + + if (getsockopt (client_fd.fd, SOL_SOCKET, SO_PEERPIDFD, &pid_fd_read, &cr_len) != 0) + { + _dbus_verbose ("Failed to getsockopt(SO_PEERPIDFD): %s\n", + _dbus_strerror (errno)); + } + else if (cr_len != sizeof (int)) + { + _dbus_verbose ("Failed to getsockopt(SO_PEERPIDFD), returned %d bytes, expected %d\n", + cr_len, (int) sizeof (int)); + } +#endif + +#elif defined(HAVE_UNPCBID) && defined(LOCAL_PEEREID) + /* Another variant of the above - used on NetBSD + */ + struct unpcbid cr; + socklen_t cr_len = sizeof (cr); + + if (getsockopt (client_fd.fd, 0, LOCAL_PEEREID, &cr, &cr_len) != 0) + { + _dbus_verbose ("Failed to getsockopt(LOCAL_PEEREID): %s\n", + _dbus_strerror (errno)); + } + else if (cr_len != sizeof (cr)) + { + _dbus_verbose ("Failed to getsockopt(LOCAL_PEEREID), returned %d bytes, expected %d\n", + cr_len, (int) sizeof (cr)); + } + else + { + pid_read = cr.unp_pid; + uid_read = cr.unp_euid; + } +#elif defined(HAVE_CMSGCRED) + /* We only check for HAVE_CMSGCRED, but we're really assuming that the + * presence of that struct implies SCM_CREDS. Supported by at least + * FreeBSD and DragonflyBSD. + * + * This mechanism requires the peer to help us (it has to send us a + * SCM_CREDS message) but it does pass the process ID through, + * which makes it better than getpeereid(). + */ + struct cmsgcred *cred; + struct cmsghdr *cmsgp; + + for (cmsgp = CMSG_FIRSTHDR (&msg); + cmsgp != NULL; + cmsgp = CMSG_NXTHDR (&msg, cmsgp)) + { + if (cmsgp->cmsg_type == SCM_CREDS && + cmsgp->cmsg_level == SOL_SOCKET && + cmsgp->cmsg_len >= CMSG_LEN (sizeof (struct cmsgcred))) + { + cred = (struct cmsgcred *) (void *) CMSG_DATA (cmsgp); + pid_read = cred->cmcred_pid; + uid_read = cred->cmcred_euid; + break; + } + } + +#elif defined(HAVE_GETPEERUCRED) + /* Supported in at least Solaris >= 10. It should probably be higher + * up this list, because it carries the pid and we use this code path + * for audit data. */ + ucred_t * ucred = NULL; + if (getpeerucred (client_fd.fd, &ucred) == 0) + { +#ifdef HAVE_ADT + adt_session_data_t *adth = NULL; +#endif + pid_read = ucred_getpid (ucred); + uid_read = ucred_geteuid (ucred); +#ifdef HAVE_ADT + /* generate audit session data based on socket ucred */ + if (adt_start_session (&adth, NULL, 0) || (adth == NULL)) + { + _dbus_verbose ("Failed to adt_start_session(): %s\n", _dbus_strerror (errno)); + } + else + { + if (adt_set_from_ucred (adth, ucred, ADT_NEW)) + { + _dbus_verbose ("Failed to adt_set_from_ucred(): %s\n", _dbus_strerror (errno)); + } + else + { + adt_export_data_t *data = NULL; + size_t size = adt_export_session_data (adth, &data); + if (size <= 0) + { + _dbus_verbose ("Failed to adt_export_session_data(): %s\n", _dbus_strerror (errno)); + } + else + { + _dbus_credentials_add_adt_audit_data (credentials, data, size); + free (data); + } + } + (void) adt_end_session (adth); + } +#endif /* HAVE_ADT */ + } + else + { + _dbus_verbose ("Failed to getpeerucred() credentials: %s\n", _dbus_strerror (errno)); + } + if (ucred != NULL) + ucred_free (ucred); + + /* ---------------------------------------------------------------- + * When adding new mechanisms, please add them above this point + * if they support passing the process ID through, or below if not. + * ---------------------------------------------------------------- */ + +#elif defined(HAVE_GETPEEREID) + /* getpeereid() originates from D.J. Bernstein and is fairly + * widely-supported. According to a web search, it might be present in + * any/all of: + * + * - AIX? + * - Blackberry? + * - Cygwin + * - FreeBSD 4.6+ (but we prefer SCM_CREDS: it carries the pid) + * - Mac OS X + * - Minix 3.1.8+ + * - MirBSD? + * - NetBSD 5.0+ (but LOCAL_PEEREID would be better: it carries the pid) + * - OpenBSD 3.0+ (but we prefer SO_PEERCRED: it carries the pid) + * - QNX? + */ + uid_t euid; + gid_t egid; + if (getpeereid (client_fd.fd, &euid, &egid) == 0) + { + uid_read = euid; + } + else + { + _dbus_verbose ("Failed to getpeereid() credentials: %s\n", _dbus_strerror (errno)); + } +#else /* no supported mechanism */ + +#warning Socket credentials not supported on this Unix OS +#warning Please tell https://gitlab.freedesktop.org/dbus/dbus/-/issues/new + + /* Please add other operating systems known to support at least one of + * the mechanisms above to this list, keeping alphabetical order. + * Everything not in this list is best-effort. + */ +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ + defined(__linux__) || \ + defined(__OpenBSD__) || \ + defined(__NetBSD__) +# error Credentials passing not working on this OS is a regression! +#endif + + _dbus_verbose ("Socket credentials not supported on this OS\n"); +#endif + } + + _dbus_verbose ("Credentials:" + " pid "DBUS_PID_FORMAT + " uid "DBUS_UID_FORMAT + "\n", + pid_read, + uid_read); + + /* Assign this first, so we don't have to close it manually in case one of + * the next steps fails. */ + if (pid_fd_read >= 0) + _dbus_credentials_take_pid_fd (credentials, pid_fd_read); + + if (pid_read != DBUS_PID_UNSET) + { + if (!_dbus_credentials_add_pid (credentials, pid_read)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + } + + if (uid_read != DBUS_UID_UNSET) + { + if (!_dbus_credentials_add_unix_uid (credentials, uid_read)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + } + + if (!add_linux_security_label_to_credentials (client_fd.fd, credentials)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + /* We don't put any groups in the credentials unless we can put them + * all there. */ + if (!add_groups_to_credentials (client_fd.fd, credentials, primary_gid_read)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + return TRUE; +} + +/** + * Sends a single nul byte with our UNIX credentials as ancillary + * data. Returns #TRUE if the data was successfully written. On + * systems that don't support sending credentials, just writes a byte, + * doesn't send any credentials. On some systems, such as Linux, + * reading/writing the byte isn't actually required, but we do it + * anyway just to avoid multiple codepaths. + * + * Fails if no byte can be written, so you must select() first. + * + * The point of the byte is that on some systems we have to + * use sendmsg()/recvmsg() to transmit credentials. + * + * @param server_fd file descriptor for connection to server + * @param error return location for error code + * @returns #TRUE if the byte was sent + */ +dbus_bool_t +_dbus_send_credentials_socket (DBusSocket server_fd, + DBusError *error) +{ + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (write_credentials_byte (server_fd.fd, error)) + return TRUE; + else + return FALSE; +} + +/** + * Accepts a connection on a listening socket. + * Handles EINTR for you. + * + * This will enable FD_CLOEXEC for the returned socket. + * + * @param listen_fd the listen file descriptor + * @returns the connection fd of the client, or -1 on error + */ +DBusSocket +_dbus_accept (DBusSocket listen_fd) +{ + DBusSocket client_fd; + struct sockaddr addr; + socklen_t addrlen; +#ifdef HAVE_ACCEPT4 + dbus_bool_t cloexec_done; +#endif + + addrlen = sizeof (addr); + + retry: + +#ifdef HAVE_ACCEPT4 + /* + * At compile-time, we assume that if accept4() is available in + * libc headers, SOCK_CLOEXEC is too. At runtime, it is still + * not necessarily true that either is supported by the running kernel. + */ + client_fd.fd = accept4 (listen_fd.fd, &addr, &addrlen, SOCK_CLOEXEC); + cloexec_done = client_fd.fd >= 0; + + if (client_fd.fd < 0 && (errno == ENOSYS || errno == EINVAL)) +#endif + { + client_fd.fd = accept (listen_fd.fd, &addr, &addrlen); + } + + if (client_fd.fd < 0) + { + if (errno == EINTR) + goto retry; + } + + _dbus_verbose ("client fd %d accepted\n", client_fd.fd); + +#ifdef HAVE_ACCEPT4 + if (!cloexec_done) +#endif + { + _dbus_fd_set_close_on_exec(client_fd.fd); + } + + return client_fd; +} + +/** + * Checks to make sure the given directory is + * private to the user + * + * @param dir the name of the directory + * @param error error return + * @returns #FALSE on failure + **/ +dbus_bool_t +_dbus_check_dir_is_private_to_user (DBusString *dir, DBusError *error) +{ + const char *directory; + struct stat sb; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + directory = _dbus_string_get_const_data (dir); + + if (stat (directory, &sb) < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "%s", _dbus_strerror (errno)); + + return FALSE; + } + + if (sb.st_uid != geteuid ()) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "%s directory is owned by user %lu, not %lu", + directory, + (unsigned long) sb.st_uid, + (unsigned long) geteuid ()); + return FALSE; + } + + if ((S_IROTH & sb.st_mode) || (S_IWOTH & sb.st_mode) || + (S_IRGRP & sb.st_mode) || (S_IWGRP & sb.st_mode)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "%s directory is not private to the user", directory); + return FALSE; + } + + return TRUE; +} + +static dbus_bool_t +fill_user_info_from_passwd (struct passwd *p, + DBusUserInfo *info, + DBusError *error) +{ + _dbus_assert (p->pw_name != NULL); + _dbus_assert (p->pw_dir != NULL); + + info->uid = p->pw_uid; + info->primary_gid = p->pw_gid; + info->username = _dbus_strdup (p->pw_name); + info->homedir = _dbus_strdup (p->pw_dir); + + if (info->username == NULL || + info->homedir == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return FALSE; + } + + return TRUE; +} + +static dbus_bool_t +fill_user_info (DBusUserInfo *info, + dbus_uid_t uid, + const DBusString *username, + DBusError *error) +{ + const char *username_c; + + /* exactly one of username/uid provided */ + _dbus_assert (username != NULL || uid != DBUS_UID_UNSET); + _dbus_assert (username == NULL || uid == DBUS_UID_UNSET); + + info->uid = DBUS_UID_UNSET; + info->primary_gid = DBUS_GID_UNSET; + info->group_ids = NULL; + info->n_group_ids = 0; + info->username = NULL; + info->homedir = NULL; + + if (username != NULL) + username_c = _dbus_string_get_const_data (username); + else + username_c = NULL; + + /* For now assuming that the getpwnam() and getpwuid() flavors + * are always symmetrical, if not we have to add more configure + * checks + */ + + { + struct passwd *p; + char *buf = NULL; + int result; +#ifdef HAVE_GETPWNAM_R + size_t buflen; + struct passwd p_str; + + /* retrieve maximum needed size for buf */ + buflen = sysconf (_SC_GETPW_R_SIZE_MAX); + + /* sysconf actually returns a long, but everything else expects size_t, + * so just recast here. + * https://bugs.freedesktop.org/show_bug.cgi?id=17061 + */ + if ((long) buflen <= 0) + buflen = 1024; + + result = -1; + while (1) + { + buf = dbus_malloc (buflen); + if (buf == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return FALSE; + } + + p = NULL; + if (uid != DBUS_UID_UNSET) + result = getpwuid_r (uid, &p_str, buf, buflen, + &p); + else + result = getpwnam_r (username_c, &p_str, buf, buflen, + &p); + //Try a bigger buffer if ERANGE was returned + if (result == ERANGE && buflen < 512 * 1024) + { + dbus_free (buf); + buflen *= 2; + } + else + { + break; + } + } + + /* There are three possibilities: + * - an error: result is a nonzero error code, p should be NULL + * - name or uid not found: result is 0, p is NULL + * - success: result is 0, p should be &p_str + * + * Ensure that in all failure cases, p is set to NULL, matching the + * getpwuid/getpwnam interface. */ + if (result != 0 || p != &p_str) + p = NULL; + +#else /* ! HAVE_GETPWNAM_R */ + /* I guess we're screwed on thread safety here */ +#warning getpwnam_r() not available, please report this to the dbus maintainers with details of your OS + + /* It is unspecified whether "failed to find" counts as an error, + * or whether it's reported as p == NULL without touching errno. + * Reset errno so we can distinguish. */ + errno = 0; + + if (uid != DBUS_UID_UNSET) + p = getpwuid (uid); + else + p = getpwnam (username_c); + + /* Always initialized, but only meaningful if p is NULL */ + result = errno; +#endif /* ! HAVE_GETPWNAM_R */ + + if (p != NULL) + { + if (!fill_user_info_from_passwd (p, info, error)) + { + dbus_free (buf); + return FALSE; + } + dbus_free (buf); + } + else + { + DBusError local_error = DBUS_ERROR_INIT; + const char *error_str; + + if (result == 0) + error_str = "not found"; + else + error_str = _dbus_strerror (result); + + if (uid != DBUS_UID_UNSET) + dbus_set_error (&local_error, _dbus_error_from_errno (result), + "Looking up user ID " DBUS_UID_FORMAT ": %s", + uid, error_str); + else + dbus_set_error (&local_error, _dbus_error_from_errno (result), + "Looking up user \"%s\": %s", + username_c ? username_c : "???", error_str); + + _dbus_verbose ("%s", local_error.message); + dbus_move_error (&local_error, error); + dbus_free (buf); + return FALSE; + } + } + + /* Fill this in so we can use it to get groups */ + username_c = info->username; + +#ifdef HAVE_GETGROUPLIST + { + gid_t *buf; + int buf_count; + int i; + int initial_buf_count; + + initial_buf_count = 17; + buf_count = initial_buf_count; + buf = dbus_new (gid_t, buf_count); + if (buf == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + if (getgrouplist (username_c, + info->primary_gid, + buf, &buf_count) < 0) + { + gid_t *new; + /* Presumed cause of negative return code: buf has insufficient + entries to hold the entire group list. The Linux behavior in this + case is to pass back the actual number of groups in buf_count, but + on Mac OS X 10.5, buf_count is unhelpfully left alone. + So as a hack, try to help out a bit by guessing a larger + number of groups, within reason.. might still fail, of course, + but we can at least print a more informative message. I looked up + the "right way" to do this by downloading Apple's own source code + for the "id" command, and it turns out that they use an + undocumented library function getgrouplist_2 (!) which is not + declared in any header in /usr/include (!!). That did not seem + like the way to go here. + */ + if (buf_count == initial_buf_count) + { + buf_count *= 16; /* Retry with an arbitrarily scaled-up array */ + } + new = dbus_realloc (buf, buf_count * sizeof (buf[0])); + if (new == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + dbus_free (buf); + goto failed; + } + + buf = new; + + errno = 0; + if (getgrouplist (username_c, info->primary_gid, buf, &buf_count) < 0) + { + if (errno == 0) + { + _dbus_warn ("It appears that username \"%s\" is in more than %d groups.\nProceeding with just the first %d groups.", + username_c, buf_count, buf_count); + } + else + { + dbus_set_error (error, + _dbus_error_from_errno (errno), + "Failed to get groups for username \"%s\" primary GID " + DBUS_GID_FORMAT ": %s\n", + username_c, info->primary_gid, + _dbus_strerror (errno)); + dbus_free (buf); + goto failed; + } + } + } + + info->group_ids = dbus_new (dbus_gid_t, buf_count); + if (info->group_ids == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + dbus_free (buf); + goto failed; + } + + for (i = 0; i < buf_count; ++i) + info->group_ids[i] = buf[i]; + + info->n_group_ids = buf_count; + + dbus_free (buf); + } +#else /* HAVE_GETGROUPLIST */ + { + /* We just get the one group ID */ + info->group_ids = dbus_new (dbus_gid_t, 1); + if (info->group_ids == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + info->n_group_ids = 1; + + (info->group_ids)[0] = info->primary_gid; + } +#endif /* HAVE_GETGROUPLIST */ + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + return TRUE; + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + return FALSE; +} + +/** + * Gets user info for the given username. + * + * @param info user info object to initialize + * @param username the username + * @param error error return + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_user_info_fill (DBusUserInfo *info, + const DBusString *username, + DBusError *error) +{ + return fill_user_info (info, DBUS_UID_UNSET, + username, error); +} + +/** + * Gets user info for the given user ID. + * + * @param info user info object to initialize + * @param uid the user ID + * @param error error return + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_user_info_fill_uid (DBusUserInfo *info, + dbus_uid_t uid, + DBusError *error) +{ + return fill_user_info (info, uid, + NULL, error); +} + +/** + * Adds the most important credentials of the current process + * (the uid and pid) to the passed-in credentials object. + * + * The group vector is not included because it is rarely needed. + * The Linux security label is not included because it is rarely + * needed, it requires reading /proc, and the LSM API doesn't actually + * guarantee that the string seen in /proc is comparable to the strings + * found in SO_PEERSEC results. + * + * @param credentials credentials to add to + * @returns #FALSE if no memory; does not properly roll back on failure, so only some credentials may have been added + */ +dbus_bool_t +_dbus_credentials_add_from_current_process (DBusCredentials *credentials) +{ + dbus_pid_t pid = _dbus_getpid (); + + /* The POSIX spec certainly doesn't promise this, but + * we need these assertions to fail as soon as we're wrong about + * it so we can do the porting fixups + */ + _DBUS_STATIC_ASSERT (sizeof (pid_t) <= sizeof (dbus_pid_t)); + _DBUS_STATIC_ASSERT (sizeof (uid_t) <= sizeof (dbus_uid_t)); + _DBUS_STATIC_ASSERT (sizeof (gid_t) <= sizeof (dbus_gid_t)); + +#if HAVE_DECL_SYS_PIDFD_OPEN + /* Normally this syscall would have a race condition, but we can trust + * that our own process isn't going to exit, so the pid won't get reused. */ + int pid_fd = (int) syscall (SYS_pidfd_open, pid, 0); + if (pid_fd >= 0) + _dbus_credentials_take_pid_fd (credentials, pid_fd); +#endif + if (!_dbus_credentials_add_pid (credentials, pid)) + return FALSE; + if (!_dbus_credentials_add_unix_uid(credentials, _dbus_geteuid())) + return FALSE; + + return TRUE; +} + +/** + * Resolve the PID from the PID FD, if any. This allows us to avoid + * PID reuse attacks. Returns DBUS_PID_UNSET if the PID could not be resolved. + * Note that this requires being able to read /proc/self/fdinfo/<FD>, + * which is created as 600 and owned by the original UID that the + * process started as. So it cannot work when the start as root and + * drop privileges mechanism is in use (the systemd unit no longer + * does this, but third-party init-scripts might). + * + * @param pid_fd the PID FD + * @returns the resolved PID if found, DBUS_PID_UNSET otherwise + */ +dbus_pid_t +_dbus_resolve_pid_fd (int pid_fd) +{ +#ifdef __linux__ + DBusError error = DBUS_ERROR_INIT; + DBusString content = _DBUS_STRING_INIT_INVALID; + DBusString filename = _DBUS_STRING_INIT_INVALID; + dbus_pid_t result = DBUS_PID_UNSET; + int pid_index; + + if (pid_fd < 0) + goto out; + + if (!_dbus_string_init (&content)) + goto out; + + if (!_dbus_string_init (&filename)) + goto out; + + if (!_dbus_string_append_printf (&filename, "/proc/self/fdinfo/%d", pid_fd)) + goto out; + + if (!_dbus_file_get_contents (&content, &filename, &error)) + { + _dbus_verbose ("Cannot read '/proc/self/fdinfo/%d', unable to resolve PID, %s: %s\n", + pid_fd, error.name, error.message); + goto out; + } + + /* Ensure we are not reading PPid, either it's the first line of the file or + * there's a newline before it. */ + if (!_dbus_string_find (&content, 0, "Pid:", &pid_index) || + (pid_index > 0 && _dbus_string_get_byte (&content, pid_index - 1) != '\n')) + { + _dbus_verbose ("Cannot find 'Pid:' in '/proc/self/fdinfo/%d', unable to resolve PID\n", + pid_fd); + goto out; + } + + if (!_dbus_string_parse_uint (&content, pid_index + strlen ("Pid:"), &result, NULL)) + { + _dbus_verbose ("Cannot parse 'Pid:' from '/proc/self/fdinfo/%d', unable to resolve PID\n", + pid_fd); + goto out; + } + +out: + _dbus_string_free (&content); + _dbus_string_free (&filename); + dbus_error_free (&error); + + if (result <= 0) + return DBUS_PID_UNSET; + + return result; +#else + return DBUS_PID_UNSET; +#endif + +} + +/** + * Append to the string the identity we would like to have when we + * authenticate, on UNIX this is the current process UID and on + * Windows something else, probably a Windows SID string. No escaping + * is required, that is done in dbus-auth.c. The username here + * need not be anything human-readable, it can be the machine-readable + * form i.e. a user id. + * + * @param str the string to append to + * @returns #FALSE on no memory + */ +dbus_bool_t +_dbus_append_user_from_current_process (DBusString *str) +{ + return _dbus_string_append_uint (str, + _dbus_geteuid ()); +} + +/** + * Gets our process ID + * @returns process ID + */ +dbus_pid_t +_dbus_getpid (void) +{ + return getpid (); +} + +/** Gets our UID + * @returns process UID + */ +dbus_uid_t +_dbus_getuid (void) +{ + return getuid (); +} + +/** Gets our effective UID + * @returns process effective UID + */ +dbus_uid_t +_dbus_geteuid (void) +{ + return geteuid (); +} + +/** + * The only reason this is separate from _dbus_getpid() is to allow it + * on Windows for logging but not for other purposes. + * + * @returns process ID to put in log messages + */ +unsigned long +_dbus_pid_for_log (void) +{ + return getpid (); +} + +#if !defined(HAVE_STDATOMIC_H) && !DBUS_USE_SYNC +/* To be thread-safe by default on platforms that don't necessarily have + * atomic operations (notably Debian armel, which is armv4t), we must + * use a mutex that can be initialized statically, like this. + * GLib >= 2.32 uses a similar system. + */ +static pthread_mutex_t atomic_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +/** + * Atomically increments an integer + * + * @param atomic pointer to the integer to increment + * @returns the value before incrementing + */ +dbus_int32_t +_dbus_atomic_inc (DBusAtomic *atomic) +{ +#ifdef HAVE_STDATOMIC_H + /* Atomic version of "old = *atomic; *atomic += 1; return old" */ + return atomic_fetch_add (&atomic->value, 1); +#elif DBUS_USE_SYNC + /* Atomic version of "*atomic += 1; return *atomic - 1" */ + return __sync_add_and_fetch(&atomic->value, 1)-1; +#else + dbus_int32_t res; + + pthread_mutex_lock (&atomic_mutex); + res = atomic->value; + atomic->value += 1; + pthread_mutex_unlock (&atomic_mutex); + + return res; +#endif +} + +/** + * Atomically decrement an integer + * + * @param atomic pointer to the integer to decrement + * @returns the value before decrementing + */ +dbus_int32_t +_dbus_atomic_dec (DBusAtomic *atomic) +{ +#ifdef HAVE_STDATOMIC_H + /* Atomic version of "old = *atomic; *atomic -= 1; return old" */ + return atomic_fetch_sub (&atomic->value, 1); +#elif DBUS_USE_SYNC + /* Atomic version of "*atomic -= 1; return *atomic + 1" */ + return __sync_sub_and_fetch(&atomic->value, 1)+1; +#else + dbus_int32_t res; + + pthread_mutex_lock (&atomic_mutex); + res = atomic->value; + atomic->value -= 1; + pthread_mutex_unlock (&atomic_mutex); + + return res; +#endif +} + +/** + * Atomically get the value of an integer. It may change at any time + * thereafter, so this is mostly only useful for assertions. + * + * @param atomic pointer to the integer to get + * @returns the value at this moment + */ +dbus_int32_t +_dbus_atomic_get (DBusAtomic *atomic) +{ +#ifdef HAVE_STDATOMIC_H + /* Atomic version of "return *atomic" */ + return atomic_load (&atomic->value); +#elif DBUS_USE_SYNC + __sync_synchronize (); + return atomic->value; +#else + dbus_int32_t res; + + pthread_mutex_lock (&atomic_mutex); + res = atomic->value; + pthread_mutex_unlock (&atomic_mutex); + + return res; +#endif +} + +/** + * Atomically set the value of an integer to 0. + * + * @param atomic pointer to the integer to set + */ +void +_dbus_atomic_set_zero (DBusAtomic *atomic) +{ +#ifdef HAVE_STDATOMIC_H + /* Atomic version of "*atomic = 0" */ + atomic_store (&atomic->value, 0); +#elif DBUS_USE_SYNC + /* Atomic version of "*atomic &= 0; return *atomic" */ + __sync_and_and_fetch (&atomic->value, 0); +#else + pthread_mutex_lock (&atomic_mutex); + atomic->value = 0; + pthread_mutex_unlock (&atomic_mutex); +#endif +} + +/** + * Atomically set the value of an integer to something nonzero. + * + * @param atomic pointer to the integer to set + */ +void +_dbus_atomic_set_nonzero (DBusAtomic *atomic) +{ +#ifdef HAVE_STDATOMIC_H + /* Atomic version of "*atomic = 1" */ + atomic_store (&atomic->value, 1); +#elif DBUS_USE_SYNC + /* Atomic version of "*atomic |= 1; return *atomic" */ + __sync_or_and_fetch (&atomic->value, 1); +#else + pthread_mutex_lock (&atomic_mutex); + atomic->value = 1; + pthread_mutex_unlock (&atomic_mutex); +#endif +} + +/** + * Wrapper for poll(). + * + * @param fds the file descriptors to poll + * @param n_fds number of descriptors in the array + * @param timeout_milliseconds timeout or -1 for infinite + * @returns numbers of fds with revents, or <0 on error + */ +int +_dbus_poll (DBusPollFD *fds, + int n_fds, + int timeout_milliseconds) +{ +#if defined(HAVE_POLL) && !defined(BROKEN_POLL) + /* DBusPollFD is a struct pollfd in this code path, so we can just poll() */ + if (timeout_milliseconds < -1) + { + timeout_milliseconds = -1; + } + + return poll (fds, + n_fds, + timeout_milliseconds); +#else /* ! HAVE_POLL */ + /* Emulate poll() in terms of select() */ + fd_set read_set, write_set, err_set; + int max_fd = 0; + int i; + struct timeval tv; + int ready; + + FD_ZERO (&read_set); + FD_ZERO (&write_set); + FD_ZERO (&err_set); + + for (i = 0; i < n_fds; i++) + { + DBusPollFD *fdp = &fds[i]; + + if (fdp->events & _DBUS_POLLIN) + FD_SET (fdp->fd, &read_set); + + if (fdp->events & _DBUS_POLLOUT) + FD_SET (fdp->fd, &write_set); + + FD_SET (fdp->fd, &err_set); + + max_fd = MAX (max_fd, fdp->fd); + } + + tv.tv_sec = timeout_milliseconds / 1000; + tv.tv_usec = (timeout_milliseconds % 1000) * 1000; + + ready = select (max_fd + 1, &read_set, &write_set, &err_set, + timeout_milliseconds < 0 ? NULL : &tv); + + if (ready > 0) + { + for (i = 0; i < n_fds; i++) + { + DBusPollFD *fdp = &fds[i]; + + fdp->revents = 0; + + if (FD_ISSET (fdp->fd, &read_set)) + fdp->revents |= _DBUS_POLLIN; + + if (FD_ISSET (fdp->fd, &write_set)) + fdp->revents |= _DBUS_POLLOUT; + + if (FD_ISSET (fdp->fd, &err_set)) + fdp->revents |= _DBUS_POLLERR; + } + } + + return ready; +#endif +} + +/** + * Get current time, as in gettimeofday(). Use the monotonic clock if + * available, to avoid problems when the system time changes. + * + * @param tv_sec return location for number of seconds + * @param tv_usec return location for number of microseconds + */ +void +_dbus_get_monotonic_time (dbus_int64_t *tv_sec, + long *tv_usec) +{ +#ifdef HAVE_MONOTONIC_CLOCK + struct timespec ts; + clock_gettime (CLOCK_MONOTONIC, &ts); + + if (tv_sec) + *tv_sec = ts.tv_sec; + if (tv_usec) + *tv_usec = ts.tv_nsec / 1000; +#else + struct timeval t; + + gettimeofday (&t, NULL); + + if (tv_sec) + *tv_sec = t.tv_sec; + if (tv_usec) + *tv_usec = t.tv_usec; +#endif +} + +/** + * Get current time, as in gettimeofday(). Never uses the monotonic + * clock. + * + * @param tv_sec return location for number of seconds + * @param tv_usec return location for number of microseconds + */ +void +_dbus_get_real_time (dbus_int64_t *tv_sec, + long *tv_usec) +{ + struct timeval t; + + gettimeofday (&t, NULL); + + if (tv_sec) + *tv_sec = t.tv_sec; + if (tv_usec) + *tv_usec = t.tv_usec; +} + +/** + * Creates a directory; succeeds if the directory + * is created or already existed. + * + * @param filename directory filename + * @param error initialized error object + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_ensure_directory (const DBusString *filename, + DBusError *error) +{ + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + if (mkdir (filename_c, 0700) < 0) + { + if (errno == EEXIST) + return TRUE; + + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to create directory %s: %s\n", + filename_c, _dbus_strerror (errno)); + return FALSE; + } + else + return TRUE; +} + +/** + * Creates a directory. Unlike _dbus_ensure_directory(), this only succeeds + * if the directory is genuinely newly-created. + * + * @param filename directory filename + * @param error initialized error object + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_create_directory (const DBusString *filename, + DBusError *error) +{ + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + if (mkdir (filename_c, 0700) < 0) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to create directory %s: %s\n", + filename_c, _dbus_strerror (errno)); + return FALSE; + } + else + return TRUE; +} + +/** + * Appends the given filename to the given directory. + * + * @todo it might be cute to collapse multiple '/' such as "foo//" + * concat "//bar" + * + * @param dir the directory name + * @param next_component the filename + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_concat_dir_and_file (DBusString *dir, + const DBusString *next_component) +{ + dbus_bool_t dir_ends_in_slash; + dbus_bool_t file_starts_with_slash; + + if (_dbus_string_get_length (dir) == 0 || + _dbus_string_get_length (next_component) == 0) + return TRUE; + + dir_ends_in_slash = '/' == _dbus_string_get_byte (dir, + _dbus_string_get_length (dir) - 1); + + file_starts_with_slash = '/' == _dbus_string_get_byte (next_component, 0); + + if (dir_ends_in_slash && file_starts_with_slash) + { + _dbus_string_shorten (dir, 1); + } + else if (!(dir_ends_in_slash || file_starts_with_slash)) + { + if (!_dbus_string_append_byte (dir, '/')) + return FALSE; + } + + return _dbus_string_copy (next_component, 0, dir, + _dbus_string_get_length (dir)); +} + +/** nanoseconds in a second */ +#define NANOSECONDS_PER_SECOND 1000000000 +/** microseconds in a second */ +#define MICROSECONDS_PER_SECOND 1000000 +/** milliseconds in a second */ +#define MILLISECONDS_PER_SECOND 1000 +/** nanoseconds in a millisecond */ +#define NANOSECONDS_PER_MILLISECOND 1000000 +/** microseconds in a millisecond */ +#define MICROSECONDS_PER_MILLISECOND 1000 + +/** + * Sleeps the given number of milliseconds. + * @param milliseconds number of milliseconds + */ +void +_dbus_sleep_milliseconds (int milliseconds) +{ +#ifdef HAVE_NANOSLEEP + struct timespec req; + struct timespec rem; + + req.tv_sec = milliseconds / MILLISECONDS_PER_SECOND; + req.tv_nsec = (milliseconds % MILLISECONDS_PER_SECOND) * NANOSECONDS_PER_MILLISECOND; + rem.tv_sec = 0; + rem.tv_nsec = 0; + + while (nanosleep (&req, &rem) < 0 && errno == EINTR) + req = rem; +#elif defined (HAVE_USLEEP) + usleep (milliseconds * MICROSECONDS_PER_MILLISECOND); +#else /* ! HAVE_USLEEP */ + sleep (MAX (milliseconds / 1000, 1)); +#endif +} + +/** + * Generates the given number of securely random bytes, + * using the best mechanism we can come up with. + * + * @param str the string + * @param n_bytes the number of random bytes to append to string + * @param error location to store reason for failure + * @returns #TRUE on success, #FALSE on error + */ +dbus_bool_t +_dbus_generate_random_bytes (DBusString *str, + int n_bytes, + DBusError *error) +{ + int old_len = _dbus_string_get_length (str); + int fd; + int result; +#ifdef HAVE_GETRANDOM + char *buffer; + + if (!_dbus_string_lengthen (str, n_bytes)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + buffer = _dbus_string_get_data_len (str, old_len, n_bytes); + result = getrandom (buffer, n_bytes, GRND_NONBLOCK); + + if (result == n_bytes) + return TRUE; + + _dbus_string_set_length (str, old_len); +#endif + + /* note, urandom on linux will fall back to pseudorandom */ + fd = open ("/dev/urandom", O_RDONLY); + + if (fd < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not open /dev/urandom: %s", + _dbus_strerror (errno)); + return FALSE; + } + + _dbus_verbose ("/dev/urandom fd %d opened\n", fd); + + result = _dbus_read (fd, str, n_bytes); + + if (result != n_bytes) + { + if (result < 0) + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not read /dev/urandom: %s", + _dbus_strerror (errno)); + else + dbus_set_error (error, DBUS_ERROR_IO_ERROR, + "Short read from /dev/urandom"); + + _dbus_close (fd, NULL); + _dbus_string_set_length (str, old_len); + return FALSE; + } + + _dbus_verbose ("Read %d bytes from /dev/urandom\n", + n_bytes); + + _dbus_close (fd, NULL); + + return TRUE; +} + +/** + * Exit the process, returning the given value. + * + * @param code the exit code + */ +void +_dbus_exit (int code) +{ + _exit (code); +} + +/** + * A wrapper around strerror() because some platforms + * may be lame and not have strerror(). Also, never + * returns NULL. + * + * @param error_number errno. + * @returns error description. + */ +const char* +_dbus_strerror (int error_number) +{ + const char *msg; + + msg = strerror (error_number); + if (msg == NULL) + msg = "unknown"; + + return msg; +} + +/** + * signal (SIGPIPE, SIG_IGN); + */ +void +_dbus_disable_sigpipe (void) +{ + signal (SIGPIPE, SIG_IGN); +} + +/** + * Sets the file descriptor to be close + * on exec. Should be called for all file + * descriptors in D-Bus code. + * + * @param fd the file descriptor + */ +void +_dbus_fd_set_close_on_exec (int fd) +{ + int val; + + val = fcntl (fd, F_GETFD, 0); + + if (val < 0) + return; + + val |= FD_CLOEXEC; + + fcntl (fd, F_SETFD, val); +} + +/** + * Sets the file descriptor to *not* be close-on-exec. This can be called + * after _dbus_fd_set_all_close_on_exec() to make exceptions for pipes + * used to communicate with child processes. + * + * @param fd the file descriptor + */ +void +_dbus_fd_clear_close_on_exec (int fd) +{ + int val; + + val = fcntl (fd, F_GETFD, 0); + + if (val < 0) + return; + + val &= ~FD_CLOEXEC; + + fcntl (fd, F_SETFD, val); +} + +/** + * Closes a file descriptor. + * + * @param fd the file descriptor + * @param error error object + * @returns #FALSE if error set + */ +dbus_bool_t +_dbus_close (int fd, + DBusError *error) +{ + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + again: + if (close (fd) < 0) + { + if (errno == EINTR) + goto again; + + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not close fd %d", fd); + return FALSE; + } + + return TRUE; +} + +/** + * Duplicates a file descriptor. Makes sure the fd returned is >= 3 + * (i.e. avoids stdin/stdout/stderr). Sets O_CLOEXEC. + * + * @param fd the file descriptor to duplicate + * @param error address of error location. + * @returns duplicated file descriptor + * */ +int +_dbus_dup(int fd, + DBusError *error) +{ + int new_fd; + +#ifdef F_DUPFD_CLOEXEC + dbus_bool_t cloexec_done; + + new_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + cloexec_done = new_fd >= 0; + + if (new_fd < 0 && errno == EINVAL) +#endif + { + new_fd = fcntl(fd, F_DUPFD, 3); + } + + if (new_fd < 0) { + + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not duplicate fd %d", fd); + return -1; + } + +#ifdef F_DUPFD_CLOEXEC + if (!cloexec_done) +#endif + { + _dbus_fd_set_close_on_exec(new_fd); + } + + return new_fd; +} + +/** + * Sets a file descriptor to be nonblocking. + * + * @param fd the file descriptor. + * @param error address of error location. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_set_socket_nonblocking (DBusSocket fd, + DBusError *error) +{ + return _dbus_set_fd_nonblocking (fd.fd, error); +} + +static dbus_bool_t +_dbus_set_fd_nonblocking (int fd, + DBusError *error) +{ + int val; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + val = fcntl (fd, F_GETFL, 0); + if (val < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to get flags from file descriptor %d: %s", + fd, _dbus_strerror (errno)); + _dbus_verbose ("Failed to get flags for fd %d: %s\n", fd, + _dbus_strerror (errno)); + return FALSE; + } + + if (fcntl (fd, F_SETFL, val | O_NONBLOCK) < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to set nonblocking flag of file descriptor %d: %s", + fd, _dbus_strerror (errno)); + _dbus_verbose ("Failed to set fd %d nonblocking: %s\n", + fd, _dbus_strerror (errno)); + + return FALSE; + } + + return TRUE; +} + +/** + * On GNU libc systems, print a crude backtrace to stderr. On other + * systems, print "no backtrace support" and block for possible gdb + * attachment if an appropriate environment variable is set. + */ +void +_dbus_print_backtrace (void) +{ +#if defined (HAVE_BACKTRACE) && defined (DBUS_BUILT_R_DYNAMIC) + void *bt[500]; + int bt_size; + int i; + char **syms; + + bt_size = backtrace (bt, 500); + + syms = backtrace_symbols (bt, bt_size); + + i = 0; + while (i < bt_size) + { + /* don't use dbus_warn since it can _dbus_abort() */ + fprintf (stderr, " %s\n", syms[i]); + ++i; + } + fflush (stderr); + + free (syms); +#elif defined (HAVE_BACKTRACE) && ! defined (DBUS_BUILT_R_DYNAMIC) + fprintf (stderr, " D-Bus not built with -rdynamic so unable to print a backtrace\n"); +#else + fprintf (stderr, " D-Bus not compiled with backtrace support so unable to print a backtrace\n"); +#endif +} + +/** + * Creates pair of connect sockets (as in socketpair()). + * Sets both ends of the pair nonblocking. + * + * Marks both file descriptors as close-on-exec + * + * @param fd1 return location for one end + * @param fd2 return location for the other end + * @param blocking #TRUE if pair should be blocking + * @param error error return + * @returns #FALSE on failure (if error is set) + */ +dbus_bool_t +_dbus_socketpair (DBusSocket *fd1, + DBusSocket *fd2, + dbus_bool_t blocking, + DBusError *error) +{ +#ifdef HAVE_SOCKETPAIR + int fds[2]; + int retval; + +#ifdef SOCK_CLOEXEC + dbus_bool_t cloexec_done; + + retval = socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, fds); + cloexec_done = retval >= 0; + + if (retval < 0 && (errno == EINVAL || errno == EPROTOTYPE)) +#endif + { + retval = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); + } + + if (retval < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not create full-duplex pipe"); + return FALSE; + } + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + +#ifdef SOCK_CLOEXEC + if (!cloexec_done) +#endif + { + _dbus_fd_set_close_on_exec (fds[0]); + _dbus_fd_set_close_on_exec (fds[1]); + } + + if (!blocking && + (!_dbus_set_fd_nonblocking (fds[0], NULL) || + !_dbus_set_fd_nonblocking (fds[1], NULL))) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not set full-duplex pipe nonblocking"); + + _dbus_close (fds[0], NULL); + _dbus_close (fds[1], NULL); + + return FALSE; + } + + fd1->fd = fds[0]; + fd2->fd = fds[1]; + + _dbus_verbose ("full-duplex pipe %d <-> %d\n", + fd1->fd, fd2->fd); + + return TRUE; +#else + _dbus_warn ("_dbus_socketpair() not implemented on this OS"); + dbus_set_error (error, DBUS_ERROR_FAILED, + "_dbus_socketpair() not implemented on this OS"); + return FALSE; +#endif +} + +/** + * Measure the length of the given format string and arguments, + * not including the terminating nul. + * + * @param format a printf-style format string + * @param args arguments for the format string + * @returns length of the given format string and args, or -1 if no memory + */ +int +_dbus_printf_string_upper_bound (const char *format, + va_list args) +{ + char static_buf[1024]; + int bufsize = sizeof (static_buf); + int len; + va_list args_copy; + + va_copy (args_copy, args); + len = vsnprintf (static_buf, bufsize, format, args_copy); + va_end (args_copy); + + /* If vsnprintf() returned non-negative, then either the string fits in + * static_buf, or this OS has the POSIX and C99 behaviour where vsnprintf + * returns the number of characters that were needed, or this OS returns the + * truncated length. + * + * We ignore the possibility that snprintf might just ignore the length and + * overrun the buffer (64-bit Solaris 7), because that's pathological. + * If your libc is really that bad, come back when you have a better one. */ + if (len == bufsize) + { + /* This could be the truncated length (Tru64 and IRIX have this bug), + * or the real length could be coincidentally the same. Which is it? + * If vsnprintf returns the truncated length, we'll go to the slow + * path. */ + va_copy (args_copy, args); + + if (vsnprintf (static_buf, 1, format, args_copy) == 1) + len = -1; + + va_end (args_copy); + } + + /* If vsnprintf() returned negative, we have to do more work. + * HP-UX returns negative. */ + while (len < 0) + { + char *buf; + + bufsize *= 2; + + buf = dbus_malloc (bufsize); + + if (buf == NULL) + return -1; + + va_copy (args_copy, args); + len = vsnprintf (buf, bufsize, format, args_copy); + va_end (args_copy); + + dbus_free (buf); + + /* If the reported length is exactly the buffer size, round up to the + * next size, in case vsnprintf has been returning the truncated + * length */ + if (len == bufsize) + len = -1; + } + + return len; +} + +/** + * Gets the temporary files directory by inspecting the environment variables + * TMPDIR, TMP, and TEMP in that order. If none of those are set "/tmp" is returned + * + * @returns location of temp directory, or #NULL if no memory for locking + */ +const char* +_dbus_get_tmpdir(void) +{ + /* Protected by _DBUS_LOCK_sysdeps */ + static const char* tmpdir = NULL; + + if (!_DBUS_LOCK (sysdeps)) + return NULL; + + if (tmpdir == NULL) + { + /* TMPDIR is what glibc uses, then + * glibc falls back to the P_tmpdir macro which + * just expands to "/tmp" + */ + if (tmpdir == NULL) + tmpdir = getenv("TMPDIR"); + + /* These two env variables are probably + * broken, but maybe some OS uses them? + */ + if (tmpdir == NULL) + tmpdir = getenv("TMP"); + if (tmpdir == NULL) + tmpdir = getenv("TEMP"); + + /* And this is the sane fallback. */ + if (tmpdir == NULL) + tmpdir = "/tmp"; + } + + _DBUS_UNLOCK (sysdeps); + + _dbus_assert(tmpdir != NULL); + + return tmpdir; +} + +#if defined(DBUS_ENABLE_X11_AUTOLAUNCH) || defined(DBUS_ENABLE_LAUNCHD) +/** + * Execute a subprocess, returning up to 1024 bytes of output + * into @p result. + * + * If successful, returns #TRUE and appends the output to @p + * result. If a failure happens, returns #FALSE and + * sets an error in @p error. + * + * @note It's not an error if the subprocess terminates normally + * without writing any data to stdout. Verify the @p result length + * before and after this function call to cover this case. + * + * @param progname initial path to exec (may or may not be absolute) + * @param path_fallback if %TRUE, search PATH for executable + * @param argv NULL-terminated list of arguments + * @param result a DBusString where the output can be append + * @param error a DBusError to store the error in case of failure + * @returns #TRUE on success, #FALSE if an error happened + */ +static dbus_bool_t +_read_subprocess_line_argv (const char *progpath, + dbus_bool_t path_fallback, + const char * const *argv, + DBusString *result, + DBusError *error) +{ + int result_pipe[2] = { -1, -1 }; + int errors_pipe[2] = { -1, -1 }; + pid_t pid; + int ret; + int status; + int orig_len; + + dbus_bool_t retval; + sigset_t new_set, old_set; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + retval = FALSE; + + /* We need to block any existing handlers for SIGCHLD temporarily; they + * will cause waitpid() below to fail. + * https://bugs.freedesktop.org/show_bug.cgi?id=21347 + */ + sigemptyset (&new_set); + sigaddset (&new_set, SIGCHLD); + sigprocmask (SIG_BLOCK, &new_set, &old_set); + + orig_len = _dbus_string_get_length (result); + +#define READ_END 0 +#define WRITE_END 1 + if (pipe (result_pipe) < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to create a pipe to call %s: %s", + progpath, _dbus_strerror (errno)); + _dbus_verbose ("Failed to create a pipe to call %s: %s\n", + progpath, _dbus_strerror (errno)); + goto out; + } + if (pipe (errors_pipe) < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to create a pipe to call %s: %s", + progpath, _dbus_strerror (errno)); + _dbus_verbose ("Failed to create a pipe to call %s: %s\n", + progpath, _dbus_strerror (errno)); + goto out; + } + + /* Make sure our output buffers aren't redundantly printed by both the + * parent and the child */ + fflush (stdout); + fflush (stderr); + + pid = fork (); + if (pid < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to fork() to call %s: %s", + progpath, _dbus_strerror (errno)); + _dbus_verbose ("Failed to fork() to call %s: %s\n", + progpath, _dbus_strerror (errno)); + goto out; + } + + if (pid == 0) + { + /* child process */ + const char *error_str; + + if (!_dbus_ensure_standard_fds (DBUS_FORCE_STDIN_NULL, &error_str)) + { + int saved_errno = errno; + + /* Try to write details into the pipe, but don't bother + * trying too hard (no retry loop). */ + + if (write (errors_pipe[WRITE_END], error_str, strlen (error_str)) < 0 || + write (errors_pipe[WRITE_END], ": ", 2) < 0) + { + /* ignore, not much we can do */ + } + + error_str = _dbus_strerror (saved_errno); + + if (write (errors_pipe[WRITE_END], error_str, strlen (error_str)) < 0) + { + /* ignore, not much we can do */ + } + + _exit (1); + } + + /* set-up stdXXX */ + close (result_pipe[READ_END]); + close (errors_pipe[READ_END]); + + if (dup2 (result_pipe[WRITE_END], 1) == -1) /* setup stdout */ + _exit (1); + if (dup2 (errors_pipe[WRITE_END], 2) == -1) /* setup stderr */ + _exit (1); + + _dbus_close_all (); + + sigprocmask (SIG_SETMASK, &old_set, NULL); + + /* If it looks fully-qualified, try execv first */ + if (progpath[0] == '/') + { + execv (progpath, (char * const *) argv); + /* Ok, that failed. Now if path_fallback is given, let's + * try unqualified. This is mostly a hack to work + * around systems which ship dbus-launch in /usr/bin + * but everything else in /bin (because dbus-launch + * depends on X11). + */ + if (path_fallback) + /* We must have a slash, because we checked above */ + execvp (strrchr (progpath, '/')+1, (char * const *) argv); + } + else + execvp (progpath, (char * const *) argv); + + /* still nothing, we failed */ + _exit (1); + } + + /* parent process */ + close (result_pipe[WRITE_END]); + close (errors_pipe[WRITE_END]); + result_pipe[WRITE_END] = -1; + errors_pipe[WRITE_END] = -1; + + ret = 0; + do + { + ret = _dbus_read (result_pipe[READ_END], result, 1024); + } + while (ret > 0); + + /* reap the child process to avoid it lingering as zombie */ + do + { + ret = waitpid (pid, &status, 0); + } + while (ret == -1 && errno == EINTR); + + /* We succeeded if the process exited with status 0 and + anything was read */ + if (!WIFEXITED (status) || WEXITSTATUS (status) != 0 ) + { + /* The process ended with error */ + DBusString error_message; + if (!_dbus_string_init (&error_message)) + { + _DBUS_SET_OOM (error); + goto out; + } + + ret = 0; + do + { + ret = _dbus_read (errors_pipe[READ_END], &error_message, 1024); + } + while (ret > 0); + + _dbus_string_set_length (result, orig_len); + if (_dbus_string_get_length (&error_message) > 0) + dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED, + "%s terminated abnormally with the following error: %s", + progpath, _dbus_string_get_data (&error_message)); + else + dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED, + "%s terminated abnormally without any error message", + progpath); + goto out; + } + + retval = TRUE; + + out: + sigprocmask (SIG_SETMASK, &old_set, NULL); + + _DBUS_ASSERT_ERROR_XOR_BOOL (error, retval); + + if (result_pipe[0] != -1) + close (result_pipe[0]); + if (result_pipe[1] != -1) + close (result_pipe[1]); + if (errors_pipe[0] != -1) + close (errors_pipe[0]); + if (errors_pipe[1] != -1) + close (errors_pipe[1]); + + return retval; +} +#endif + +/** + * Returns the address of a new session bus. + * + * If successful, returns #TRUE and appends the address to @p + * address. If a failure happens, returns #FALSE and + * sets an error in @p error. + * + * @param scope scope of autolaunch (Windows only) + * @param address a DBusString where the address can be stored + * @param error a DBusError to store the error in case of failure + * @returns #TRUE on success, #FALSE if an error happened + */ +dbus_bool_t +_dbus_get_autolaunch_address (const char *scope, + DBusString *address, + DBusError *error) +{ +#ifdef DBUS_ENABLE_X11_AUTOLAUNCH + static const char arg_dbus_launch[] = "dbus-launch"; + static const char arg_autolaunch[] = "--autolaunch"; + static const char arg_binary_syntax[] = "--binary-syntax"; + static const char arg_close_stderr[] = "--close-stderr"; + + /* Perform X11-based autolaunch. (We also support launchd-based autolaunch, + * but that's done elsewhere, and if it worked, this function wouldn't + * be called.) */ + const char *display; + const char *progpath; + const char *argv[6]; + int i; + DBusString uuid; + dbus_bool_t retval; + + if (_dbus_check_setuid ()) + { + dbus_set_error_const (error, DBUS_ERROR_NOT_SUPPORTED, + "Unable to autolaunch when setuid"); + return FALSE; + } + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + retval = FALSE; + + /* fd.o #19997: if $DISPLAY isn't set to something useful, then + * dbus-launch-x11 is just going to fail. Rather than trying to + * run it, we might as well bail out early with a nice error. + * + * This is not strictly true in a world where the user bus exists, + * because dbus-launch --autolaunch knows how to connect to that - + * but if we were going to connect to the user bus, we'd have done + * so before trying autolaunch: in any case. */ + display = _dbus_getenv ("DISPLAY"); + + if (display == NULL || display[0] == '\0') + { + dbus_set_error_const (error, DBUS_ERROR_NOT_SUPPORTED, + "Unable to autolaunch a dbus-daemon without a $DISPLAY for X11"); + return FALSE; + } + + if (!_dbus_string_init (&uuid)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_get_local_machine_uuid_encoded (&uuid, error)) + { + goto out; + } + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + progpath = _dbus_getenv ("DBUS_TEST_DBUS_LAUNCH"); + + if (progpath == NULL) +#endif + progpath = DBUS_BINDIR "/dbus-launch"; + /* + * argv[0] is always dbus-launch, that's the name what we'll + * get from /proc, or ps(1), regardless what the progpath is, + * see fd.o#69716 + */ + i = 0; + argv[i] = arg_dbus_launch; + ++i; + argv[i] = arg_autolaunch; + ++i; + argv[i] = _dbus_string_get_data (&uuid); + ++i; + argv[i] = arg_binary_syntax; + ++i; + argv[i] = arg_close_stderr; + ++i; + argv[i] = NULL; + ++i; + + _dbus_assert (i == _DBUS_N_ELEMENTS (argv)); + + retval = _read_subprocess_line_argv (progpath, + TRUE, + argv, address, error); + + out: + _dbus_string_free (&uuid); + return retval; +#else + dbus_set_error_const (error, DBUS_ERROR_NOT_SUPPORTED, + "Using X11 for dbus-daemon autolaunch was disabled at compile time, " + "set your DBUS_SESSION_BUS_ADDRESS instead"); + return FALSE; +#endif +} + +/** + * Reads the uuid of the machine we're running on from + * the dbus configuration. Optionally try to create it + * (only root can do this usually). + * + * On UNIX, reads a file that gets created by dbus-uuidgen + * in a post-install script. On Windows, if there's a standard + * machine uuid we could just use that, but I can't find one + * with the right properties (the hardware profile guid can change + * without rebooting I believe). If there's no standard one + * we might want to use the registry instead of a file for + * this, and I'm not sure how we'd ensure the uuid gets created. + * + * @param machine_id guid to init with the machine's uuid + * @param create_if_not_found try to create the uuid if it doesn't exist + * @param error the error return + * @returns #FALSE if the error is set + */ +dbus_bool_t +_dbus_read_local_machine_uuid (DBusGUID *machine_id, + dbus_bool_t create_if_not_found, + DBusError *error) +{ + DBusError our_error = DBUS_ERROR_INIT; + DBusError etc_error = DBUS_ERROR_INIT; + DBusString filename; + dbus_bool_t b; + + _dbus_string_init_const (&filename, DBUS_MACHINE_UUID_FILE); + + b = _dbus_read_uuid_file (&filename, machine_id, FALSE, &our_error); + if (b) + return TRUE; + + /* Fallback to the system machine ID */ + _dbus_string_init_const (&filename, "/etc/machine-id"); + b = _dbus_read_uuid_file (&filename, machine_id, FALSE, &etc_error); + + if (b) + { + if (create_if_not_found) + { + /* try to copy it to the DBUS_MACHINE_UUID_FILE, but do not + * complain if that isn't possible for whatever reason */ + _dbus_string_init_const (&filename, DBUS_MACHINE_UUID_FILE); + _dbus_write_uuid_file (&filename, machine_id, NULL); + } + + dbus_error_free (&our_error); + return TRUE; + } + + if (!create_if_not_found) + { + dbus_set_error (error, etc_error.name, + "D-Bus library appears to be incorrectly set up: " + "see the manual page for dbus-uuidgen to correct " + "this issue. (%s; %s)", + our_error.message, etc_error.message); + dbus_error_free (&our_error); + dbus_error_free (&etc_error); + return FALSE; + } + + dbus_error_free (&our_error); + dbus_error_free (&etc_error); + + /* if none found, try to make a new one */ + _dbus_string_init_const (&filename, DBUS_MACHINE_UUID_FILE); + + if (!_dbus_generate_uuid (machine_id, error)) + return FALSE; + + return _dbus_write_uuid_file (&filename, machine_id, error); +} + +/** + * quries launchd for a specific env var which holds the socket path. + * @param socket_path append the socket path to this DBusString + * @param launchd_env_var the env var to look up + * @param error a DBusError to store the error in case of failure + * @return the value of the env var + */ +dbus_bool_t +_dbus_lookup_launchd_socket (DBusString *socket_path, + const char *launchd_env_var, + DBusError *error) +{ +#ifdef DBUS_ENABLE_LAUNCHD + char *argv[4]; + int i; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (_dbus_check_setuid ()) + { + dbus_set_error_const (error, DBUS_ERROR_NOT_SUPPORTED, + "Unable to find launchd socket when setuid"); + return FALSE; + } + + i = 0; + argv[i] = "launchctl"; + ++i; + argv[i] = "getenv"; + ++i; + argv[i] = (char*)launchd_env_var; + ++i; + argv[i] = NULL; + ++i; + + _dbus_assert (i == _DBUS_N_ELEMENTS (argv)); + + if (!_read_subprocess_line_argv(argv[0], TRUE, argv, socket_path, error)) + { + return FALSE; + } + + /* no error, but no result either */ + if (_dbus_string_get_length(socket_path) == 0) + { + return FALSE; + } + + /* strip the carriage-return */ + _dbus_string_shorten(socket_path, 1); + return TRUE; +#else /* DBUS_ENABLE_LAUNCHD */ + dbus_set_error(error, DBUS_ERROR_NOT_SUPPORTED, + "can't lookup socket from launchd; launchd support not compiled in"); + return FALSE; +#endif +} + +#ifdef DBUS_ENABLE_LAUNCHD +static dbus_bool_t +_dbus_lookup_session_address_launchd (DBusString *address, DBusError *error) +{ + dbus_bool_t valid_socket; + DBusString socket_path; + + if (_dbus_check_setuid ()) + { + dbus_set_error_const (error, DBUS_ERROR_NOT_SUPPORTED, + "Unable to find launchd socket when setuid"); + return FALSE; + } + + if (!_dbus_string_init (&socket_path)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + valid_socket = _dbus_lookup_launchd_socket (&socket_path, "DBUS_LAUNCHD_SESSION_BUS_SOCKET", error); + + if (dbus_error_is_set(error)) + { + _dbus_string_free(&socket_path); + return FALSE; + } + + if (!valid_socket) + { + dbus_set_error(error, "no socket path", + "launchd did not provide a socket path, " + "verify that org.freedesktop.dbus-session.plist is loaded!"); + _dbus_string_free(&socket_path); + return FALSE; + } + if (!_dbus_string_append (address, "unix:path=")) + { + _DBUS_SET_OOM (error); + _dbus_string_free(&socket_path); + return FALSE; + } + if (!_dbus_string_copy (&socket_path, 0, address, + _dbus_string_get_length (address))) + { + _DBUS_SET_OOM (error); + _dbus_string_free(&socket_path); + return FALSE; + } + + _dbus_string_free(&socket_path); + return TRUE; +} +#endif + +static dbus_bool_t +_dbus_lookup_user_bus (dbus_bool_t *supported, + DBusString *address, + DBusError *error) +{ + const char *runtime_dir = _dbus_getenv ("XDG_RUNTIME_DIR"); + dbus_bool_t ret = FALSE; + struct stat stbuf; + DBusString user_bus_path; + + if (runtime_dir == NULL) + { + _dbus_verbose ("XDG_RUNTIME_DIR not found in environment"); + *supported = FALSE; + return TRUE; /* Cannot use it, but not an error */ + } + + if (!_dbus_string_init (&user_bus_path)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_append_printf (&user_bus_path, "%s/bus", runtime_dir)) + { + _DBUS_SET_OOM (error); + goto out; + } + + if (lstat (_dbus_string_get_const_data (&user_bus_path), &stbuf) == -1) + { + _dbus_verbose ("XDG_RUNTIME_DIR/bus not available: %s", + _dbus_strerror (errno)); + *supported = FALSE; + ret = TRUE; /* Cannot use it, but not an error */ + goto out; + } + + if (stbuf.st_uid != getuid ()) + { + _dbus_verbose ("XDG_RUNTIME_DIR/bus owned by uid %ld, not our uid %ld", + (long) stbuf.st_uid, (long) getuid ()); + *supported = FALSE; + ret = TRUE; /* Cannot use it, but not an error */ + goto out; + } + + if ((stbuf.st_mode & S_IFMT) != S_IFSOCK) + { + _dbus_verbose ("XDG_RUNTIME_DIR/bus is not a socket: st_mode = 0o%lo", + (long) stbuf.st_mode); + *supported = FALSE; + ret = TRUE; /* Cannot use it, but not an error */ + goto out; + } + + if (!_dbus_string_append (address, "unix:path=") || + !_dbus_address_append_escaped (address, &user_bus_path)) + { + _DBUS_SET_OOM (error); + goto out; + } + + *supported = TRUE; + ret = TRUE; + +out: + _dbus_string_free (&user_bus_path); + return ret; +} + +/** + * Determines the address of the session bus by querying a + * platform-specific method. + * + * The first parameter will be a boolean specifying whether + * or not a dynamic session lookup is supported on this platform. + * + * If supported is TRUE and the return value is #TRUE, the + * address will be appended to @p address. + * If a failure happens, returns #FALSE and sets an error in + * @p error. + * + * If supported is FALSE, ignore the return value. + * + * @param supported returns whether this method is supported + * @param address a DBusString where the address can be stored + * @param error a DBusError to store the error in case of failure + * @returns #TRUE on success, #FALSE if an error happened + */ +dbus_bool_t +_dbus_lookup_session_address (dbus_bool_t *supported, + DBusString *address, + DBusError *error) +{ +#ifdef DBUS_ENABLE_LAUNCHD + *supported = TRUE; + return _dbus_lookup_session_address_launchd (address, error); +#else + *supported = FALSE; + + if (!_dbus_lookup_user_bus (supported, address, error)) + return FALSE; + else if (*supported) + return TRUE; + + /* On non-Mac Unix platforms, if the session address isn't already + * set in DBUS_SESSION_BUS_ADDRESS environment variable and the + * $XDG_RUNTIME_DIR/bus can't be used, we punt and fall back to the + * autolaunch: global default; see init_session_address in + * dbus/dbus-bus.c. */ + return TRUE; +#endif +} + +/** + * Called when the bus daemon is signaled to reload its configuration; any + * caches should be nuked. Of course any caches that need explicit reload + * are probably broken, but c'est la vie. + * + * + */ +void +_dbus_flush_caches (void) +{ + _dbus_user_database_flush_system (); +} + +/** + * Appends the directory in which a keyring for the given credentials + * should be stored. The credentials should have either a Windows or + * UNIX user in them. The directory should be an absolute path. + * + * On UNIX the directory is ~/.dbus-keyrings while on Windows it should probably + * be something else, since the dotfile convention is not normal on Windows. + * + * @param directory string to append directory to + * @param credentials credentials the directory should be for + * + * @returns #FALSE on no memory + */ +dbus_bool_t +_dbus_append_keyring_directory_for_credentials (DBusString *directory, + DBusCredentials *credentials) +{ + DBusString homedir; + DBusString dotdir; + dbus_uid_t uid; + + _dbus_assert (credentials != NULL); + _dbus_assert (!_dbus_credentials_are_anonymous (credentials)); + + if (!_dbus_string_init (&homedir)) + return FALSE; + + uid = _dbus_credentials_get_unix_uid (credentials); + _dbus_assert (uid != DBUS_UID_UNSET); + + if (!_dbus_homedir_from_uid (uid, &homedir)) + goto failed; + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + { + const char *override; + + override = _dbus_getenv ("DBUS_TEST_HOMEDIR"); + if (override != NULL && *override != '\0') + { + _dbus_string_set_length (&homedir, 0); + if (!_dbus_string_append (&homedir, override)) + goto failed; + + _dbus_verbose ("Using fake homedir for testing: %s\n", + _dbus_string_get_const_data (&homedir)); + } + else + { + /* Not strictly thread-safe, but if we fail at thread-safety here, + * the worst that will happen is some extra warnings. */ + static dbus_bool_t already_warned = FALSE; + if (!already_warned) + { + _dbus_warn ("Using %s for testing, set DBUS_TEST_HOMEDIR to avoid", + _dbus_string_get_const_data (&homedir)); + already_warned = TRUE; + } + } + } +#endif + + _dbus_string_init_const (&dotdir, ".dbus-keyrings"); + if (!_dbus_concat_dir_and_file (&homedir, + &dotdir)) + goto failed; + + if (!_dbus_string_copy (&homedir, 0, + directory, _dbus_string_get_length (directory))) { + goto failed; + } + + _dbus_string_free (&homedir); + return TRUE; + + failed: + _dbus_string_free (&homedir); + return FALSE; +} + +/* Documented in dbus-sysdeps-win.c, does nothing on Unix */ +dbus_bool_t +_dbus_daemon_unpublish_session_bus_address (void) +{ + return TRUE; +} + +/** + * See if errno is EAGAIN or EWOULDBLOCK (this has to be done differently + * for Winsock so is abstracted) + * + * @returns #TRUE if e == EAGAIN or e == EWOULDBLOCK + */ +dbus_bool_t +_dbus_get_is_errno_eagain_or_ewouldblock (int e) +{ + /* Avoid the -Wlogical-op GCC warning, which can be triggered when EAGAIN and + * EWOULDBLOCK are numerically equal, which is permitted as described by + * errno(3). + */ +#if EAGAIN == EWOULDBLOCK + return e == EAGAIN; +#else + return e == EAGAIN || e == EWOULDBLOCK; +#endif +} + +/** + * Removes a directory; Directory must be empty + * + * @param filename directory filename + * @param error initialized error object + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_delete_directory (const DBusString *filename, + DBusError *error) +{ + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + if (rmdir (filename_c) != 0) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to remove directory %s: %s\n", + filename_c, _dbus_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +/** + * Checks whether file descriptors may be passed via the socket + * + * @param fd the socket + * @return TRUE when fd passing over this socket is supported + * + */ +dbus_bool_t +_dbus_socket_can_pass_unix_fd (DBusSocket fd) +{ +#ifdef SCM_RIGHTS + union { + struct sockaddr sa; + struct sockaddr_storage storage; + struct sockaddr_un un; + } sa_buf; + + socklen_t sa_len = sizeof(sa_buf); + + _DBUS_ZERO(sa_buf); + + if (getsockname(fd.fd, &sa_buf.sa, &sa_len) < 0) + return FALSE; + + return sa_buf.sa.sa_family == AF_UNIX; + +#else + return FALSE; + +#endif +} + +/* + * Similar to Solaris fdwalk(3), but without the ability to stop iteration, + * and may call func for integers that are not actually valid fds. + */ +static void +act_on_fds_3_and_up (void (*func) (int fd)) +{ + int maxfds, i; + +#if defined(__linux__) && defined(__GLIBC__) + DIR *d; + + /* On Linux we can optimize this a bit if /proc is available. If it + isn't available, fall back to the brute force way. */ + + d = opendir ("/proc/self/fd"); + if (d) + { + for (;;) + { + struct dirent *de; + int fd; + long l; + char *e = NULL; + + de = readdir (d); + if (!de) + break; + + if (de->d_name[0] == '.') + continue; + + errno = 0; + l = strtol (de->d_name, &e, 10); + if (errno != 0 || e == NULL || *e != '\0') + continue; + + fd = (int) l; + if (fd < 3) + continue; + + if (fd == dirfd (d)) + continue; + + func (fd); + } + + closedir (d); + return; + } +#endif + + maxfds = sysconf (_SC_OPEN_MAX); + + /* Pick something reasonable if for some reason sysconf says + * unlimited. + */ + if (maxfds < 0) + maxfds = 1024; + + /* close all inherited fds */ + for (i = 3; i < maxfds; i++) + func (i); +} + +/* Some library implementations of closefrom() are not async-signal-safe, + * and we call _dbus_close_all() after forking, so we only do this on + * operating systems where we know that closefrom() is a system call */ +#if defined(HAVE_CLOSEFROM) && ( \ + defined(__FreeBSD__) || \ + defined(__NetBSD__) || \ + defined(__OpenBSD__) || \ + defined(__sun__) && defined(F_CLOSEFROM) \ +) +#define CLOSEFROM_SIGNAL_SAFE 1 +#else +#define CLOSEFROM_SIGNAL_SAFE 0 +static void +close_ignore_error (int fd) +{ + close (fd); +} +#endif + +/** + * Closes all file descriptors except the first three (i.e. stdin, + * stdout, stderr). + */ +void +_dbus_close_all (void) +{ +#ifdef HAVE_CLOSE_RANGE + if (close_range (3, INT_MAX, 0) == 0) + return; +#endif + +#if CLOSEFROM_SIGNAL_SAFE + closefrom (3); +#else + act_on_fds_3_and_up (close_ignore_error); +#endif +} + +/** + * Sets all file descriptors except the first three (i.e. stdin, + * stdout, stderr) to be close-on-execute. + */ +void +_dbus_fd_set_all_close_on_exec (void) +{ +#if defined(HAVE_CLOSE_RANGE) && defined(CLOSE_RANGE_CLOEXEC) + if (close_range (3, INT_MAX, CLOSE_RANGE_CLOEXEC) == 0) + return; +#endif + + act_on_fds_3_and_up (_dbus_fd_set_close_on_exec); +} + +/** + * **NOTE**: If you modify this function, please also consider making + * the corresponding change in GLib. See + * glib/gutils.c:g_check_setuid(). + * + * Returns TRUE if the current process was executed as setuid (or an + * equivalent __libc_enable_secure is available). See: + * http://osdir.com/ml/linux.lfs.hardened/2007-04/msg00032.html + */ +dbus_bool_t +_dbus_check_setuid (void) +{ + /* TODO: get __libc_enable_secure exported from glibc. + * See http://www.openwall.com/lists/owl-dev/2012/08/14/1 + */ +#if 0 && defined(HAVE_LIBC_ENABLE_SECURE) + { + /* See glibc/include/unistd.h */ + extern int __libc_enable_secure; + return __libc_enable_secure; + } +#elif defined(HAVE_ISSETUGID) + /* BSD: http://www.freebsd.org/cgi/man.cgi?query=issetugid&sektion=2 */ + return issetugid (); +#else + uid_t ruid, euid, suid; /* Real, effective and saved user ID's */ + gid_t rgid, egid, sgid; /* Real, effective and saved group ID's */ + + /* We call into this function from _dbus_threads_init_platform_specific() + * to make sure these are initialized before we start threading. */ + static dbus_bool_t check_setuid_initialised; + static dbus_bool_t is_setuid; + + if (_DBUS_UNLIKELY (!check_setuid_initialised)) + { +#ifdef HAVE_GETRESUID + if (getresuid (&ruid, &euid, &suid) != 0 || + getresgid (&rgid, &egid, &sgid) != 0) +#endif /* HAVE_GETRESUID */ + { + suid = ruid = getuid (); + sgid = rgid = getgid (); + euid = geteuid (); + egid = getegid (); + } + + check_setuid_initialised = TRUE; + is_setuid = (ruid != euid || ruid != suid || + rgid != egid || rgid != sgid); + + } + return is_setuid; +#endif +} + +/** + * Read the address from the socket and append it to the string + * + * @param fd the socket + * @param address + * @param error return location for error code + */ +dbus_bool_t +_dbus_append_address_from_socket (DBusSocket fd, + DBusString *address, + DBusError *error) +{ + union { + struct sockaddr sa; + struct sockaddr_storage storage; + struct sockaddr_un un; + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } socket; + char hostip[INET6_ADDRSTRLEN]; + socklen_t size = sizeof (socket); + DBusString path_str; + const char *family_name = NULL; + dbus_uint16_t port; + + if (getsockname (fd.fd, &socket.sa, &size)) + goto err; + + switch (socket.sa.sa_family) + { + case AF_UNIX: + if (socket.un.sun_path[0]=='\0') + { + _dbus_string_init_const (&path_str, &(socket.un.sun_path[1])); + if (_dbus_string_append (address, "unix:abstract=") && + _dbus_address_append_escaped (address, &path_str)) + { + return TRUE; + } + else + { + _DBUS_SET_OOM (error); + return FALSE; + } + } + else + { + _dbus_string_init_const (&path_str, socket.un.sun_path); + if (_dbus_string_append (address, "unix:path=") && + _dbus_address_append_escaped (address, &path_str)) + { + return TRUE; + } + else + { + _DBUS_SET_OOM (error); + return FALSE; + } + } + /* not reached */ + break; + + case AF_INET: +#ifdef AF_INET6 + case AF_INET6: +#endif + _dbus_string_init_const (&path_str, hostip); + + if (_dbus_inet_sockaddr_to_string (&socket, size, hostip, sizeof (hostip), + &family_name, &port, error)) + { + if (_dbus_string_append_printf (address, "tcp:family=%s,port=%u,host=", + family_name, port) && + _dbus_address_append_escaped (address, &path_str)) + { + return TRUE; + } + else + { + _DBUS_SET_OOM (error); + return FALSE; + } + } + else + { + return FALSE; + } + /* not reached */ + break; + + default: + dbus_set_error (error, + _dbus_error_from_errno (EINVAL), + "Failed to read address from socket: Unknown socket type."); + return FALSE; + } + err: + dbus_set_error (error, + _dbus_error_from_errno (errno), + "Failed to read address from socket: %s", + _dbus_strerror (errno)); + return FALSE; +} + +int +_dbus_save_socket_errno (void) +{ + return errno; +} + +void +_dbus_restore_socket_errno (int saved_errno) +{ + errno = saved_errno; +} + +static const char *syslog_tag = "dbus"; +#ifdef HAVE_SYSLOG_H +static DBusLogFlags log_flags = DBUS_LOG_FLAGS_STDERR; +#endif + +/** + * Initialize the system log. + * + * The "tag" is not copied, and must remain valid for the entire lifetime of + * the process or until _dbus_init_system_log() is called again. In practice + * it will normally be a constant. + * + * On platforms that do not support a system log, the + * #DBUS_LOG_FLAGS_SYSTEM_LOG flag is treated as equivalent to + * #DBUS_LOG_FLAGS_STDERR. + * + * @param tag the name of the executable (syslog tag) + * @param mode whether to log to stderr, the system log or both + */ +void +_dbus_init_system_log (const char *tag, + DBusLogFlags flags) +{ + /* We never want to turn off logging completely */ + _dbus_assert ( + (flags & (DBUS_LOG_FLAGS_STDERR | DBUS_LOG_FLAGS_SYSTEM_LOG)) != 0); + + syslog_tag = tag; + +#ifdef HAVE_SYSLOG_H + log_flags = flags; + + if (log_flags & DBUS_LOG_FLAGS_SYSTEM_LOG) + openlog (tag, LOG_PID, LOG_DAEMON); +#endif +} + +/** + * Log a message to the system log file (e.g. syslog on Unix) and/or stderr. + * + * @param severity a severity value + * @param msg a printf-style format string + * @param args arguments for the format string + */ +void +_dbus_logv (DBusSystemLogSeverity severity, + const char *msg, + va_list args) +{ + va_list tmp; +#ifdef HAVE_SYSLOG_H + if (log_flags & DBUS_LOG_FLAGS_SYSTEM_LOG) + { + int flags; + switch (severity) + { + case DBUS_SYSTEM_LOG_INFO: + flags = LOG_DAEMON | LOG_INFO; + break; + case DBUS_SYSTEM_LOG_WARNING: + flags = LOG_DAEMON | LOG_WARNING; + break; + case DBUS_SYSTEM_LOG_SECURITY: + flags = LOG_AUTH | LOG_NOTICE; + break; + case DBUS_SYSTEM_LOG_ERROR: + flags = LOG_DAEMON|LOG_CRIT; + break; + default: + _dbus_assert_not_reached ("invalid log severity"); + } + + va_copy (tmp, args); + vsyslog (flags, msg, tmp); + va_end (tmp); + } + + /* If we don't have syslog.h, we always behave as though stderr was in + * the flags */ + if (log_flags & DBUS_LOG_FLAGS_STDERR) +#endif + { + va_copy (tmp, args); + fprintf (stderr, "%s[" DBUS_PID_FORMAT "]: ", syslog_tag, _dbus_getpid ()); + vfprintf (stderr, msg, tmp); + fputc ('\n', stderr); + va_end (tmp); + } +} + +/* + * Return the low-level representation of a socket error, as used by + * cross-platform socket APIs like inet_ntop(), send() and recv(). This + * is the standard errno on Unix, but is WSAGetLastError() on Windows. + * + * Some libdbus internal functions copy this into errno, but with + * hindsight that was probably a design flaw. + */ +int +_dbus_get_low_level_socket_errno (void) +{ + return errno; +} + +/* tests in dbus-sysdeps-util.c */ diff --git a/src/3rdparty/libdbus/dbus/dbus-sysdeps-unix.h b/src/3rdparty/libdbus/dbus/dbus-sysdeps-unix.h new file mode 100644 index 00000000..b400cf86 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-sysdeps-unix.h @@ -0,0 +1,169 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sysdeps-unix.h UNIX-specific wrappers around system/libc features (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003, 2006 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_SYSDEPS_UNIX_H +#define DBUS_SYSDEPS_UNIX_H + +#include <dbus/dbus-sysdeps.h> + +#ifdef DBUS_WIN +#error "Don't include this on Windows" +#endif + +DBUS_BEGIN_DECLS + +/** + * @defgroup DBusSysdepsUnix UNIX-specific internal API + * @ingroup DBusInternals + * @brief Internal system-dependent API available on UNIX only + * @{ + */ + +DBUS_PRIVATE_EXPORT +dbus_bool_t +_dbus_close (int fd, + DBusError *error); +DBUS_PRIVATE_EXPORT +int _dbus_dup (int fd, + DBusError *error); +DBUS_PRIVATE_EXPORT +int +_dbus_read (int fd, + DBusString *buffer, + int count); +int +_dbus_write (int fd, + const DBusString *buffer, + int start, + int len); +int +_dbus_write_two (int fd, + const DBusString *buffer1, + int start1, + int len1, + const DBusString *buffer2, + int start2, + int len2); + +int _dbus_listen_systemd_sockets (DBusSocket **fd, + DBusError *error); + +dbus_bool_t _dbus_read_credentials (int client_fd, + DBusCredentials *credentials, + DBusError *error); +dbus_bool_t _dbus_send_credentials (int server_fd, + DBusError *error); + +dbus_bool_t _dbus_lookup_launchd_socket (DBusString *socket_path, + const char *launchd_env_var, + DBusError *error); + +/** Information about a UNIX user */ +typedef struct DBusUserInfo DBusUserInfo; +/** Information about a UNIX group */ +typedef struct DBusGroupInfo DBusGroupInfo; + +/** + * Information about a UNIX user + */ +struct DBusUserInfo +{ + size_t refcount; /**< Reference count */ + dbus_uid_t uid; /**< UID */ + dbus_gid_t primary_gid; /**< GID */ + dbus_gid_t *group_ids; /**< Groups IDs, *including* above primary group */ + int n_group_ids; /**< Size of group IDs array */ + char *username; /**< Username */ + char *homedir; /**< Home directory */ +}; + +/** + * Information about a UNIX group + */ +struct DBusGroupInfo +{ + size_t refcount; /**< Reference count */ + dbus_gid_t gid; /**< GID */ + char *groupname; /**< Group name */ +}; + +dbus_bool_t _dbus_user_info_fill (DBusUserInfo *info, + const DBusString *username, + DBusError *error); +dbus_bool_t _dbus_user_info_fill_uid (DBusUserInfo *info, + dbus_uid_t uid, + DBusError *error); +void _dbus_user_info_free (DBusUserInfo *info); + +dbus_bool_t _dbus_group_info_fill (DBusGroupInfo *info, + const DBusString *groupname, + DBusError *error); +dbus_bool_t _dbus_group_info_fill_gid (DBusGroupInfo *info, + dbus_gid_t gid, + DBusError *error); +void _dbus_group_info_free (DBusGroupInfo *info); + +DBUS_PRIVATE_EXPORT +dbus_uid_t _dbus_geteuid (void); + +DBUS_PRIVATE_EXPORT +void _dbus_close_all (void); +DBUS_PRIVATE_EXPORT +void _dbus_fd_set_all_close_on_exec (void); +DBUS_PRIVATE_EXPORT +void _dbus_fd_clear_close_on_exec (int fd); + +dbus_bool_t _dbus_append_address_from_socket (DBusSocket fd, + DBusString *address, + DBusError *error); + +DBUS_PRIVATE_EXPORT +void _dbus_fd_set_close_on_exec (int fd); + +typedef enum +{ + DBUS_FORCE_STDIN_NULL = (1 << 0), + DBUS_FORCE_STDOUT_NULL = (1 << 1), + DBUS_FORCE_STDERR_NULL = (1 << 2) +} DBusEnsureStandardFdsFlags; + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_ensure_standard_fds (DBusEnsureStandardFdsFlags flags, + const char **error_str_p); + +/** A UNIX signal handler */ +typedef void (* DBusSignalHandler) (int sig); + +void _dbus_set_signal_handler (int sig, + DBusSignalHandler handler); + +dbus_bool_t _dbus_reset_oom_score_adj (const char **error_str_p); + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_SYSDEPS_UNIX_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-sysdeps-win.c b/src/3rdparty/libdbus/dbus/dbus-sysdeps-win.c new file mode 100644 index 00000000..d44dc0a9 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-sysdeps-win.c @@ -0,0 +1,4521 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sysdeps.c Wrappers around system/libc features (internal to D-BUS implementation) + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2005 Novell, Inc. + * Copyright (C) 2006 Peter Kümmel <syntheticpp@gmx.net> + * Copyright (C) 2006 Christian Ehrlicher <ch.ehrlicher@gmx.de> + * Copyright (C) 2006-2021 Ralf Habacker <ralf.habacker@freenet.de> + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <config.h> + +#define STRSAFE_NO_DEPRECATE + +#include "dbus-internals.h" +#include "dbus-sha.h" +#include "dbus-sysdeps.h" +#include "dbus-threads.h" +#include "dbus-protocol.h" +#include "dbus-string.h" +#include "dbus-sysdeps.h" +#include "dbus-sysdeps-win.h" +#include "dbus-protocol.h" +#include "dbus-hash.h" +#include "dbus-sockets-win.h" +#include "dbus-list.h" +#include "dbus-nonce.h" +#include "dbus-credentials.h" + +#include <windows.h> +#include <wincrypt.h> +#include <iphlpapi.h> +#ifdef HAVE_AFUNIX_H +#include <afunix.h> +#endif + +/* Declarations missing in mingw's and windows sdk 7.0 headers */ +extern BOOL WINAPI ConvertStringSidToSidA (LPCSTR StringSid, PSID *Sid); +extern BOOL WINAPI ConvertSidToStringSidA (PSID Sid, LPSTR *StringSid); + +#include <stdio.h> +#include <stdlib.h> + +#include <string.h> +#if HAVE_ERRNO_H +#include <errno.h> +#endif +#ifndef DBUS_WINCE +#include <mbstring.h> +#include <sys/stat.h> +#include <sys/types.h> +#endif + +#ifdef HAVE_WS2TCPIP_H +/* getaddrinfo for Windows CE (and Windows). */ +#include <ws2tcpip.h> +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#ifndef PROCESS_QUERY_LIMITED_INFORMATION +/* MinGW32 < 4 does not define this value in its headers */ +#define PROCESS_QUERY_LIMITED_INFORMATION (0x1000) +#endif + +typedef int socklen_t; + +/* uncomment to enable windows event based poll implementation */ +//#define USE_CHRIS_IMPL + +void +_dbus_win_set_errno (int err) +{ +#ifdef DBUS_WINCE + SetLastError (err); +#else + errno = err; +#endif +} + +static BOOL is_winxp_sp3_or_lower (void); + +/* + * _MIB_TCPROW_EX and friends are not available in system headers + * and are mapped to attribute identical ...OWNER_PID typedefs. + */ +typedef MIB_TCPROW_OWNER_PID _MIB_TCPROW_EX; +typedef MIB_TCPTABLE_OWNER_PID MIB_TCPTABLE_EX; +typedef PMIB_TCPTABLE_OWNER_PID PMIB_TCPTABLE_EX; +typedef DWORD (WINAPI *ProcAllocateAndGetTcpExtTableFromStack)(PMIB_TCPTABLE_EX*,BOOL,HANDLE,DWORD,DWORD); + +/* Not protected by a lock, but if we miss a write, all that + * happens is that the lazy initialization will happen in two threads + * concurrently - it results in the same value either way so that's OK */ +static ProcAllocateAndGetTcpExtTableFromStack lpfnAllocateAndGetTcpExTableFromStack = NULL; + +/** + * AllocateAndGetTcpExTableFromStack() is undocumented and not exported, + * but is the only way to do this in older XP versions. + * @return true if the procedures could be loaded + */ +static BOOL +load_ex_ip_helper_procedures(void) +{ + HMODULE hModule = LoadLibrary ("iphlpapi.dll"); + if (hModule == NULL) + { + _dbus_verbose ("could not load iphlpapi.dll\n"); + return FALSE; + } + + lpfnAllocateAndGetTcpExTableFromStack = (ProcAllocateAndGetTcpExtTableFromStack) (void (*)(void))GetProcAddress (hModule, "AllocateAndGetTcpExTableFromStack"); + if (lpfnAllocateAndGetTcpExTableFromStack == NULL) + { + _dbus_verbose ("could not find function AllocateAndGetTcpExTableFromStack in iphlpapi.dll\n"); + return FALSE; + } + return TRUE; +} + +/** + * get pid from localhost tcp connection using peer_port + * This function is available on WinXP >= SP3 + * @param peer_port peers tcp port + * @return process id or 0 if connection has not been found + */ +static dbus_pid_t +get_pid_from_extended_tcp_table(int peer_port) +{ + dbus_pid_t result; + DWORD errorCode, size = 0, i; + MIB_TCPTABLE_OWNER_PID *tcp_table; + + if ((errorCode = + GetExtendedTcpTable (NULL, &size, TRUE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0)) == ERROR_INSUFFICIENT_BUFFER) + { + tcp_table = (MIB_TCPTABLE_OWNER_PID *) dbus_malloc (size); + if (tcp_table == NULL) + { + _dbus_verbose ("Error allocating memory\n"); + return 0; + } + } + else + { + _dbus_win_warn_win_error ("unexpected error returned from GetExtendedTcpTable", errorCode); + return 0; + } + + if ((errorCode = GetExtendedTcpTable (tcp_table, &size, TRUE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0)) != NO_ERROR) + { + _dbus_verbose ("Error fetching tcp table %d\n", (int)errorCode); + dbus_free (tcp_table); + return 0; + } + + result = 0; + for (i = 0; i < tcp_table->dwNumEntries; i++) + { + MIB_TCPROW_OWNER_PID *p = &tcp_table->table[i]; + int local_address = ntohl (p->dwLocalAddr); + int local_port = ntohs (p->dwLocalPort); + if (p->dwState == MIB_TCP_STATE_ESTAB + && local_address == INADDR_LOOPBACK && local_port == peer_port) + result = p->dwOwningPid; + } + + dbus_free (tcp_table); + _dbus_verbose ("got pid %lu\n", result); + return result; +} + +/** + * get pid from localhost tcp connection using peer_port + * This function is available on all WinXP versions, but + * not in wine (at least version <= 1.6.0) + * @param peer_port peers tcp port + * @return process id or 0 if connection has not been found + */ +static dbus_pid_t +get_pid_from_tcp_ex_table(int peer_port) +{ + dbus_pid_t result; + DWORD errorCode, i; + PMIB_TCPTABLE_EX tcp_table = NULL; + + if (!load_ex_ip_helper_procedures ()) + { + _dbus_verbose + ("Error not been able to load iphelper procedures\n"); + return 0; + } + + errorCode = lpfnAllocateAndGetTcpExTableFromStack (&tcp_table, TRUE, GetProcessHeap(), 0, 2); + + if (errorCode != NO_ERROR) + { + _dbus_verbose + ("Error not been able to call AllocateAndGetTcpExTableFromStack()\n"); + return 0; + } + + result = 0; + for (i = 0; i < tcp_table->dwNumEntries; i++) + { + _MIB_TCPROW_EX *p = &tcp_table->table[i]; + int local_port = ntohs (p->dwLocalPort); + int local_address = ntohl (p->dwLocalAddr); + if (local_address == INADDR_LOOPBACK && local_port == peer_port) + { + result = p->dwOwningPid; + break; + } + } + + HeapFree (GetProcessHeap(), 0, tcp_table); + _dbus_verbose ("got pid %lu\n", result); + return result; +} + +/** + * @brief return peer process id from tcp handle for localhost connections + * @param handle tcp socket descriptor + * @return process id or 0 in case the process id could not be fetched + */ +static dbus_pid_t +_dbus_get_peer_pid_from_tcp_handle (int handle) +{ + struct sockaddr_storage addr; + socklen_t len = sizeof (addr); + int peer_port; + + dbus_pid_t result; + dbus_bool_t is_localhost = FALSE; + + getpeername (handle, (struct sockaddr *) &addr, &len); + + if (addr.ss_family == AF_INET) + { + struct sockaddr_in *s = (struct sockaddr_in *) &addr; + peer_port = ntohs (s->sin_port); + is_localhost = (ntohl (s->sin_addr.s_addr) == INADDR_LOOPBACK); + } + else if (addr.ss_family == AF_INET6) + { + _dbus_verbose ("FIXME [61922]: IPV6 support not working on windows\n"); + return 0; + /* + struct sockaddr_in6 *s = (struct sockaddr_in6 * )&addr; + peer_port = ntohs (s->sin6_port); + is_localhost = (memcmp(s->sin6_addr.s6_addr, in6addr_loopback.s6_addr, 16) == 0); + _dbus_verbose ("IPV6 %08x %08x\n", s->sin6_addr.s6_addr, in6addr_loopback.s6_addr); + */ + } + else + { + _dbus_verbose ("no idea what address family %d is\n", addr.ss_family); + return 0; + } + + if (!is_localhost) + { + _dbus_verbose ("could not fetch process id from remote process\n"); + return 0; + } + + if (peer_port == 0) + { + _dbus_verbose + ("Error not been able to fetch tcp peer port from connection\n"); + return 0; + } + + _dbus_verbose ("trying to get peer's pid\n"); + + result = get_pid_from_extended_tcp_table (peer_port); + if (result > 0) + return result; + result = get_pid_from_tcp_ex_table (peer_port); + return result; +} + +/* Convert GetLastError() to a dbus error. */ +const char* +_dbus_win_error_from_last_error (void) +{ + switch (GetLastError()) + { + case 0: + return DBUS_ERROR_FAILED; + + case ERROR_NO_MORE_FILES: + case ERROR_TOO_MANY_OPEN_FILES: + return DBUS_ERROR_LIMITS_EXCEEDED; /* kernel out of memory */ + + case ERROR_ACCESS_DENIED: + case ERROR_CANNOT_MAKE: + return DBUS_ERROR_ACCESS_DENIED; + + case ERROR_NOT_ENOUGH_MEMORY: + return DBUS_ERROR_NO_MEMORY; + + case ERROR_FILE_EXISTS: + return DBUS_ERROR_FILE_EXISTS; + + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + return DBUS_ERROR_FILE_NOT_FOUND; + + default: + return DBUS_ERROR_FAILED; + } +} + + +char* +_dbus_win_error_string (int error_number) +{ + char *msg; + + FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, error_number, 0, + (LPSTR) &msg, 0, NULL); + + if (msg[strlen (msg) - 1] == '\n') + msg[strlen (msg) - 1] = '\0'; + if (msg[strlen (msg) - 1] == '\r') + msg[strlen (msg) - 1] = '\0'; + + return msg; +} + +void +_dbus_win_free_error_string (char *string) +{ + LocalFree (string); +} + +/** + * Socket interface + * + */ + +/** + * Thin wrapper around the read() system call that appends + * the data it reads to the DBusString buffer. It appends + * up to the given count, and returns the same value + * and same errno as read(). The only exception is that + * _dbus_read_socket() handles EINTR for you. + * _dbus_read_socket() can return ENOMEM, even though + * regular UNIX read doesn't. + * + * @param fd the file descriptor to read from + * @param buffer the buffer to append data to + * @param count the amount of data to read + * @returns the number of bytes read or -1 + */ + +int +_dbus_read_socket (DBusSocket fd, + DBusString *buffer, + int count) +{ + int bytes_read; + int start; + char *data; + + _dbus_assert (count >= 0); + + start = _dbus_string_get_length (buffer); + + if (!_dbus_string_lengthen (buffer, count)) + { + _dbus_win_set_errno (ENOMEM); + return -1; + } + + data = _dbus_string_get_data_len (buffer, start, count); + + again: + + _dbus_verbose ("recv: count=%d fd=%Iu\n", count, fd.sock); + bytes_read = recv (fd.sock, data, count, 0); + + if (bytes_read == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO(); + _dbus_verbose ("recv: failed: %s (%d)\n", _dbus_strerror (errno), errno); + bytes_read = -1; + } + else + _dbus_verbose ("recv: = %d\n", bytes_read); + + if (bytes_read < 0) + { + if (errno == EINTR) + goto again; + else + { + /* put length back (note that this doesn't actually realloc anything) */ + _dbus_string_set_length (buffer, start); + return -1; + } + } + else + { + /* put length back (doesn't actually realloc) */ + _dbus_string_set_length (buffer, start + bytes_read); + +#if 0 + if (bytes_read > 0) + _dbus_verbose_bytes_of_string (buffer, start, bytes_read); +#endif + + return bytes_read; + } +} + +/** + * Thin wrapper around the write() system call that writes a part of a + * DBusString and handles EINTR for you. + * + * @param fd the file descriptor to write + * @param buffer the buffer to write data from + * @param start the first byte in the buffer to write + * @param len the number of bytes to try to write + * @returns the number of bytes written or -1 on error + */ +int +_dbus_write_socket (DBusSocket fd, + const DBusString *buffer, + int start, + int len) +{ + const char *data; + int bytes_written; + + data = _dbus_string_get_const_data_len (buffer, start, len); + + again: + + _dbus_verbose ("send: len=%d fd=%Iu\n", len, fd.sock); + bytes_written = send (fd.sock, data, len, 0); + + if (bytes_written == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO(); + _dbus_verbose ("send: failed: %s\n", _dbus_strerror_from_errno ()); + bytes_written = -1; + } + else + _dbus_verbose ("send: = %d\n", bytes_written); + + if (bytes_written < 0 && errno == EINTR) + goto again; + +#if 0 + if (bytes_written > 0) + _dbus_verbose_bytes_of_string (buffer, start, bytes_written); +#endif + + return bytes_written; +} + + +/** + * Closes a socket and invalidates it. + * + * @param fd the file descriptor + * @param error error object + * @returns #FALSE if error set + */ +dbus_bool_t +_dbus_close_socket (DBusSocket *fd, + DBusError *error) +{ + _dbus_assert (fd != NULL); + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + again: + if (closesocket (fd->sock) == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO (); + + if (errno == EINTR) + goto again; + + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not close socket: socket=%Iu, , %s", + fd->sock, _dbus_strerror_from_errno ()); + _dbus_socket_invalidate (fd); + return FALSE; + } + _dbus_verbose ("socket=%Iu, \n", fd->sock); + + _dbus_socket_invalidate (fd); + return TRUE; +} + +/** + * Sets the file descriptor to be close + * on exec. Should be called for all file + * descriptors in D-Bus code. + * + * @param handle the Windows HANDLE (a SOCKET is also OK) + */ +static void +_dbus_win_handle_set_close_on_exec (HANDLE handle) +{ + if ( !SetHandleInformation( (HANDLE) handle, + HANDLE_FLAG_INHERIT | HANDLE_FLAG_PROTECT_FROM_CLOSE, + 0 /*disable both flags*/ ) ) + { + _dbus_win_warn_win_error ("Disabling socket handle inheritance failed:", GetLastError()); + } +} + +/** + * Sets a file descriptor to be nonblocking. + * + * @param handle the file descriptor. + * @param error address of error location. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_set_socket_nonblocking (DBusSocket handle, + DBusError *error) +{ + u_long one = 1; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (ioctlsocket (handle.sock, FIONBIO, &one) == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO (); + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to set socket %Iu to nonblocking: %s", + handle.sock, _dbus_strerror_from_errno ()); + return FALSE; + } + + return TRUE; +} + + +/** + * Like _dbus_write() but will use writev() if possible + * to write both buffers in sequence. The return value + * is the number of bytes written in the first buffer, + * plus the number written in the second. If the first + * buffer is written successfully and an error occurs + * writing the second, the number of bytes in the first + * is returned (i.e. the error is ignored), on systems that + * don't have writev. Handles EINTR for you. + * The second buffer may be #NULL. + * + * @param fd the file descriptor + * @param buffer1 first buffer + * @param start1 first byte to write in first buffer + * @param len1 number of bytes to write from first buffer + * @param buffer2 second buffer, or #NULL + * @param start2 first byte to write in second buffer + * @param len2 number of bytes to write in second buffer + * @returns total bytes written from both buffers, or -1 on error + */ +int +_dbus_write_socket_two (DBusSocket fd, + const DBusString *buffer1, + int start1, + int len1, + const DBusString *buffer2, + int start2, + int len2) +{ + WSABUF vectors[2]; + const char *data1; + const char *data2; + int rc; + DWORD bytes_written; + + _dbus_assert (buffer1 != NULL); + _dbus_assert (start1 >= 0); + _dbus_assert (start2 >= 0); + _dbus_assert (len1 >= 0); + _dbus_assert (len2 >= 0); + + + data1 = _dbus_string_get_const_data_len (buffer1, start1, len1); + + if (buffer2 != NULL) + data2 = _dbus_string_get_const_data_len (buffer2, start2, len2); + else + { + data2 = NULL; + start2 = 0; + len2 = 0; + } + + vectors[0].buf = (char*) data1; + vectors[0].len = len1; + vectors[1].buf = (char*) data2; + vectors[1].len = len2; + + again: + + _dbus_verbose ("WSASend: len1+2=%d+%d fd=%Iu\n", len1, len2, fd.sock); + rc = WSASend (fd.sock, + vectors, + data2 ? 2 : 1, + &bytes_written, + 0, + NULL, + NULL); + + if (rc == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO (); + _dbus_verbose ("WSASend: failed: %s\n", _dbus_strerror_from_errno ()); + bytes_written = (DWORD) -1; + } + else + _dbus_verbose ("WSASend: = %ld\n", bytes_written); + + if (bytes_written == (DWORD) -1 && errno == EINTR) + goto again; + + return bytes_written; +} + +#if 0 + +/** + * Opens the client side of a Windows named pipe. The connection D-BUS + * file descriptor index is returned. It is set up as nonblocking. + * + * @param path the path to named pipe socket + * @param error return location for error code + * @returns connection D-BUS file descriptor or -1 on error + */ +int +_dbus_connect_named_pipe (const char *path, + DBusError *error) +{ + _dbus_assert_not_reached ("not implemented"); +} + +#endif + +/** + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_win_startup_winsock (void) +{ + /* Straight from MSDN, deuglified */ + + /* Protected by _DBUS_LOCK_sysdeps */ + static dbus_bool_t beenhere = FALSE; + + WORD wVersionRequested; + WSADATA wsaData; + int err; + + if (!_DBUS_LOCK (sysdeps)) + return FALSE; + + if (beenhere) + goto out; + + wVersionRequested = MAKEWORD (2, 0); + + err = WSAStartup (wVersionRequested, &wsaData); + if (err != 0) + { + _dbus_assert_not_reached ("Could not initialize WinSock"); + _dbus_abort (); + } + + /* Confirm that the WinSock DLL supports 2.0. Note that if the DLL + * supports versions greater than 2.0 in addition to 2.0, it will + * still return 2.0 in wVersion since that is the version we + * requested. + */ + if (LOBYTE (wsaData.wVersion) != 2 || + HIBYTE (wsaData.wVersion) != 0) + { + _dbus_assert_not_reached ("No usable WinSock found"); + _dbus_abort (); + } + + beenhere = TRUE; + +out: + _DBUS_UNLOCK (sysdeps); + return TRUE; +} + + + + + + + + + +/************************************************************************ + + UTF / string code + + ************************************************************************/ + +/** + * Measure the message length without terminating nul + */ +int _dbus_printf_string_upper_bound (const char *format, + va_list args) +{ + /* MSVCRT's vsnprintf semantics are a bit different */ + char buf[1024]; + int bufsize; + int len; + va_list args_copy; + + bufsize = sizeof (buf); + va_copy (args_copy, args); + len = _vsnprintf (buf, bufsize - 1, format, args_copy); + va_end (args_copy); + + while (len == -1) /* try again */ + { + char *p; + + bufsize *= 2; + + p = malloc (bufsize); + + if (p == NULL) + return -1; + + va_copy (args_copy, args); + len = _vsnprintf (p, bufsize - 1, format, args_copy); + va_end (args_copy); + free (p); + } + + return len; +} + + +/** + * Returns the UTF-16 form of a UTF-8 string. The result should be + * freed with dbus_free() when no longer needed. + * + * @param str the UTF-8 string + * @param error return location for error code + */ +wchar_t * +_dbus_win_utf8_to_utf16 (const char *str, + DBusError *error) +{ + DBusString s; + int n; + wchar_t *retval; + + _dbus_string_init_const (&s, str); + + if (!_dbus_string_validate_utf8 (&s, 0, _dbus_string_get_length (&s))) + { + dbus_set_error_const (error, DBUS_ERROR_FAILED, "Invalid UTF-8"); + return NULL; + } + + n = MultiByteToWideChar (CP_UTF8, 0, str, -1, NULL, 0); + + if (n == 0) + { + _dbus_win_set_error_from_win_error (error, GetLastError ()); + return NULL; + } + + retval = dbus_new (wchar_t, n); + + if (!retval) + { + _DBUS_SET_OOM (error); + return NULL; + } + + if (MultiByteToWideChar (CP_UTF8, 0, str, -1, retval, n) != n) + { + dbus_free (retval); + dbus_set_error_const (error, DBUS_ERROR_FAILED, "MultiByteToWideChar inconsistency"); + return NULL; + } + + return retval; +} + +/** + * Returns the UTF-8 form of a UTF-16 string. The result should be + * freed with dbus_free() when no longer needed. + * + * @param str the UTF-16 string + * @param error return location for error code + */ +char * +_dbus_win_utf16_to_utf8 (const wchar_t *str, + DBusError *error) +{ + int n; + char *retval; + + n = WideCharToMultiByte (CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); + + if (n == 0) + { + _dbus_win_set_error_from_win_error (error, GetLastError ()); + return NULL; + } + + retval = dbus_malloc (n); + + if (!retval) + { + _DBUS_SET_OOM (error); + return NULL; + } + + if (WideCharToMultiByte (CP_UTF8, 0, str, -1, retval, n, NULL, NULL) != n) + { + dbus_free (retval); + dbus_set_error_const (error, DBUS_ERROR_FAILED, "WideCharToMultiByte inconsistency"); + return NULL; + } + + return retval; +} + + + + + + +/************************************************************************ + + + ************************************************************************/ + +dbus_bool_t +_dbus_win_account_to_sid (const wchar_t *waccount, + void **ppsid, + DBusError *error) +{ + dbus_bool_t retval = FALSE; + DWORD sid_length, wdomain_length; + SID_NAME_USE use; + wchar_t *wdomain; + + *ppsid = NULL; + + sid_length = 0; + wdomain_length = 0; + if (!LookupAccountNameW (NULL, waccount, NULL, &sid_length, + NULL, &wdomain_length, &use) && + GetLastError () != ERROR_INSUFFICIENT_BUFFER) + { + _dbus_win_set_error_from_win_error (error, GetLastError ()); + return FALSE; + } + + *ppsid = dbus_malloc (sid_length); + if (!*ppsid) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + wdomain = dbus_new (wchar_t, wdomain_length); + if (!wdomain) + { + _DBUS_SET_OOM (error); + goto out1; + } + + if (!LookupAccountNameW (NULL, waccount, (PSID) *ppsid, &sid_length, + wdomain, &wdomain_length, &use)) + { + _dbus_win_set_error_from_win_error (error, GetLastError ()); + goto out2; + } + + if (!IsValidSid ((PSID) *ppsid)) + { + dbus_set_error_const (error, DBUS_ERROR_FAILED, "Invalid SID"); + goto out2; + } + + retval = TRUE; + +out2: + dbus_free (wdomain); +out1: + if (!retval) + { + dbus_free (*ppsid); + *ppsid = NULL; + } + + return retval; +} + +/** @} end of sysdeps-win */ + + +/** + * The only reason this is separate from _dbus_getpid() is to allow it + * on Windows for logging but not for other purposes. + * + * @returns process ID to put in log messages + */ +unsigned long +_dbus_pid_for_log (void) +{ + return _dbus_getpid (); +} + +#ifndef DBUS_WINCE + +static BOOL +is_winxp_sp3_or_lower (void) +{ + OSVERSIONINFOEX osvi; + DWORDLONG dwlConditionMask = 0; + int op=VER_LESS_EQUAL; + + // Initialize the OSVERSIONINFOEX structure. + + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + osvi.dwMajorVersion = 5; + osvi.dwMinorVersion = 1; + osvi.wServicePackMajor = 3; + osvi.wServicePackMinor = 0; + + // Initialize the condition mask. + + VER_SET_CONDITION (dwlConditionMask, VER_MAJORVERSION, op); + VER_SET_CONDITION (dwlConditionMask, VER_MINORVERSION, op); + VER_SET_CONDITION (dwlConditionMask, VER_SERVICEPACKMAJOR, op); + VER_SET_CONDITION (dwlConditionMask, VER_SERVICEPACKMINOR, op); + + // Perform the test. + + return VerifyVersionInfo( + &osvi, + VER_MAJORVERSION | VER_MINORVERSION | + VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, + dwlConditionMask); +} + +/** Gets our SID + * @param sid points to sid buffer, need to be freed with LocalFree() + * @param process_id the process id for which the sid should be returned (use 0 for current process) + * @returns process sid + */ +dbus_bool_t +_dbus_getsid(char **sid, dbus_pid_t process_id) +{ + HANDLE process_token = INVALID_HANDLE_VALUE; + TOKEN_USER *token_user = NULL; + DWORD n; + PSID psid; + int retval = FALSE; + + HANDLE process_handle; + if (process_id == 0) + process_handle = GetCurrentProcess(); + else if (is_winxp_sp3_or_lower()) + process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, process_id); + else + process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id); + + if (!OpenProcessToken (process_handle, TOKEN_QUERY, &process_token)) + { + _dbus_win_warn_win_error ("OpenProcessToken failed", GetLastError ()); + goto failed; + } + if ((!GetTokenInformation (process_token, TokenUser, NULL, 0, &n) + && GetLastError () != ERROR_INSUFFICIENT_BUFFER) + || (token_user = alloca (n)) == NULL + || !GetTokenInformation (process_token, TokenUser, token_user, n, &n)) + { + _dbus_win_warn_win_error ("GetTokenInformation failed", GetLastError ()); + goto failed; + } + psid = token_user->User.Sid; + if (!IsValidSid (psid)) + { + _dbus_verbose("invalid sid\n"); + goto failed; + } + if (!ConvertSidToStringSidA (psid, sid)) + { + _dbus_verbose("invalid sid\n"); + goto failed; + } +//okay: + retval = TRUE; + +failed: + CloseHandle (process_handle); + if (process_token != INVALID_HANDLE_VALUE) + CloseHandle (process_token); + + _dbus_verbose("_dbus_getsid() got '%s' and returns %d\n", *sid, retval); + return retval; +} +#endif + +/************************************************************************ + + pipes + + ************************************************************************/ + +/** + * Creates pair of connect sockets (as in socketpair()). + * Sets both ends of the pair nonblocking. + * + * Marks both file descriptors as close-on-exec + * + * @param fd1 return location for one end + * @param fd2 return location for the other end + * @param blocking #TRUE if pair should be blocking + * @param error error return + * @returns #FALSE on failure (if error is set) + */ +dbus_bool_t +_dbus_socketpair (DBusSocket *fd1, + DBusSocket *fd2, + dbus_bool_t blocking, + DBusError *error) +{ + SOCKET temp, socket1 = -1, socket2 = -1; + struct sockaddr_in saddr; + int len; + u_long arg; + + if (!_dbus_win_startup_winsock ()) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + temp = socket (AF_INET, SOCK_STREAM, 0); + if (temp == INVALID_SOCKET) + { + DBUS_SOCKET_SET_ERRNO (); + goto out0; + } + + _DBUS_ZERO (saddr); + saddr.sin_family = AF_INET; + saddr.sin_port = 0; + saddr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + + if (bind (temp, (struct sockaddr *)&saddr, sizeof (saddr)) == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO (); + goto out0; + } + + if (listen (temp, 1) == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO (); + goto out0; + } + + len = sizeof (saddr); + if (getsockname (temp, (struct sockaddr *)&saddr, &len) == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO (); + goto out0; + } + + socket1 = socket (AF_INET, SOCK_STREAM, 0); + if (socket1 == INVALID_SOCKET) + { + DBUS_SOCKET_SET_ERRNO (); + goto out0; + } + + if (connect (socket1, (struct sockaddr *)&saddr, len) == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO (); + goto out1; + } + + socket2 = accept (temp, (struct sockaddr *) &saddr, &len); + if (socket2 == INVALID_SOCKET) + { + DBUS_SOCKET_SET_ERRNO (); + goto out1; + } + + if (!blocking) + { + arg = 1; + if (ioctlsocket (socket1, FIONBIO, &arg) == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO (); + goto out2; + } + + arg = 1; + if (ioctlsocket (socket2, FIONBIO, &arg) == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO (); + goto out2; + } + } + + fd1->sock = socket1; + fd2->sock = socket2; + + _dbus_verbose ("full-duplex pipe %Iu:%Iu <-> %Iu:%Iu\n", + fd1->sock, socket1, fd2->sock, socket2); + + closesocket (temp); + + return TRUE; + +out2: + closesocket (socket2); +out1: + closesocket (socket1); +out0: + closesocket (temp); + + dbus_set_error (error, _dbus_error_from_errno (errno), + "Could not setup socket pair: %s", + _dbus_strerror_from_errno ()); + + return FALSE; +} + +#ifdef DBUS_ENABLE_VERBOSE_MODE +static dbus_bool_t +_dbus_dump_fd_events (DBusPollFD *fds, int n_fds) +{ + DBusString msg = _DBUS_STRING_INIT_INVALID; + dbus_bool_t result = FALSE; + int i; + + if (!_dbus_string_init (&msg)) + goto oom; + + for (i = 0; i < n_fds; i++) + { + DBusPollFD *fdp = &fds[i]; + if (!_dbus_string_append (&msg, i > 0 ? "\n\t" : "\t")) + goto oom; + + if ((fdp->events & _DBUS_POLLIN) && + !_dbus_string_append_printf (&msg, "R:%Iu ", fdp->fd.sock)) + goto oom; + + if ((fdp->events & _DBUS_POLLOUT) && + !_dbus_string_append_printf (&msg, "W:%Iu ", fdp->fd.sock)) + goto oom; + + if (!_dbus_string_append_printf (&msg, "E:%Iu", fdp->fd.sock)) + goto oom; + } + + _dbus_verbose ("%s\n", _dbus_string_get_const_data (&msg)); + result = TRUE; +oom: + _dbus_string_free (&msg); + return result; +} + +#ifdef USE_CHRIS_IMPL +static dbus_bool_t +_dbus_dump_fd_revents (DBusPollFD *fds, int n_fds) +{ + DBusString msg = _DBUS_STRING_INIT_INVALID; + dbus_bool_t result = FALSE; + int i; + + if (!_dbus_string_init (&msg)) + goto oom; + + for (i = 0; i < n_fds; i++) + { + DBusPollFD *fdp = &fds[i]; + if (!_dbus_string_append (&msg, i > 0 ? "\n\t" : "\t")) + goto oom; + + if ((fdp->revents & _DBUS_POLLIN) && + !_dbus_string_append_printf (&msg, "R:%Iu ", fdp->fd.sock)) + goto oom; + + if ((fdp->revents & _DBUS_POLLOUT) && + !_dbus_string_append_printf (&msg, "W:%Iu ", fdp->fd.sock)) + goto oom; + + if ((fdp->revents & _DBUS_POLLERR) && + !_dbus_string_append_printf (&msg, "E:%Iu", fdp->fd.sock)) + goto oom; + } + + _dbus_verbose ("%s\n", _dbus_string_get_const_data (&msg)); + result = TRUE; +oom: + _dbus_string_free (&msg); + return result; +} +#else +static dbus_bool_t +_dbus_dump_fdset (DBusPollFD *fds, int n_fds, fd_set *read_set, fd_set *write_set, fd_set *err_set) +{ + DBusString msg = _DBUS_STRING_INIT_INVALID; + dbus_bool_t result = FALSE; + int i; + + if (!_dbus_string_init (&msg)) + goto oom; + + for (i = 0; i < n_fds; i++) + { + DBusPollFD *fdp = &fds[i]; + + if (!_dbus_string_append (&msg, i > 0 ? "\n\t" : "\t")) + goto oom; + + if (FD_ISSET (fdp->fd.sock, read_set) && + !_dbus_string_append_printf (&msg, "R:%Iu ", fdp->fd.sock)) + goto oom; + + if (FD_ISSET (fdp->fd.sock, write_set) && + !_dbus_string_append_printf (&msg, "W:%Iu ", fdp->fd.sock)) + goto oom; + + if (FD_ISSET (fdp->fd.sock, err_set) && + !_dbus_string_append_printf (&msg, "E:%Iu", fdp->fd.sock)) + goto oom; + } + _dbus_verbose ("%s\n", _dbus_string_get_const_data (&msg)); + result = TRUE; +oom: + _dbus_string_free (&msg); + return result; +} +#endif +#endif + +#ifdef USE_CHRIS_IMPL +/** + * Windows event based implementation for _dbus_poll(). + * + * @param fds the file descriptors to poll + * @param n_fds number of descriptors in the array + * @param timeout_milliseconds timeout or -1 for infinite + * @returns numbers of fds with revents, or <0 on error + */ +static int +_dbus_poll_events (DBusPollFD *fds, + int n_fds, + int timeout_milliseconds) +{ + int ret = 0; + int i; + DWORD ready; + +#define DBUS_STACK_WSAEVENTS 256 + WSAEVENT eventsOnStack[DBUS_STACK_WSAEVENTS]; + WSAEVENT *pEvents = NULL; + if (n_fds > DBUS_STACK_WSAEVENTS) + pEvents = calloc(sizeof(WSAEVENT), n_fds); + else + pEvents = eventsOnStack; + + if (pEvents == NULL) + { + _dbus_win_set_errno (ENOMEM); + ret = -1; + goto oom; + } + +#ifdef DBUS_ENABLE_VERBOSE_MODE + _dbus_verbose ("_dbus_poll: to=%d", timeout_milliseconds); + if (!_dbus_dump_fd_events (fds, n_fds)) + { + _dbus_win_set_errno (ENOMEM); + ret = -1; + goto oom; + } +#endif + + for (i = 0; i < n_fds; i++) + pEvents[i] = WSA_INVALID_EVENT; + + for (i = 0; i < n_fds; i++) + { + DBusPollFD *fdp = &fds[i]; + WSAEVENT ev; + long lNetworkEvents = FD_OOB; + + ev = WSACreateEvent(); + + if (fdp->events & _DBUS_POLLIN) + lNetworkEvents |= FD_READ | FD_ACCEPT | FD_CLOSE; + + if (fdp->events & _DBUS_POLLOUT) + lNetworkEvents |= FD_WRITE | FD_CONNECT; + + WSAEventSelect (fdp->fd.sock, ev, lNetworkEvents); + + pEvents[i] = ev; + } + + ready = WSAWaitForMultipleEvents (n_fds, pEvents, FALSE, timeout_milliseconds, FALSE); + + if (ready == WSA_WAIT_FAILED) + { + DBUS_SOCKET_SET_ERRNO (); + if (errno != WSAEWOULDBLOCK) + _dbus_verbose ("WSAWaitForMultipleEvents: failed: %s\n", _dbus_strerror_from_errno ()); + ret = -1; + } + else if (ready == WSA_WAIT_TIMEOUT) + { + _dbus_verbose ("WSAWaitForMultipleEvents: WSA_WAIT_TIMEOUT\n"); + ret = 0; + } + else if (ready < (WSA_WAIT_EVENT_0 + n_fds)) + { + for (i = 0; i < n_fds; i++) + { + DBusPollFD *fdp = &fds[i]; + WSANETWORKEVENTS ne; + + fdp->revents = 0; + + WSAEnumNetworkEvents (fdp->fd.sock, pEvents[i], &ne); + + if (ne.lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) + fdp->revents |= _DBUS_POLLIN; + + if (ne.lNetworkEvents & (FD_WRITE | FD_CONNECT)) + fdp->revents |= _DBUS_POLLOUT; + + if (ne.lNetworkEvents & (FD_OOB)) + fdp->revents |= _DBUS_POLLERR; + + if(ne.lNetworkEvents) + ret++; + + WSAEventSelect (fdp->fd.sock, pEvents[i], 0); + } +#ifdef DBUS_ENABLE_VERBOSE_MODE + _dbus_verbose ("_dbus_poll: to=%d", timeout_milliseconds); + if (!_dbus_dump_fd_revents (fds, n_fds)) + { + _dbus_win_set_errno (ENOMEM); + ret = -1; + goto oom; + } +#endif + } + else + { + _dbus_verbose ("WSAWaitForMultipleEvents: failed for unknown reason!"); + ret = -1; + } + +oom: + if (pEvents != NULL) + { + for (i = 0; i < n_fds; i++) + { + if (pEvents[i] != WSA_INVALID_EVENT) + WSACloseEvent (pEvents[i]); + } + if (n_fds > DBUS_STACK_WSAEVENTS) + free (pEvents); + } + + return ret; +} +#else +/** + * Select based implementation for _dbus_poll(). + * + * @param fds the file descriptors to poll + * @param n_fds number of descriptors in the array + * @param timeout_milliseconds timeout or -1 for infinite + * @returns numbers of fds with revents, or <0 on error + */ +static int +_dbus_poll_select (DBusPollFD *fds, + int n_fds, + int timeout_milliseconds) +{ + fd_set read_set, write_set, err_set; + SOCKET max_fd = 0; + int i; + struct timeval tv; + int ready; + + FD_ZERO (&read_set); + FD_ZERO (&write_set); + FD_ZERO (&err_set); +#ifdef DBUS_ENABLE_VERBOSE_MODE + _dbus_verbose("_dbus_poll: to=%d\n", timeout_milliseconds); + if (!_dbus_dump_fd_events (fds, n_fds)) + return -1; +#endif + + for (i = 0; i < n_fds; i++) + { + DBusPollFD *fdp = &fds[i]; + + if (fdp->events & _DBUS_POLLIN) + FD_SET (fdp->fd.sock, &read_set); + + if (fdp->events & _DBUS_POLLOUT) + FD_SET (fdp->fd.sock, &write_set); + + FD_SET (fdp->fd.sock, &err_set); + + max_fd = MAX (max_fd, fdp->fd.sock); + } + + // Avoid random lockups with send(), for lack of a better solution so far + tv.tv_sec = timeout_milliseconds < 0 ? 1 : timeout_milliseconds / 1000; + tv.tv_usec = timeout_milliseconds < 0 ? 0 : (timeout_milliseconds % 1000) * 1000; + + ready = select (max_fd + 1, &read_set, &write_set, &err_set, &tv); + + if (DBUS_SOCKET_API_RETURNS_ERROR (ready)) + { + DBUS_SOCKET_SET_ERRNO (); + if (errno != WSAEWOULDBLOCK) + _dbus_verbose ("select: failed: %s\n", _dbus_strerror_from_errno ()); + } + else if (ready == 0) + _dbus_verbose ("select: = 0\n"); + else + if (ready > 0) + { +#ifdef DBUS_ENABLE_VERBOSE_MODE + _dbus_verbose ("select: to=%d\n", ready); + if (!_dbus_dump_fdset (fds, n_fds, &read_set, &write_set, &err_set)) + { + _dbus_win_set_errno (ENOMEM); + return -1; + } +#endif + for (i = 0; i < n_fds; i++) + { + DBusPollFD *fdp = &fds[i]; + + fdp->revents = 0; + + if (FD_ISSET (fdp->fd.sock, &read_set)) + fdp->revents |= _DBUS_POLLIN; + + if (FD_ISSET (fdp->fd.sock, &write_set)) + fdp->revents |= _DBUS_POLLOUT; + + if (FD_ISSET (fdp->fd.sock, &err_set)) + fdp->revents |= _DBUS_POLLERR; + } + } + return ready; +} +#endif + +/** + * Wrapper for poll(). + * + * @param fds the file descriptors to poll + * @param n_fds number of descriptors in the array + * @param timeout_milliseconds timeout or -1 for infinite + * @returns numbers of fds with revents, or <0 on error + */ +int +_dbus_poll (DBusPollFD *fds, + int n_fds, + int timeout_milliseconds) +{ +#ifdef USE_CHRIS_IMPL + return _dbus_poll_events (fds, n_fds, timeout_milliseconds); +#else + return _dbus_poll_select (fds, n_fds, timeout_milliseconds); +#endif +} + +/****************************************************************************** + +Original CVS version of dbus-sysdeps.c + +******************************************************************************/ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sysdeps.c Wrappers around system/libc features (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2005 Novell, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +/** + * Exit the process, returning the given value. + * + * @param code the exit code + */ +void +_dbus_exit (int code) +{ + _exit (code); +} + +/** + * Creates a socket and connects to a socket at the given host + * and port. The connection fd is returned, and is set up as + * nonblocking. + * + * @param host the host name to connect to + * @param port the port to connect to + * @param family the address family to listen on, NULL for all + * @param error return location for error code + * @returns connection file descriptor or -1 on error + */ +DBusSocket +_dbus_connect_tcp_socket (const char *host, + const char *port, + const char *family, + DBusError *error) +{ + return _dbus_connect_tcp_socket_with_nonce (host, port, family, (const char*)NULL, error); +} + +DBusSocket +_dbus_connect_tcp_socket_with_nonce (const char *host, + const char *port, + const char *family, + const char *noncefile, + DBusError *error) +{ + int saved_errno = 0; + DBusList *connect_errors = NULL; + DBusSocket fd = DBUS_SOCKET_INIT; + int res; + struct addrinfo hints; + struct addrinfo *ai = NULL; + const struct addrinfo *tmp; + DBusError *connect_error; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_win_startup_winsock ()) + { + _DBUS_SET_OOM (error); + return _dbus_socket_get_invalid (); + } + + _DBUS_ZERO (hints); + + if (!family) + hints.ai_family = AF_UNSPEC; + else if (!strcmp(family, "ipv4")) + hints.ai_family = AF_INET; + else if (!strcmp(family, "ipv6")) + hints.ai_family = AF_INET6; + else + { + dbus_set_error (error, + DBUS_ERROR_BAD_ADDRESS, + "Unknown address family %s", family); + return _dbus_socket_get_invalid (); + } + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; +#ifdef AI_ADDRCONFIG + hints.ai_flags = AI_ADDRCONFIG; +#else + hints.ai_flags = 0; +#endif + + if ((res = getaddrinfo(host, port, &hints, &ai)) != 0 || !ai) + { + dbus_set_error (error, + _dbus_error_from_errno (res), + "Failed to lookup host/port: \"%s:%s\": %s (%d)", + host, port, _dbus_strerror (res), res); + goto out; + } + + tmp = ai; + while (tmp) + { + if ((fd.sock = socket (tmp->ai_family, SOCK_STREAM, 0)) == INVALID_SOCKET) + { + saved_errno = _dbus_get_low_level_socket_errno (); + dbus_set_error (error, + _dbus_error_from_errno (saved_errno), + "Failed to open socket: %s", + _dbus_strerror (saved_errno)); + _dbus_assert (!_dbus_socket_is_valid (fd)); + goto out; + } + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + + if (connect (fd.sock, (struct sockaddr*) tmp->ai_addr, tmp->ai_addrlen) == SOCKET_ERROR) + { + saved_errno = _dbus_get_low_level_socket_errno (); + _dbus_close_socket (&fd, NULL); + + connect_error = dbus_new0 (DBusError, 1); + + if (connect_error == NULL) + { + _DBUS_SET_OOM (error); + goto out; + } + + dbus_error_init (connect_error); + _dbus_set_error_with_inet_sockaddr (connect_error, + tmp->ai_addr, tmp->ai_addrlen, + "Failed to connect to socket", + saved_errno); + + if (!_dbus_list_append (&connect_errors, connect_error)) + { + dbus_error_free (connect_error); + dbus_free (connect_error); + _DBUS_SET_OOM (error); + goto out; + } + + tmp = tmp->ai_next; + continue; + } + + break; + } + + if (!_dbus_socket_is_valid (fd)) + { + _dbus_combine_tcp_errors (&connect_errors, "Failed to connect", + host, port, error); + goto out; + } + + if (noncefile != NULL) + { + DBusString noncefileStr; + dbus_bool_t ret; + _dbus_string_init_const (&noncefileStr, noncefile); + ret = _dbus_send_nonce (fd, &noncefileStr, error); + + if (!ret) + { + _dbus_close_socket (&fd, NULL); + goto out; + } + } + + /* Every SOCKET is also a HANDLE. */ + _dbus_win_handle_set_close_on_exec ((HANDLE) fd.sock); + + if (!_dbus_set_socket_nonblocking (fd, error)) + { + _dbus_close_socket (&fd, NULL); + goto out; + } + +out: + if (ai != NULL) + freeaddrinfo (ai); + + while ((connect_error = _dbus_list_pop_first (&connect_errors))) + { + dbus_error_free (connect_error); + dbus_free (connect_error); + } + + return fd; +} + +/** + * Creates a socket and binds it to the given path, then listens on + * the socket. The socket is set to be nonblocking. In case of port=0 + * a random free port is used and returned in the port parameter. + * If inaddr_any is specified, the hostname is ignored. + * + * @param host the host name to listen on + * @param port the port to listen on, if zero a free port will be used + * @param family the address family to listen on, NULL for all + * @param retport string to return the actual port listened on + * @param retfamily string to return the actual family listened on + * @param fds_p location to store returned file descriptors + * @param error return location for errors + * @returns the number of listening file descriptors or -1 on error + */ +int +_dbus_listen_tcp_socket (const char *host, + const char *port, + const char *family, + DBusString *retport, + const char **retfamily, + DBusSocket **fds_p, + DBusError *error) +{ + int saved_errno; + int nlisten_fd = 0, res, i, port_num = -1; + DBusList *bind_errors = NULL; + DBusError *bind_error = NULL; + DBusSocket *listen_fd = NULL; + struct addrinfo hints; + struct addrinfo *ai, *tmp; + dbus_bool_t have_ipv4 = FALSE; + dbus_bool_t have_ipv6 = FALSE; + + // On Vista, sockaddr_gen must be a sockaddr_in6, and not a sockaddr_in6_old + //That's required for family == IPv6(which is the default on Vista if family is not given) + //So we use our own union instead of sockaddr_gen: + + typedef union { + struct sockaddr Address; + struct sockaddr_in AddressIn; + struct sockaddr_in6 AddressIn6; + } mysockaddr_gen; + + *fds_p = NULL; + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_win_startup_winsock ()) + { + _DBUS_SET_OOM (error); + return -1; + } + + _DBUS_ZERO (hints); + + if (!family) + hints.ai_family = AF_UNSPEC; + else if (!strcmp(family, "ipv4")) + hints.ai_family = AF_INET; + else if (!strcmp(family, "ipv6")) + hints.ai_family = AF_INET6; + else + { + dbus_set_error (error, + DBUS_ERROR_INVALID_ARGS, + "Unknown address family %s", family); + return -1; + } + + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; +#ifdef AI_ADDRCONFIG + hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE; +#else + hints.ai_flags = AI_PASSIVE; +#endif + + redo_lookup_with_port: + ai = NULL; + if ((res = getaddrinfo(host, port, &hints, &ai)) != 0 || !ai) + { + dbus_set_error (error, + _dbus_error_from_errno (res), + "Failed to lookup host/port: \"%s:%s\": %s (%d)", + host ? host : "*", port, _dbus_strerror(res), res); + return -1; + } + + tmp = ai; + while (tmp) + { + const int reuseaddr = 1, tcp_nodelay_on = 1; + DBusSocket fd = DBUS_SOCKET_INIT, *newlisten_fd; + + if ((fd.sock = socket (tmp->ai_family, SOCK_STREAM, 0)) == INVALID_SOCKET) + { + saved_errno = _dbus_get_low_level_socket_errno (); + dbus_set_error (error, + _dbus_error_from_errno (saved_errno), + "Failed to open socket: %s", + _dbus_strerror (saved_errno)); + _dbus_assert (!_dbus_socket_is_valid (fd)); + goto failed; + } + _DBUS_ASSERT_ERROR_IS_CLEAR(error); + + if (setsockopt (fd.sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuseaddr, sizeof(reuseaddr)) == SOCKET_ERROR) + { + saved_errno = _dbus_get_low_level_socket_errno (); + _dbus_warn ("Failed to set socket option \"%s:%s\": %s", + host ? host : "*", port, _dbus_strerror (saved_errno)); + } + + /* Nagle's algorithm imposes a huge delay on the initial messages + going over TCP. */ + if (setsockopt (fd.sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&tcp_nodelay_on, sizeof (tcp_nodelay_on)) == SOCKET_ERROR) + { + saved_errno = _dbus_get_low_level_socket_errno (); + _dbus_warn ("Failed to set TCP_NODELAY socket option \"%s:%s\": %s", + host ? host : "*", port, _dbus_strerror (saved_errno)); + } + + if (bind (fd.sock, (struct sockaddr *) tmp->ai_addr, tmp->ai_addrlen) == SOCKET_ERROR) + { + saved_errno = _dbus_get_low_level_socket_errno (); + closesocket (fd.sock); + + /* + * We don't treat this as a fatal error, because there might be + * other addresses that we can listen on. In particular: + * + * - If saved_errno is WSAEADDRINUSE after we + * "goto redo_lookup_with_port" after binding a port on one of the + * possible addresses, we will try to bind that same port on + * every address, including the same address again for a second + * time, which will fail with WSAEADDRINUSE . + * + * - If saved_errno is WSAEADDRINUSE, it might be because binding to + * an IPv6 address implicitly binds to a corresponding IPv4 + * address or vice versa. + * + * - If saved_errno is WSAEADDRNOTAVAIL when we asked for family + * AF_UNSPEC, it might be because IPv6 is disabled for this + * particular interface. + */ + bind_error = dbus_new0 (DBusError, 1); + + if (bind_error == NULL) + { + _DBUS_SET_OOM (error); + goto failed; + } + + dbus_error_init (bind_error); + _dbus_set_error_with_inet_sockaddr (bind_error, tmp->ai_addr, tmp->ai_addrlen, + "Failed to bind socket", + saved_errno); + + if (!_dbus_list_append (&bind_errors, bind_error)) + { + dbus_error_free (bind_error); + dbus_free (bind_error); + _DBUS_SET_OOM (error); + goto failed; + } + + /* Try the next address, maybe it will work better */ + tmp = tmp->ai_next; + continue; + } + + if (listen (fd.sock, 30 /* backlog */) == SOCKET_ERROR) + { + saved_errno = _dbus_get_low_level_socket_errno (); + closesocket (fd.sock); + _dbus_set_error_with_inet_sockaddr (error, tmp->ai_addr, tmp->ai_addrlen, + "Failed to listen on socket", + saved_errno); + goto failed; + } + + newlisten_fd = dbus_realloc(listen_fd, sizeof(DBusSocket)*(nlisten_fd+1)); + if (!newlisten_fd) + { + closesocket (fd.sock); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, + "Failed to allocate file handle array"); + goto failed; + } + listen_fd = newlisten_fd; + listen_fd[nlisten_fd] = fd; + nlisten_fd++; + + if (tmp->ai_addr->sa_family == AF_INET) + have_ipv4 = TRUE; + else if (tmp->ai_addr->sa_family == AF_INET6) + have_ipv6 = TRUE; + + if (!_dbus_string_get_length(retport)) + { + /* If the user didn't specify a port, or used 0, then + the kernel chooses a port. After the first address + is bound to, we need to force all remaining addresses + to use the same port */ + if (!port || !strcmp(port, "0")) + { + mysockaddr_gen addr; + socklen_t addrlen = sizeof(addr); + char portbuf[NI_MAXSERV]; + + if (getsockname (fd.sock, &addr.Address, &addrlen) == SOCKET_ERROR || + (res = getnameinfo (&addr.Address, addrlen, NULL, 0, + portbuf, sizeof(portbuf), + NI_NUMERICSERV)) != 0) + { + saved_errno = _dbus_get_low_level_socket_errno (); + dbus_set_error (error, _dbus_error_from_errno (saved_errno), + "Failed to resolve port \"%s:%s\": %s", + host ? host : "*", port, _dbus_strerror (saved_errno)); + goto failed; + } + if (!_dbus_string_append(retport, portbuf)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + /* Release current address list & redo lookup */ + port = _dbus_string_get_const_data(retport); + freeaddrinfo(ai); + goto redo_lookup_with_port; + } + else + { + if (!_dbus_string_append(retport, port)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + } + } + + tmp = tmp->ai_next; + } + freeaddrinfo(ai); + ai = NULL; + + if (!nlisten_fd) + { + _dbus_combine_tcp_errors (&bind_errors, "Failed to bind", host, port, error); + goto failed; + } + + if (have_ipv4 && !have_ipv6) + *retfamily = "ipv4"; + else if (!have_ipv4 && have_ipv6) + *retfamily = "ipv6"; + + sscanf(_dbus_string_get_const_data(retport), "%d", &port_num); + + for (i = 0 ; i < nlisten_fd ; i++) + { + _dbus_win_handle_set_close_on_exec ((HANDLE) listen_fd[i].sock); + if (!_dbus_set_socket_nonblocking (listen_fd[i], error)) + { + goto failed; + } + } + + *fds_p = listen_fd; + + /* This list might be non-empty even on success, because we might be + * ignoring WSAEADDRINUSE or WSAEADDRNOTAVAIL */ + while ((bind_error = _dbus_list_pop_first (&bind_errors))) + { + dbus_error_free (bind_error); + dbus_free (bind_error); + } + return nlisten_fd; + + failed: + if (ai) + freeaddrinfo(ai); + for (i = 0 ; i < nlisten_fd ; i++) + closesocket (listen_fd[i].sock); + + while ((bind_error = _dbus_list_pop_first (&bind_errors))) + { + dbus_error_free (bind_error); + dbus_free (bind_error); + } + + dbus_free(listen_fd); + return -1; +} + + +/** + * Accepts a connection on a listening socket. + * Handles EINTR for you. + * + * @param listen_fd the listen file descriptor + * @returns the connection fd of the client, or -1 on error + */ +DBusSocket +_dbus_accept (DBusSocket listen_fd) +{ + DBusSocket client_fd; + + retry: + client_fd.sock = accept (listen_fd.sock, NULL, NULL); + + if (!_dbus_socket_is_valid (client_fd)) + { + DBUS_SOCKET_SET_ERRNO (); + if (errno == EINTR) + goto retry; + } + + _dbus_verbose ("client fd %Iu accepted\n", client_fd.sock); + + return client_fd; +} + + + + +dbus_bool_t +_dbus_send_credentials_socket (DBusSocket handle, + DBusError *error) +{ +/* FIXME: for the session bus credentials shouldn't matter (?), but + * for the system bus they are presumably essential. A rough outline + * of a way to implement the credential transfer would be this: + * + * client waits to *read* a byte. + * + * server creates a named pipe with a random name, sends a byte + * contining its length, and its name. + * + * client reads the name, connects to it (using Win32 API). + * + * server waits for connection to the named pipe, then calls + * ImpersonateNamedPipeClient(), notes its now-current credentials, + * calls RevertToSelf(), closes its handles to the named pipe, and + * is done. (Maybe there is some other way to get the SID of a named + * pipe client without having to use impersonation?) + * + * client closes its handles and is done. + * + * Ralf: Why not sending credentials over the given this connection ? + * Using named pipes makes it impossible to be connected from a unix client. + * + */ + int bytes_written; + DBusString buf; + + _dbus_string_init_const_len (&buf, "\0", 1); +again: + bytes_written = _dbus_write_socket (handle, &buf, 0, 1 ); + + if (bytes_written < 0 && errno == EINTR) + goto again; + + if (bytes_written < 0) + { + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to write credentials byte: %s", + _dbus_strerror_from_errno ()); + return FALSE; + } + else if (bytes_written == 0) + { + dbus_set_error (error, DBUS_ERROR_IO_ERROR, + "wrote zero bytes writing credentials byte"); + return FALSE; + } + else + { + _dbus_assert (bytes_written == 1); + _dbus_verbose ("wrote 1 zero byte, credential sending isn't implemented yet\n"); + return TRUE; + } + return TRUE; +} + +#ifdef HAVE_AFUNIX_H +/* + * Returns false with no error set if the socket is non-AF_UNIX + * (contrary to our usual convention). + * + * Returns false with an error set on failure to identify it. + */ +static dbus_bool_t +_dbus_socket_is_af_unix (DBusSocket s, + DBusError *error) +{ + struct sockaddr_un saddr; + int len; + + len = sizeof (saddr); + if (getsockname (s.sock, (struct sockaddr *)&saddr, &len) == SOCKET_ERROR) + { + DBUS_SOCKET_SET_ERRNO (); + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to getsockname: %s", + _dbus_strerror_from_errno ()); + return FALSE; + } + + return saddr.sun_family == AF_UNIX; +} + +/** + * @brief return peer process id from Unix domain socket handle + * @param handle AF_UNIX socket descriptor + * @return process id or 0 in case the process id could not be fetched + */ +static dbus_pid_t +_dbus_get_peer_pid_from_uds_handle (int handle) +{ + DWORD pid, drc; + + if (WSAIoctl (handle, SIO_AF_UNIX_GETPEERPID, + NULL, 0U, + &pid, sizeof (pid), &drc, + NULL, NULL) == SOCKET_ERROR) + { + _dbus_verbose ("failed to get peer's pid\n"); + return 0; + } + + return pid; +} +#endif + +/** + * Reads a single byte which must be nul (an error occurs otherwise), + * and reads unix credentials if available. Fills in pid/uid/gid with + * -1 if no credentials are available. Return value indicates whether + * a byte was read, not whether we got valid credentials. On some + * systems, such as Linux, reading/writing the byte isn't actually + * required, but we do it anyway just to avoid multiple codepaths. + * + * Fails if no byte is available, so you must select() first. + * + * The point of the byte is that on some systems we have to + * use sendmsg()/recvmsg() to transmit credentials. + * + * @param handle the client file descriptor + * @param credentials struct to fill with credentials of client + * @param error location to store error code + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_read_credentials_socket (DBusSocket handle, + DBusCredentials *credentials, + DBusError *error) +{ + int bytes_read = 0; + DBusString buf; +#ifdef HAVE_AFUNIX_H + dbus_bool_t uds = FALSE; +#endif + + char *sid = NULL; + dbus_pid_t pid; + int retval = FALSE; + + // could fail due too OOM + if (_dbus_string_init (&buf)) + { + bytes_read = _dbus_read_socket (handle, &buf, 1 ); + + if (bytes_read > 0) + _dbus_verbose ("got one zero byte from server\n"); + + _dbus_string_free (&buf); + } + +#ifdef HAVE_AFUNIX_H + uds = _dbus_socket_is_af_unix (handle, error); + if (dbus_error_is_set (error)) + return FALSE; + + if (uds) + pid = _dbus_get_peer_pid_from_uds_handle (handle.sock); + else +#endif + pid = _dbus_get_peer_pid_from_tcp_handle (handle.sock); + if (pid == 0) + return TRUE; + + _dbus_credentials_add_pid (credentials, pid); + + if (_dbus_getsid (&sid, pid)) + { + if (!_dbus_credentials_add_windows_sid (credentials, sid)) + goto out; + } + + retval = TRUE; + +out: + if (sid) + LocalFree (sid); + + return retval; +} + +/** +* Checks to make sure the given directory is +* private to the user +* +* @param dir the name of the directory +* @param error error return +* @returns #FALSE on failure +**/ +dbus_bool_t +_dbus_check_dir_is_private_to_user (DBusString *dir, DBusError *error) +{ + /* TODO */ + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return TRUE; +} + + +/** + * Appends the given filename to the given directory. + * + * @todo it might be cute to collapse multiple '/' such as "foo//" + * concat "//bar" + * + * @param dir the directory name + * @param next_component the filename + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_concat_dir_and_file (DBusString *dir, + const DBusString *next_component) +{ + dbus_bool_t dir_ends_in_slash; + dbus_bool_t file_starts_with_slash; + + if (_dbus_string_get_length (dir) == 0 || + _dbus_string_get_length (next_component) == 0) + return TRUE; + + dir_ends_in_slash = + ('/' == _dbus_string_get_byte (dir, _dbus_string_get_length (dir) - 1) || + '\\' == _dbus_string_get_byte (dir, _dbus_string_get_length (dir) - 1)); + + file_starts_with_slash = + ('/' == _dbus_string_get_byte (next_component, 0) || + '\\' == _dbus_string_get_byte (next_component, 0)); + + if (dir_ends_in_slash && file_starts_with_slash) + { + _dbus_string_shorten (dir, 1); + } + else if (!(dir_ends_in_slash || file_starts_with_slash)) + { + if (!_dbus_string_append_byte (dir, '\\')) + return FALSE; + } + + return _dbus_string_copy (next_component, 0, dir, + _dbus_string_get_length (dir)); +} + +/*---------------- DBusCredentials ----------------------------------*/ + +/** + * Adds the credentials corresponding to the given username. + * + * @param credentials credentials to fill in + * @param username the username + * @returns #TRUE if the username existed and we got some credentials + */ +dbus_bool_t +_dbus_credentials_add_from_user (DBusCredentials *credentials, + const DBusString *username, + DBusCredentialsAddFlags flags, + DBusError *error) +{ + if (!_dbus_credentials_add_windows_sid (credentials, + _dbus_string_get_const_data (username))) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + return TRUE; +} + +/** + * Adds the credentials of the current process to the + * passed-in credentials object. + * + * @param credentials credentials to add to + * @returns #FALSE if no memory; does not properly roll back on failure, so only some credentials may have been added + */ + +dbus_bool_t +_dbus_credentials_add_from_current_process (DBusCredentials *credentials) +{ + dbus_bool_t retval = FALSE; + char *sid = NULL; + + if (!_dbus_getsid(&sid, _dbus_getpid())) + goto failed; + + if (!_dbus_credentials_add_pid (credentials, _dbus_getpid())) + goto failed; + + if (!_dbus_credentials_add_windows_sid (credentials,sid)) + goto failed; + + retval = TRUE; + goto end; +failed: + retval = FALSE; +end: + if (sid) + LocalFree(sid); + + return retval; +} + +/** + * Append to the string the identity we would like to have when we + * authenticate, on UNIX this is the current process UID and on + * Windows something else, probably a Windows SID string. No escaping + * is required, that is done in dbus-auth.c. The username here + * need not be anything human-readable, it can be the machine-readable + * form i.e. a user id. + * + * @param str the string to append to + * @returns #FALSE on no memory + * @todo to which class belongs this + */ +dbus_bool_t +_dbus_append_user_from_current_process (DBusString *str) +{ + dbus_bool_t retval = FALSE; + char *sid = NULL; + + if (!_dbus_getsid(&sid, _dbus_getpid())) + return FALSE; + + retval = _dbus_string_append (str,sid); + + LocalFree(sid); + return retval; +} + +/** + * Gets our process ID + * @returns process ID + */ +dbus_pid_t +_dbus_getpid (void) +{ + return GetCurrentProcessId (); +} + +/** Gets our Unix UID + * @returns on Windows, just DBUS_UID_UNSET + */ +dbus_uid_t +_dbus_getuid (void) +{ + return DBUS_UID_UNSET; +} + +/** nanoseconds in a second */ +#define NANOSECONDS_PER_SECOND 1000000000 +/** microseconds in a second */ +#define MICROSECONDS_PER_SECOND 1000000 +/** milliseconds in a second */ +#define MILLISECONDS_PER_SECOND 1000 +/** nanoseconds in a millisecond */ +#define NANOSECONDS_PER_MILLISECOND 1000000 +/** microseconds in a millisecond */ +#define MICROSECONDS_PER_MILLISECOND 1000 + +/** + * Sleeps the given number of milliseconds. + * @param milliseconds number of milliseconds + */ +void +_dbus_sleep_milliseconds (int milliseconds) +{ + Sleep (milliseconds); +} + + +/** + * Get current time, as in gettimeofday(). Never uses the monotonic + * clock. + * + * @param tv_sec return location for number of seconds + * @param tv_usec return location for number of microseconds + */ +void +_dbus_get_real_time (dbus_int64_t *tv_sec, + long *tv_usec) +{ + FILETIME ft; + dbus_uint64_t time64; + + GetSystemTimeAsFileTime (&ft); + + memcpy (&time64, &ft, sizeof (time64)); + + /* Convert from 100s of nanoseconds since 1601-01-01 + * to Unix epoch. Yes, this is Y2038 unsafe. + */ + time64 -= DBUS_INT64_CONSTANT (116444736000000000); + time64 /= 10; + + if (tv_sec) + *tv_sec = time64 / 1000000; + + if (tv_usec) + *tv_usec = time64 % 1000000; +} + +/** + * Get current time, as in gettimeofday(). Use the monotonic clock if + * available, to avoid problems when the system time changes. + * + * @param tv_sec return location for number of seconds + * @param tv_usec return location for number of microseconds + */ +void +_dbus_get_monotonic_time (dbus_int64_t *tv_sec, + long *tv_usec) +{ + /* no implementation yet, fall back to wall-clock time */ + _dbus_get_real_time (tv_sec, tv_usec); +} + +/** + * signal (SIGPIPE, SIG_IGN); + */ +void +_dbus_disable_sigpipe (void) +{ +} + +/** + * Creates a directory. Unlike _dbus_ensure_directory(), this only succeeds + * if the directory is genuinely newly-created. + * + * @param filename directory filename + * @param error initialized error object + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_create_directory (const DBusString *filename, + DBusError *error) +{ + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + if (!CreateDirectoryA (filename_c, NULL)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to create directory %s: %s\n", + filename_c, _dbus_strerror_from_errno ()); + return FALSE; + } + else + return TRUE; +} + +/** + * Creates a directory; succeeds if the directory + * is created or already existed. + * + * @param filename directory filename + * @param error initialized error object + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_ensure_directory (const DBusString *filename, + DBusError *error) +{ + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + if (!CreateDirectoryA (filename_c, NULL)) + { + if (GetLastError () == ERROR_ALREADY_EXISTS) + return TRUE; + + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to create directory %s: %s\n", + filename_c, _dbus_strerror_from_errno ()); + return FALSE; + } + else + return TRUE; +} + + +/** + * Generates the given number of random bytes, + * using the best mechanism we can come up with. + * + * @param str the string + * @param n_bytes the number of random bytes to append to string + * @param error location to store reason for failure + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_generate_random_bytes (DBusString *str, + int n_bytes, + DBusError *error) +{ + int old_len; + unsigned char *p; + HCRYPTPROV hprov; + + old_len = _dbus_string_get_length (str); + + if (!_dbus_string_lengthen (str, n_bytes)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + p = _dbus_string_get_udata_len (str, old_len, n_bytes); + + if (!CryptAcquireContext (&hprov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!CryptGenRandom (hprov, n_bytes, p)) + { + _DBUS_SET_OOM (error); + CryptReleaseContext (hprov, 0); + return FALSE; + } + + CryptReleaseContext (hprov, 0); + + return TRUE; +} + +/** + * Gets the temporary files directory, using GetTempPath() + * + * @returns location of temp directory, or #NULL if no memory for locking + */ +const char* +_dbus_get_tmpdir(void) +{ + /* Protected by _DBUS_LOCK_sysdeps */ + static const char* tmpdir = NULL; + static char buf[1000]; + + if (!_DBUS_LOCK (sysdeps)) + return NULL; + + if (tmpdir == NULL) + { + unsigned char *last_slash; + unsigned char *p = (unsigned char *)buf; + + if (!GetTempPathA (sizeof (buf), buf)) + { + _dbus_warn ("GetTempPath failed"); + _dbus_abort (); + } + + /* Drop terminating backslash or slash */ + last_slash = _mbsrchr (p, '\\'); + if (last_slash > p && last_slash[1] == '\0') + last_slash[0] = '\0'; + last_slash = _mbsrchr (p, '/'); + if (last_slash > p && last_slash[1] == '\0') + last_slash[0] = '\0'; + + tmpdir = buf; + } + + _DBUS_UNLOCK (sysdeps); + + _dbus_assert(tmpdir != NULL); + + return tmpdir; +} + + +/** + * Deletes the given file. + * + * @param filename the filename + * @param error error location + * + * @returns #TRUE if unlink() succeeded + */ +dbus_bool_t +_dbus_delete_file (const DBusString *filename, + DBusError *error) +{ + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + if (DeleteFileA (filename_c) == 0) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to delete file %s: %s\n", + filename_c, _dbus_strerror_from_errno ()); + return FALSE; + } + else + return TRUE; +} + +static dbus_uint32_t fromAscii(char ascii) +{ + if(ascii >= '0' && ascii <= '9') + return ascii - '0'; + if(ascii >= 'A' && ascii <= 'F') + return ascii - 'A' + 10; + if(ascii >= 'a' && ascii <= 'f') + return ascii - 'a' + 10; + return 0; +} + +dbus_bool_t _dbus_read_local_machine_uuid (DBusGUID *machine_id, + dbus_bool_t create_if_not_found, + DBusError *error) +{ +#ifdef DBUS_WINCE + return TRUE; + // TODO +#else + HW_PROFILE_INFOA info; + char *lpc = &info.szHwProfileGuid[0]; + dbus_uint32_t u; + + // the hw-profile guid lives long enough + if(!GetCurrentHwProfileA(&info)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); // FIXME + return FALSE; + } + + // Form: {12340001-4980-1920-6788-123456789012} + lpc++; + // 12340001 + u = ((fromAscii(lpc[0]) << 0) | + (fromAscii(lpc[1]) << 4) | + (fromAscii(lpc[2]) << 8) | + (fromAscii(lpc[3]) << 12) | + (fromAscii(lpc[4]) << 16) | + (fromAscii(lpc[5]) << 20) | + (fromAscii(lpc[6]) << 24) | + (fromAscii(lpc[7]) << 28)); + machine_id->as_uint32s[0] = u; + + lpc += 9; + // 4980-1920 + u = ((fromAscii(lpc[0]) << 0) | + (fromAscii(lpc[1]) << 4) | + (fromAscii(lpc[2]) << 8) | + (fromAscii(lpc[3]) << 12) | + (fromAscii(lpc[5]) << 16) | + (fromAscii(lpc[6]) << 20) | + (fromAscii(lpc[7]) << 24) | + (fromAscii(lpc[8]) << 28)); + machine_id->as_uint32s[1] = u; + + lpc += 10; + // 6788-1234 + u = ((fromAscii(lpc[0]) << 0) | + (fromAscii(lpc[1]) << 4) | + (fromAscii(lpc[2]) << 8) | + (fromAscii(lpc[3]) << 12) | + (fromAscii(lpc[5]) << 16) | + (fromAscii(lpc[6]) << 20) | + (fromAscii(lpc[7]) << 24) | + (fromAscii(lpc[8]) << 28)); + machine_id->as_uint32s[2] = u; + + lpc += 9; + // 56789012 + u = ((fromAscii(lpc[0]) << 0) | + (fromAscii(lpc[1]) << 4) | + (fromAscii(lpc[2]) << 8) | + (fromAscii(lpc[3]) << 12) | + (fromAscii(lpc[4]) << 16) | + (fromAscii(lpc[5]) << 20) | + (fromAscii(lpc[6]) << 24) | + (fromAscii(lpc[7]) << 28)); + machine_id->as_uint32s[3] = u; +#endif + return TRUE; +} + +// for proper cleanup in dbus-daemon +static HANDLE hDBusDaemonMutex = NULL; +static HANDLE hDBusSharedMem = NULL; +// sync _dbus_daemon_publish_session_bus_address, _dbus_daemon_unpublish_session_bus_address and _dbus_daemon_already_runs +static const char *cUniqueDBusInitMutex = "UniqueDBusInitMutex"; +// sync _dbus_get_autolaunch_address +static const char *cDBusAutolaunchMutex = "DBusAutolaunchMutex"; +// mutex to determine if dbus-daemon is already started (per user) +static const char *cDBusDaemonMutex = "DBusDaemonMutex"; +// named shm for dbus adress info (per user) +static const char *cDBusDaemonAddressInfo = "DBusDaemonAddressInfo"; + +/* custom command line parameter for autolaunching daemon */ +static const char *autolaunch_custom_command_line_parameter = ""; + +/** + * Set command line parameters for the dbus daemon to start + * for an autolaunch session. + * + * The specified instance must be valid until the dbus-daemon + * is started. + * + * This function is not thread-safe, and can only be called from a + * single-threaded unit test. + * + * @param path string to use as command line parameter + */ +void _dbus_test_win_autolaunch_set_command_line_parameter (const char *path) +{ + autolaunch_custom_command_line_parameter = path; +} + +static HANDLE *autolaunch_handle_location; + +/** + * Set location where to store process handle of an autostarted server + * + * This function is not thread-safe, and can only be called from a + * single-threaded unit test. + * + * After using the handle it must be closed with @ref CloseHandle(). + * + * @param location Pointer where to store the handle + */ +void +_dbus_test_win_set_autolaunch_handle_location (HANDLE *location) +{ + autolaunch_handle_location = location; +} + +/** + * Return the hash of the installation root directory, which can be + * used to construct a per-installation-root scope for autolaunching + * + * If the installation root directory could not be + * determined, the returned length is set to zero. + * + * @param out initialized DBusString instance to return hash string + * @returns #FALSE on OOM, #TRUE if not OOM + */ +static dbus_bool_t +_dbus_get_install_root_as_hash (DBusString *out) +{ + DBusString install_path; + dbus_bool_t retval = FALSE; + _dbus_assert (out != NULL); + + if (!_dbus_string_init (&install_path)) + return FALSE; + + if (!_dbus_get_install_root (&install_path)) + goto out; + + /* the install path can't be determined */ + if (_dbus_string_get_length (&install_path) == 0) + { + _dbus_string_set_length (out, 0); + retval = TRUE; + goto out; + } + + _dbus_string_tolower_ascii (&install_path, 0, _dbus_string_get_length (&install_path)); + + if (!_dbus_sha_compute (&install_path, out)) + goto out; + + retval = TRUE; + +out: + _dbus_string_free (&install_path); + return retval; +} + +/** + * Build a name from \p basestring and \p scope, and append it to \p out + * + * The name will be suitable for naming Windows objects such as mutexes + * and shared memory segments that need to be unique for each distinct + * \p scope, but shared between clients with the same \p scope. + * + * If \p scope has one of the special values recognised in autolaunch: + * addresses on Windows, substitute a unique string based on the scope + * (the username or the hash of the installation path) instead of the + * literal scope itself. + * + * With the '*install-path' \p scope the returned length can be zero, + * indicating that the name could not be determined. + * + * @param out initialized DBusString instance to return bus address + * @returns #FALSE on OOM, #TRUE if not OOM + */ +static dbus_bool_t +_dbus_get_address_string (DBusString *out, const char *basestring, const char *scope) +{ + _dbus_assert (out != NULL); + + if (!scope || strlen (scope) == 0) + { + return _dbus_string_append (out, basestring); + } + else if (strcmp (scope, "*install-path") == 0 + // for 1.3 compatibility + || strcmp (scope, "install-path") == 0) + { + DBusString temp; + dbus_bool_t retval = FALSE; + + if (!_dbus_string_init (&temp)) + return FALSE; + + if (!_dbus_get_install_root_as_hash (&temp)) + goto out; + + if (_dbus_string_get_length (&temp) == 0) + { + _dbus_string_set_length (out, 0); + retval = TRUE; + goto out; + } + + if (!_dbus_string_append_printf (out, "%s-%s", basestring, _dbus_string_get_const_data (&temp))) + goto out; + + retval = TRUE; +out: + _dbus_string_free (&temp); + return retval; + } + else if (strcmp (scope, "*user") == 0) + { + char *sid = NULL; + dbus_bool_t retval; + + if (!_dbus_getsid (&sid, _dbus_getpid())) + return FALSE; + + retval = _dbus_string_append_printf (out, "%s-%s", basestring, sid); + + LocalFree(sid); + + return retval; + } + else /* strlen(scope) > 0 */ + { + return _dbus_string_append_printf (out, "%s-%s", basestring, scope); + } +} + +/** + * Return name of shared memory segment constructed from the autolaunch scope \p scope + * + * See @ref _dbus_get_address_string for further usage information. + * + * @param out initialized DBusString instance to return shared memory segment name + * @returns #FALSE on OOM, #TRUE if not OOM + */ +static dbus_bool_t +_dbus_get_shm_name (DBusString *out,const char *scope) +{ + return _dbus_get_address_string (out, cDBusDaemonAddressInfo, scope); +} + +/** + * Return mutex name for scope \p scope in \p out + * + * See @ref _dbus_get_address_string for further usage information. + * + * @param out initialized DBusString instance to return mutex name + * @param scope scope for the requested mutex name + * @returns #FALSE on OOM, #TRUE if not OOM + */ +static dbus_bool_t +_dbus_get_mutex_name (DBusString *out, const char *scope) +{ + return _dbus_get_address_string (out, cDBusDaemonMutex, scope); +} + +dbus_bool_t +_dbus_daemon_is_session_bus_address_published (const char *scope) +{ + DBusRMutex *lock = NULL; + DBusString mutex_name; + + if (!_dbus_string_init (&mutex_name)) + return FALSE; + + _dbus_verbose ("scope:%s\n", scope); + if (!_dbus_get_mutex_name (&mutex_name, scope) || + /* not determinable */ + _dbus_string_get_length (&mutex_name) == 0) + { + _dbus_string_free (&mutex_name); + return FALSE; + } + + if (hDBusDaemonMutex) + { + _dbus_verbose ("(scope:%s) -> yes\n", scope); + return TRUE; + } + lock = _dbus_win_rmutex_named_new (cUniqueDBusInitMutex); + if (!lock) + return FALSE; + + // sync _dbus_daemon_publish_session_bus_address, _dbus_daemon_unpublish_session_bus_address and _dbus_daemon_already_runs + _dbus_platform_rmutex_lock (lock); + + // we use CreateMutex instead of OpenMutex because of possible race conditions, + // see http://msdn.microsoft.com/en-us/library/ms684315%28VS.85%29.aspx + hDBusDaemonMutex = CreateMutexA (NULL, FALSE, _dbus_string_get_const_data(&mutex_name)); + + /* The client uses mutex ownership to detect a running server, so the server should do so too. + Fortunally the client deletes the mutex in the lock protected area, so checking presence + will work too. */ + + _dbus_platform_rmutex_unlock (lock); + _dbus_platform_rmutex_free (lock); + + _dbus_string_free (&mutex_name); + + if (hDBusDaemonMutex == NULL) + { + _dbus_verbose ("(scope:%s) -> no\n", scope); + return FALSE; + } + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + CloseHandle(hDBusDaemonMutex); + hDBusDaemonMutex = NULL; + _dbus_verbose ("(scope:%s) -> yes\n", scope); + return TRUE; + } + // mutex wasn't created before, so return false. + // We leave the mutex name allocated for later reusage + // in _dbus_daemon_publish_session_bus_address. + _dbus_verbose ("(scope:%s) -> no\n", scope); + return FALSE; +} + +dbus_bool_t +_dbus_daemon_publish_session_bus_address (const char* address, const char *scope) +{ + DBusRMutex *lock = NULL; + char *shared_addr = NULL; + DBusString shm_name = _DBUS_STRING_INIT_INVALID; + DBusString mutex_name; + dbus_uint64_t len; + dbus_bool_t retval = FALSE; + + _dbus_assert (address); + + if (!_dbus_string_init (&mutex_name)) + return FALSE; + + _dbus_verbose ("address:%s scope:%s\n", address, scope); + if (!_dbus_get_mutex_name (&mutex_name, scope) || + /* not determinable */ + _dbus_string_get_length (&mutex_name) == 0) + { + _dbus_string_free (&mutex_name); + return FALSE; + } + + // sync _dbus_daemon_publish_session_bus_address, _dbus_daemon_unpublish_session_bus_address and _dbus_daemon_already_runs + lock = _dbus_win_rmutex_named_new (cUniqueDBusInitMutex); + if (lock == NULL) + { + _dbus_string_free (&mutex_name); + return FALSE; + } + + _dbus_platform_rmutex_lock (lock); + + if (!hDBusDaemonMutex) + { + hDBusDaemonMutex = CreateMutexA (NULL, FALSE, _dbus_string_get_const_data(&mutex_name)); + } + _dbus_string_free (&mutex_name); + + // acquire the mutex + if (WaitForSingleObject (hDBusDaemonMutex, 10) != WAIT_OBJECT_0) + { + CloseHandle (hDBusDaemonMutex); + goto out; + } + + if (!_dbus_string_init (&shm_name)) + { + goto out; + } + + if (!_dbus_get_shm_name (&shm_name, scope) || + /* not determinable */ + _dbus_string_get_length (&shm_name) == 0) + { + goto out; + } + + // create shm + len = strlen (address) + 1; + + hDBusSharedMem = CreateFileMappingA ( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, + len >> 32, len & 0xffffffffu, + _dbus_string_get_const_data (&shm_name) ); + _dbus_assert (hDBusSharedMem); + + shared_addr = MapViewOfFile (hDBusSharedMem, FILE_MAP_WRITE, 0, 0, 0); + + _dbus_assert (shared_addr); + + strcpy(shared_addr, address); + + // cleanup + UnmapViewOfFile (shared_addr); + + _dbus_verbose ("published session bus address at %s\n",_dbus_string_get_const_data (&shm_name)); + retval = TRUE; + +out: + _dbus_platform_rmutex_unlock (lock); + _dbus_platform_rmutex_free (lock); + _dbus_string_free (&shm_name); + return retval; +} + +/** + * Clear the platform-specific centralized location where the session + * bus address is published. + * + * This must only be called if \ref DBusServer.published_address is #TRUE, + * which is be the case if and only if platform-specific code has published + * the address centrally. + * + * On Windows, this is implemented by closing a global shared memory segment. + * + * On Unix, the session bus address is not published in a centralized + * location by libdbus, so this function does nothing. The closest + * equivalent on Unix is that the session bus address is published by the + * dbus-launch tool, and unpublished automatically when the dbus-launch + * tool exits. + * @return NULL in case of error + */ +dbus_bool_t +_dbus_daemon_unpublish_session_bus_address (void) +{ + DBusRMutex *lock = NULL; + + _dbus_verbose ("\n"); + // sync _dbus_daemon_publish_session_bus_address, _dbus_daemon_unpublish_session_bus_address and _dbus_daemon_already_runs + lock = _dbus_win_rmutex_named_new (cUniqueDBusInitMutex); + if (lock == NULL) + return FALSE; + + _dbus_platform_rmutex_lock (lock); + + CloseHandle (hDBusSharedMem); + + hDBusSharedMem = NULL; + + ReleaseMutex (hDBusDaemonMutex); + + CloseHandle (hDBusDaemonMutex); + + hDBusDaemonMutex = NULL; + + _dbus_platform_rmutex_unlock (lock); + _dbus_platform_rmutex_free (lock); + return TRUE; +} + +/** + * Get server bus address from shared memory segment provided by running dbus-daemon + * + * @param address initialized DBusString instance to store the retrieved address + * @param shm_name the name of the shared memory segment + * @param wait if TRUE wait maximum 2 seconds for the presence of the shared memory segment + * @return #TRUE the bus address was fetched from the shared memory segment + * @return #FALSE error during execution + */ +static dbus_bool_t +_dbus_get_autolaunch_shm (DBusString *address, DBusString *shm_name, dbus_bool_t wait) +{ + HANDLE sharedMem = NULL; + char *shared_addr; + int i; + int max = 20; /* max 2 seconds */ + dbus_bool_t retval = FALSE; + + if (!wait) + max = 1; + + // read shm + for (i = 0; i < max; ++i) + { + // we know that dbus-daemon is available, so we wait until shm is available + sharedMem = OpenFileMappingA (FILE_MAP_READ, FALSE, _dbus_string_get_const_data (shm_name)); + if (sharedMem == 0) + Sleep (100); + if (sharedMem != 0) + break; + } + + if (sharedMem == 0) + return FALSE; + + shared_addr = MapViewOfFile (sharedMem, FILE_MAP_READ, 0, 0, 0); + + if (!shared_addr) + goto out; + + retval = _dbus_string_append (address, shared_addr); + + UnmapViewOfFile (shared_addr); + +out: + CloseHandle (sharedMem); + return retval; +} + +static dbus_bool_t +_dbus_daemon_already_runs (DBusString *address, DBusString *shm_name, const char *scope) +{ + DBusRMutex *lock = NULL; + HANDLE daemon; + DBusString mutex_name; + dbus_bool_t retval = FALSE; + + if (!_dbus_string_init (&mutex_name)) + return FALSE; + + if (!_dbus_get_mutex_name (&mutex_name, scope) || + /* not determinable */ + _dbus_string_get_length (&mutex_name) == 0) + { + _dbus_string_free (&mutex_name); + return FALSE; + } + + // sync _dbus_daemon_publish_session_bus_address, _dbus_daemon_unpublish_session_bus_address and _dbus_daemon_already_runs + lock = _dbus_win_rmutex_named_new (cUniqueDBusInitMutex); + if (lock == NULL) + return FALSE; + + _dbus_platform_rmutex_lock (lock); + + // do checks + daemon = CreateMutexA (NULL, FALSE, _dbus_string_get_const_data (&mutex_name)); + if (WaitForSingleObject (daemon, 10) != WAIT_TIMEOUT) + { + ReleaseMutex (daemon); + CloseHandle (daemon); + goto out; + } + + // read shm, wait max 2 seconds + retval = _dbus_get_autolaunch_shm (address, shm_name, TRUE); + + // cleanup + CloseHandle (daemon); + +out: + _dbus_platform_rmutex_unlock (lock); + _dbus_platform_rmutex_free (lock); + _dbus_string_free (&mutex_name); + + return retval; +} + +dbus_bool_t +_dbus_get_autolaunch_address (const char *scope, + DBusString *address, + DBusError *error) +{ + DBusRMutex *lock = NULL; + STARTUPINFOA si; + PROCESS_INFORMATION pi; + dbus_bool_t retval = FALSE; + LPSTR lpFile; + char dbus_exe_path[MAX_PATH]; + DBusString dbus_args = _DBUS_STRING_INIT_INVALID; + const char *daemon_name = DBUS_DAEMON_NAME ".exe"; + DBusString shm_name; + HANDLE ready_event_handle = NULL; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&shm_name)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_get_shm_name (&shm_name, scope) || + /* not determinable */ + _dbus_string_get_length (&shm_name) == 0) + { + dbus_set_error_const (error, DBUS_ERROR_FAILED, "could not determine shm name"); + goto out; + } + + lock = _dbus_win_rmutex_named_new (cDBusAutolaunchMutex); + if (lock == NULL) + { + dbus_set_error (error, DBUS_ERROR_FAILED, "Could not lock '%s'", cDBusAutolaunchMutex); + _dbus_string_free (&shm_name); + return FALSE; + } + + _dbus_platform_rmutex_lock (lock); + + if (_dbus_daemon_already_runs (address, &shm_name, scope)) + { + _dbus_verbose ("found running dbus daemon for scope '%s' at %s\n", + scope ? scope : "", _dbus_string_get_const_data (&shm_name)); + retval = TRUE; + goto out; + } + + if (!SearchPathA (NULL, daemon_name, NULL, sizeof (dbus_exe_path), dbus_exe_path, &lpFile)) + { + // Look in directory containing dbus shared library + HMODULE hmod; + char dbus_module_path[MAX_PATH]; + DWORD rc; + + _dbus_verbose ("did not found dbus daemon executable on default search path, " + "trying path where dbus shared library is located"); + + hmod = _dbus_win_get_dll_hmodule (); + rc = GetModuleFileNameA (hmod, dbus_module_path, sizeof (dbus_module_path)); + if (rc <= 0) + { + dbus_set_error_const (error, DBUS_ERROR_FAILED, "could not retrieve dbus shared library file name"); + retval = FALSE; + goto out; + } + else + { + char *ext_idx = strrchr (dbus_module_path, '\\'); + if (ext_idx) + *ext_idx = '\0'; + if (!SearchPathA (dbus_module_path, daemon_name, NULL, sizeof (dbus_exe_path), dbus_exe_path, &lpFile)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Could not find dbus-daemon executable. " + "Please add the path to %s to your PATH " + "environment variable or start the daemon manually", + daemon_name); + retval = FALSE; + goto out; + } + _dbus_verbose ("found dbus daemon executable at %s", dbus_module_path); + } + } + + // Create process + ZeroMemory (&si, sizeof (si)); + si.cb = sizeof (si); + ZeroMemory (&pi, sizeof (pi)); + + if (!_dbus_string_init (&dbus_args)) + { + dbus_set_error_const (error, DBUS_ERROR_NO_MEMORY, "Failed to initialize argument buffer"); + retval = FALSE; + goto out; + } + + if (!_dbus_string_append_printf (&dbus_args, "\"%s\" %s", dbus_exe_path, + autolaunch_custom_command_line_parameter ? autolaunch_custom_command_line_parameter : "--session")) + { + _DBUS_SET_OOM (error); + retval = FALSE; + goto out; + } + + ready_event_handle = _dbus_win_event_create_inheritable (error); + if (ready_event_handle == NULL) + goto out; + + _dbus_verbose ("Creating connection readiness event: handle=%p\n", ready_event_handle); + if (!_dbus_string_append_printf (&dbus_args, " \"--ready-event-handle=%p\"", ready_event_handle)) + { + _DBUS_SET_OOM (error); + goto out; + } + + _dbus_verbose ("Starting dbus daemon with args: '%s'\n", _dbus_string_get_const_data (&dbus_args)); + if (CreateProcessA (dbus_exe_path, _dbus_string_get_data (&dbus_args), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) + { + DWORD status; + HANDLE events[2]; + + CloseHandle (pi.hThread); + + _dbus_verbose ("Wait until dbus-daemon is ready for connections (event handle %p)\n", ready_event_handle); + + events[0] = ready_event_handle; + events[1] = pi.hProcess; + status = WaitForMultipleObjects (2, events, FALSE, 30000); + + switch (status) + { + case WAIT_OBJECT_0: + /* ready event signalled, everything is okay */ + retval = TRUE; + break; + + case WAIT_OBJECT_0 + 1: + /* dbus-daemon process has exited */ + dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_EXITED, "dbus-daemon exited before signalling ready"); + goto out; + + case WAIT_FAILED: + _dbus_win_set_error_from_last_error (error, "Unable to wait for server readiness (handle %p)", ready_event_handle); + goto out; + + case WAIT_TIMEOUT: + /* GetLastError() is not set */ + dbus_set_error (error, DBUS_ERROR_TIMEOUT, "Timed out waiting for server readiness or exit (handle %p)", ready_event_handle); + goto out; + + default: + /* GetLastError() is probably not set? */ + dbus_set_error (error, DBUS_ERROR_FAILED, "Unknown result '%lu' while waiting for server readiness (handle %p)", status, ready_event_handle); + goto out; + } + _dbus_verbose ("Got signal that dbus-daemon with process id '%ld' is ready for connections\n", GetProcessId (pi.hProcess)); + + if (autolaunch_handle_location != NULL) + { + *autolaunch_handle_location = pi.hProcess; + _dbus_verbose ("Returning process handle of started server (handle=%p)\n", pi.hProcess); + } + else + { + CloseHandle (pi.hProcess); + } + + /* do not wait for the appearance of shm, we can assume that it is present */ + retval = _dbus_get_autolaunch_shm (address, &shm_name, FALSE); + if (retval == FALSE) + dbus_set_error_const (error, DBUS_ERROR_FAILED, "Failed to get autolaunch address from launched dbus-daemon"); + } + else + { + dbus_set_error_const (error, DBUS_ERROR_FAILED, "Failed to launch dbus-daemon"); + retval = FALSE; + } + +out: + _DBUS_ASSERT_ERROR_XOR_BOOL (error, retval); + _dbus_platform_rmutex_unlock (lock); + _dbus_platform_rmutex_free (lock); + _dbus_string_free (&shm_name); + _dbus_string_free (&dbus_args); + if (ready_event_handle) + _dbus_win_event_free (ready_event_handle, NULL); + + _DBUS_ASSERT_ERROR_XOR_BOOL (error, retval); + return retval; +} + +/** Makes the file readable by every user in the system. + * + * @param filename the filename + * @param error error location + * @returns #TRUE if the file's permissions could be changed. + */ +dbus_bool_t +_dbus_make_file_world_readable(const DBusString *filename, + DBusError *error) +{ + // TODO + return TRUE; +} + +/** + * Atomically increments an integer + * + * @param atomic pointer to the integer to increment + * @returns the value before incrementing + * + */ +dbus_int32_t +_dbus_atomic_inc (DBusAtomic *atomic) +{ +#ifdef HAVE_STDATOMIC_H + /* Atomic version of "old = *atomic; *atomic += 1; return old" */ + return atomic_fetch_add (&atomic->value, 1); +#else + /* Atomic version of "*atomic += 1; return *atomic - 1" */ + return InterlockedIncrement (&atomic->value) - 1; +#endif +} + +/** + * Atomically decrement an integer + * + * @param atomic pointer to the integer to decrement + * @returns the value before decrementing + * + */ +dbus_int32_t +_dbus_atomic_dec (DBusAtomic *atomic) +{ +#ifdef HAVE_STDATOMIC_H + /* Atomic version of "old = *atomic; *atomic -= 1; return old" */ + return atomic_fetch_sub (&atomic->value, 1); +#else + /* Atomic version of "*atomic -= 1; return *atomic + 1" */ + return InterlockedDecrement (&atomic->value) + 1; +#endif +} + +/** + * Atomically get the value of an integer. It may change at any time + * thereafter, so this is mostly only useful for assertions. + * + * @param atomic pointer to the integer to get + * @returns the value at this moment + */ +dbus_int32_t +_dbus_atomic_get (DBusAtomic *atomic) +{ +#ifdef HAVE_STDATOMIC_H + /* Atomic version of "return *atomic" */ + return atomic_load (&atomic->value); +#else + /* In this situation, GLib issues a MemoryBarrier() and then returns + * atomic->value. However, mingw from mingw.org (not to be confused with + * mingw-w64 from mingw-w64.sf.net) does not have MemoryBarrier in its + * headers, so we have to get a memory barrier some other way. + * + * InterlockedIncrement is older, and is documented on MSDN to be a full + * memory barrier, so let's use that. + */ + long dummy = 0; + + InterlockedExchange (&dummy, 1); + + return atomic->value; +#endif +} + +/** + * Atomically set the value of an integer to 0. + * + * @param atomic pointer to the integer to set + */ +void +_dbus_atomic_set_zero (DBusAtomic *atomic) +{ +#ifdef HAVE_STDATOMIC_H + /* Atomic version of "*atomic = 0" */ + atomic_store (&atomic->value, 0); +#else + InterlockedExchange (&atomic->value, 0); +#endif +} + +/** + * Atomically set the value of an integer to something nonzero. + * + * @param atomic pointer to the integer to set + */ +void +_dbus_atomic_set_nonzero (DBusAtomic *atomic) +{ +#ifdef HAVE_STDATOMIC_H + /* Atomic version of "*atomic = 1" */ + atomic_store (&atomic->value, 1); +#else + InterlockedExchange (&atomic->value, 1); +#endif +} + +/** + * Called when the bus daemon is signaled to reload its configuration; any + * caches should be nuked. Of course any caches that need explicit reload + * are probably broken, but c'est la vie. + * + * + */ +void +_dbus_flush_caches (void) +{ +} + +/** + * See if errno is EAGAIN or EWOULDBLOCK (this has to be done differently + * for Winsock so is abstracted) + * + * @returns #TRUE if e == EAGAIN or e == EWOULDBLOCK + */ +dbus_bool_t +_dbus_get_is_errno_eagain_or_ewouldblock (int e) +{ + return e == WSAEWOULDBLOCK; +} + +/** + * Fill str with the absolute path of the D-Bus installation, or truncate str + * to zero length if we cannot determine it. + * + * @param str buffer for installation path + * @returns #FALSE on OOM, #TRUE if not OOM + */ +dbus_bool_t +_dbus_get_install_root (DBusString *str) +{ + /* this is just an initial guess */ + DWORD pathLength = MAX_PATH; + unsigned char *lastSlash; + unsigned char *prefix; + + do + { + /* allocate enough space for our best guess at the length */ + if (!_dbus_string_set_length (str, pathLength)) + { + _dbus_string_set_length (str, 0); + return FALSE; + } + + SetLastError (0); + pathLength = GetModuleFileNameA (_dbus_win_get_dll_hmodule (), + _dbus_string_get_data (str), _dbus_string_get_length (str)); + + if (pathLength == 0 || GetLastError () != 0) + { + /* failed, but not OOM */ + _dbus_string_set_length (str, 0); + return TRUE; + } + + /* if the return is strictly less than the buffer size, it has + * not been truncated, so we can continue */ + if (pathLength < (DWORD) _dbus_string_get_length (str)) + { + /* reduce the length to match what Windows filled in */ + if (!_dbus_string_set_length (str, pathLength)) + { + _dbus_string_set_length (str, 0); + return FALSE; + } + + break; + } + + /* else it may have been truncated; try with a larger buffer */ + pathLength *= 2; + } + while (TRUE); + + /* the rest of this function works by direct byte manipulation of the + * underlying buffer */ + prefix = _dbus_string_get_udata (str); + + lastSlash = _mbsrchr (prefix, '\\'); + if (lastSlash == NULL) { + /* failed, but not OOM */ + _dbus_string_set_length (str, 0); + return TRUE; + } + //cut off binary name + lastSlash[1] = 0; + + //cut possible "\\bin" + //this fails if we are in a double-byte system codepage and the + //folder's name happens to end with the *bytes* + //"\\bin"... (I.e. the second byte of some Han character and then + //the Latin "bin", but that is not likely I think... + if (lastSlash - prefix >= 4 && _mbsnicmp (lastSlash - 4, (const unsigned char *)"\\bin", 4) == 0) + lastSlash[-3] = 0; + else if (lastSlash - prefix >= 10 && _mbsnicmp (lastSlash - 10, (const unsigned char *)"\\bin\\debug", 10) == 0) + lastSlash[-9] = 0; + else if (lastSlash - prefix >= 12 && _mbsnicmp (lastSlash - 12, (const unsigned char *)"\\bin\\release", 12) == 0) + lastSlash[-11] = 0; + + /* fix up the length to match the byte-manipulation */ + _dbus_string_set_length (str, strlen ((char *) prefix)); + + return TRUE; +} + +/* See comment in dbus-sysdeps-unix.c */ +dbus_bool_t +_dbus_lookup_session_address (dbus_bool_t *supported, + DBusString *address, + DBusError *error) +{ + /* Probably fill this in with something based on COM? */ + *supported = FALSE; + return TRUE; +} + +/** + * Appends the directory in which a keyring for the given credentials + * should be stored. The credentials should have either a Windows or + * UNIX user in them. The directory should be an absolute path. + * + * On UNIX the directory is ~/.dbus-keyrings while on Windows it should probably + * be something else, since the dotfile convention is not normal on Windows. + * + * @param directory string to append directory to + * @param credentials credentials the directory should be for + * + * @returns #FALSE on no memory + */ +dbus_bool_t +_dbus_append_keyring_directory_for_credentials (DBusString *directory, + DBusCredentials *credentials) +{ + DBusString homedir; + DBusString dotdir; + const char *homepath; + const char *homedrive; + + _dbus_assert (credentials != NULL); + _dbus_assert (!_dbus_credentials_are_anonymous (credentials)); + + if (!_dbus_string_init (&homedir)) + return FALSE; + + homedrive = _dbus_getenv("HOMEDRIVE"); + if (homedrive != NULL && *homedrive != '\0') + { + _dbus_string_append(&homedir,homedrive); + } + + homepath = _dbus_getenv("HOMEPATH"); + if (homepath != NULL && *homepath != '\0') + { + _dbus_string_append(&homedir,homepath); + } + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + { + const char *override; + + override = _dbus_getenv ("DBUS_TEST_HOMEDIR"); + if (override != NULL && *override != '\0') + { + _dbus_string_set_length (&homedir, 0); + if (!_dbus_string_append (&homedir, override)) + goto failed; + + _dbus_verbose ("Using fake homedir for testing: %s\n", + _dbus_string_get_const_data (&homedir)); + } + else + { + /* Not strictly thread-safe, but if we fail at thread-safety here, + * the worst that will happen is some extra warnings. */ + static dbus_bool_t already_warned = FALSE; + if (!already_warned) + { + _dbus_warn ("Using your real home directory for testing, set DBUS_TEST_HOMEDIR to avoid"); + already_warned = TRUE; + } + } + } +#endif + +#ifdef DBUS_WINCE + /* It's not possible to create a .something directory in Windows CE + using the file explorer. */ +#define KEYRING_DIR "dbus-keyrings" +#else +#define KEYRING_DIR ".dbus-keyrings" +#endif + + _dbus_string_init_const (&dotdir, KEYRING_DIR); + if (!_dbus_concat_dir_and_file (&homedir, + &dotdir)) + goto failed; + + if (!_dbus_string_copy (&homedir, 0, + directory, _dbus_string_get_length (directory))) { + goto failed; + } + + _dbus_string_free (&homedir); + return TRUE; + + failed: + _dbus_string_free (&homedir); + return FALSE; +} + +/** Checks if a file exists +* +* @param file full path to the file +* @returns #TRUE if file exists +*/ +dbus_bool_t +_dbus_file_exists (const char *file) +{ + DWORD attributes = GetFileAttributesA (file); + + if (attributes != INVALID_FILE_ATTRIBUTES && GetLastError() != ERROR_PATH_NOT_FOUND) + return TRUE; + else + return FALSE; +} + +/** + * A wrapper around strerror() because some platforms + * may be lame and not have strerror(). + * + * @param error_number errno. + * @returns error description. + */ +const char* +_dbus_strerror (int error_number) +{ +#ifdef DBUS_WINCE + // TODO + return "unknown"; +#else + const char *msg; + + switch (error_number) + { + case WSAEINTR: + return "Interrupted function call"; + case WSAEACCES: + return "Permission denied"; + case WSAEFAULT: + return "Bad address"; + case WSAEINVAL: + return "Invalid argument"; + case WSAEMFILE: + return "Too many open files"; + case WSAEWOULDBLOCK: + return "Resource temporarily unavailable"; + case WSAEINPROGRESS: + return "Operation now in progress"; + case WSAEALREADY: + return "Operation already in progress"; + case WSAENOTSOCK: + return "Socket operation on nonsocket"; + case WSAEDESTADDRREQ: + return "Destination address required"; + case WSAEMSGSIZE: + return "Message too long"; + case WSAEPROTOTYPE: + return "Protocol wrong type for socket"; + case WSAENOPROTOOPT: + return "Bad protocol option"; + case WSAEPROTONOSUPPORT: + return "Protocol not supported"; + case WSAESOCKTNOSUPPORT: + return "Socket type not supported"; + case WSAEOPNOTSUPP: + return "Operation not supported"; + case WSAEPFNOSUPPORT: + return "Protocol family not supported"; + case WSAEAFNOSUPPORT: + return "Address family not supported by protocol family"; + case WSAEADDRINUSE: + return "Address already in use"; + case WSAEADDRNOTAVAIL: + return "Cannot assign requested address"; + case WSAENETDOWN: + return "Network is down"; + case WSAENETUNREACH: + return "Network is unreachable"; + case WSAENETRESET: + return "Network dropped connection on reset"; + case WSAECONNABORTED: + return "Software caused connection abort"; + case WSAECONNRESET: + return "Connection reset by peer"; + case WSAENOBUFS: + return "No buffer space available"; + case WSAEISCONN: + return "Socket is already connected"; + case WSAENOTCONN: + return "Socket is not connected"; + case WSAESHUTDOWN: + return "Cannot send after socket shutdown"; + case WSAETIMEDOUT: + return "Connection timed out"; + case WSAECONNREFUSED: + return "Connection refused"; + case WSAEHOSTDOWN: + return "Host is down"; + case WSAEHOSTUNREACH: + return "No route to host"; + case WSAEPROCLIM: + return "Too many processes"; + case WSAEDISCON: + return "Graceful shutdown in progress"; + case WSATYPE_NOT_FOUND: + return "Class type not found"; + case WSAHOST_NOT_FOUND: + return "Host not found"; + case WSATRY_AGAIN: + return "Nonauthoritative host not found"; + case WSANO_RECOVERY: + return "This is a nonrecoverable error"; + case WSANO_DATA: + return "Valid name, no data record of requested type"; + case WSA_INVALID_HANDLE: + return "Specified event object handle is invalid"; + case WSA_INVALID_PARAMETER: + return "One or more parameters are invalid"; + case WSA_IO_INCOMPLETE: + return "Overlapped I/O event object not in signaled state"; + case WSA_IO_PENDING: + return "Overlapped operations will complete later"; + case WSA_NOT_ENOUGH_MEMORY: + return "Insufficient memory available"; + case WSA_OPERATION_ABORTED: + return "Overlapped operation aborted"; +#ifdef WSAINVALIDPROCTABLE + + case WSAINVALIDPROCTABLE: + return "Invalid procedure table from service provider"; +#endif +#ifdef WSAINVALIDPROVIDER + + case WSAINVALIDPROVIDER: + return "Invalid service provider version number"; +#endif +#ifdef WSAPROVIDERFAILEDINIT + + case WSAPROVIDERFAILEDINIT: + return "Unable to initialize a service provider"; +#endif + + case WSASYSCALLFAILURE: + return "System call failure"; + + default: + msg = strerror (error_number); + + if (msg == NULL) + msg = "unknown"; + + return msg; + } +#endif //DBUS_WINCE +} + +/** + * Assigns an error name and message corresponding to a Win32 error + * code to a DBusError. Does nothing if error is #NULL. + * + * @param error the error. + * @param code the Win32 error code + */ +void +_dbus_win_set_error_from_win_error (DBusError *error, + int code) +{ + char *msg; + + /* As we want the English message, use the A API */ + FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, code, MAKELANGID (LANG_ENGLISH, SUBLANG_ENGLISH_US), + (LPSTR) &msg, 0, NULL); + if (msg) + { + dbus_set_error (error, "win32.error", "%s", msg); + LocalFree (msg); + } + else + dbus_set_error (error, "win32.error", "Unknown error code %d or FormatMessage failed", code); +} + +void +_dbus_win_warn_win_error (const char *message, + unsigned long code) +{ + DBusError error; + + dbus_error_init (&error); + _dbus_win_set_error_from_win_error (&error, code); + _dbus_warn ("%s: %s", message, error.message); + dbus_error_free (&error); +} + +/** + * Removes a directory; Directory must be empty + * + * @param filename directory filename + * @param error initialized error object + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_delete_directory (const DBusString *filename, + DBusError *error) +{ + const char *filename_c; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + filename_c = _dbus_string_get_const_data (filename); + + if (RemoveDirectoryA (filename_c) == 0) + { + char *emsg = _dbus_win_error_string (GetLastError ()); + dbus_set_error (error, _dbus_win_error_from_last_error (), + "Failed to remove directory %s: %s", + filename_c, emsg); + _dbus_win_free_error_string (emsg); + return FALSE; + } + + return TRUE; +} + +/** + * Checks whether the filename is an absolute path + * + * @param filename the filename + * @returns #TRUE if an absolute path + */ +dbus_bool_t +_dbus_path_is_absolute (const DBusString *filename) +{ + if (_dbus_string_get_length (filename) > 0) + return _dbus_string_get_byte (filename, 1) == ':' + || _dbus_string_get_byte (filename, 0) == '\\' + || _dbus_string_get_byte (filename, 0) == '/'; + else + return FALSE; +} + +dbus_bool_t +_dbus_check_setuid (void) +{ + return FALSE; +} + +int +_dbus_save_socket_errno (void) +{ + return errno; +} + +void +_dbus_restore_socket_errno (int saved_errno) +{ + _dbus_win_set_errno (saved_errno); +} + +static const char *log_tag = "dbus"; +static DBusLogFlags log_flags = DBUS_LOG_FLAGS_STDERR; + +/** + * Initialize the system log. + * + * The "tag" is not copied, and must remain valid for the entire lifetime of + * the process or until _dbus_init_system_log() is called again. In practice + * it will normally be a constant. + * + * @param tag the name of the executable (syslog tag) + * @param mode whether to log to stderr, the system log or both + */ +void +_dbus_init_system_log (const char *tag, + DBusLogFlags flags) +{ + /* We never want to turn off logging completely */ + _dbus_assert ( + (flags & (DBUS_LOG_FLAGS_STDERR | DBUS_LOG_FLAGS_SYSTEM_LOG)) != 0); + + log_tag = tag; + log_flags = flags; +} + +/** + * Log a message to the system log file (e.g. syslog on Unix). + * + * @param severity a severity value + * @param msg a printf-style format string + * @param args arguments for the format string + */ +void +_dbus_logv (DBusSystemLogSeverity severity, + const char *msg, + va_list args) +{ + const char *s = ""; + va_list tmp; + + switch(severity) + { + case DBUS_SYSTEM_LOG_INFO: s = "info"; break; + case DBUS_SYSTEM_LOG_WARNING: s = "warning"; break; + case DBUS_SYSTEM_LOG_SECURITY: s = "security"; break; + case DBUS_SYSTEM_LOG_ERROR: s = "error"; break; + default: _dbus_assert_not_reached ("invalid log severity"); + } + + if (log_flags & DBUS_LOG_FLAGS_SYSTEM_LOG) + { + DBusString out = _DBUS_STRING_INIT_INVALID; + const char *message = NULL; + va_copy (tmp, args); + + if (!_dbus_string_init (&out)) + goto out; + if (!_dbus_string_append_printf (&out, "%s: ", s)) + goto out; + if (!_dbus_string_append_printf_valist (&out, msg, tmp)) + goto out; + message = _dbus_string_get_const_data (&out); +out: + if (message != NULL) + { + OutputDebugStringA (message); + } + else + { + OutputDebugStringA ("Out of memory while formatting message: '''"); + OutputDebugStringA (msg); + OutputDebugStringA ("'''"); + } + + va_end (tmp); + _dbus_string_free (&out); + } + + if (log_flags & DBUS_LOG_FLAGS_STDERR) + { + va_copy (tmp, args); + fprintf (stderr, "%s[%lu]: %s: ", log_tag, _dbus_pid_for_log (), s); + vfprintf (stderr, msg, tmp); + fprintf (stderr, "\n"); + va_end (tmp); + } +} + +/* + * Return the low-level representation of a socket error, as used by + * cross-platform socket APIs like inet_ntop(), send() and recv(). This + * is the standard errno on Unix, but is WSAGetLastError() on Windows. + * + * Some libdbus internal functions copy this into errno, but with + * hindsight that was probably a design flaw. + */ +int +_dbus_get_low_level_socket_errno (void) +{ + return WSAGetLastError (); +} + +void +_dbus_win_set_error_from_last_error (DBusError *error, + const char *format, + ...) +{ + const char *name; + char *message = NULL; + + if (error == NULL) + return; + + /* make sure to do this first, in case subsequent library calls overwrite GetLastError() */ + name = _dbus_win_error_from_last_error (); + message = _dbus_win_error_string (GetLastError ()); + + if (format != NULL) + { + DBusString str; + va_list args; + dbus_bool_t retval; + + if (!_dbus_string_init (&str)) + { + _DBUS_SET_OOM (error); + goto out; + } + + va_start (args, format); + retval = _dbus_string_append_printf_valist (&str, format, args); + va_end (args); + if (!retval) + { + _DBUS_SET_OOM (error); + _dbus_string_free (&str); + goto out; + } + + dbus_set_error (error, name, "%s: %s", _dbus_string_get_const_data (&str), message); + _dbus_string_free (&str); + } + else + { + dbus_set_error (error, name, "%s", message); + } + +out: + if (message != NULL) + _dbus_win_free_error_string (message); + + _DBUS_ASSERT_ERROR_IS_SET (error); +} + +/** + * Creates a Windows event object and returns the corresponding handle + * + * The returned object is unnamed, is a manual-reset event object, + * is initially in the non-signalled state, and is inheritable by child + * processes. + * + * @param error the error to set + * @return handle for the created event + * @return #NULL if an error has occurred, the reason is returned in \p error + */ +HANDLE +_dbus_win_event_create_inheritable (DBusError *error) +{ + HANDLE handle; + + handle = CreateEvent (NULL, TRUE, FALSE, NULL); + if (handle == NULL) + { + _dbus_win_set_error_from_last_error (error, "Could not create event"); + return NULL; + } + else if (GetLastError () == ERROR_ALREADY_EXISTS) + { + _dbus_win_set_error_from_last_error (error, "Event already exists"); + return NULL; + } + + if (!SetHandleInformation (handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) + { + _dbus_win_set_error_from_last_error (error, "Could not set inheritance for event %p", handle); + CloseHandle (handle); + return NULL; + } + return handle; +} + +/** + * Set a Windows event to the signalled state + * + * @param handle the handle for the event to be set + * @return TRUE the event was set successfully + * @return FALSE an error has occurred, the reason is returned in \p error + */ +dbus_bool_t +_dbus_win_event_set (HANDLE handle, DBusError *error) +{ + _dbus_assert (handle != NULL); + + if (!SetEvent (handle)) + { + _dbus_win_set_error_from_last_error (error, "Could not trigger event (handle %p)", handle); + return FALSE; + } + return TRUE; +} + +/** + * Wait for a Windows event to enter the signalled state + * + * @param handle the handle for the event to wait for + * @param timeout the waiting time in milliseconds, or INFINITE to wait forever, + * or 0 to check immediately and not wait (polling) + * @param error the error to set + * @return TRUE the event was set successfully + * @return FALSE an error has occurred, the reason is returned in \p error + */ +dbus_bool_t +_dbus_win_event_wait (HANDLE handle, int timeout, DBusError *error) +{ + DWORD status; + + _dbus_assert (handle != NULL); + + status = WaitForSingleObject (handle, timeout); + switch (status) + { + case WAIT_OBJECT_0: + return TRUE; + + case WAIT_FAILED: + { + _dbus_win_set_error_from_last_error (error, "Unable to wait for event (handle %p)", handle); + return FALSE; + } + + case WAIT_TIMEOUT: + /* GetLastError() is not set */ + dbus_set_error (error, DBUS_ERROR_TIMEOUT, "Timed out waiting for event (handle %p)", handle); + return FALSE; + + default: + /* GetLastError() is probably not set? */ + dbus_set_error (error, DBUS_ERROR_FAILED, "Unknown result '%lu' while waiting for event (handle %p)", status, handle); + return FALSE; + } +} + +/** + * Delete a Windows event + * + * @param handle handle for the event to delete + * @param error the error to set (optional) + * @return TRUE the event has been deleted successfully or the handle is one of the special sentinel values #NULL or #INVALID_HANDLE_VALUE + * @return FALSE an error has occurred, the reason is returned in \p error if specified + */ +dbus_bool_t +_dbus_win_event_free (HANDLE handle, DBusError *error) +{ + if (handle == NULL || handle == INVALID_HANDLE_VALUE) + return TRUE; + + if (CloseHandle (handle)) + return TRUE; + + /* the handle may already be closed */ + if (GetLastError () == ERROR_INVALID_HANDLE) + return TRUE; + + _dbus_win_set_error_from_last_error (error, "Could not close event (handle %p)", handle); + return FALSE; +} + +#ifdef HAVE_AFUNIX_H +static dbus_bool_t +_dbus_open_socket (SOCKET *socket_p, + int domain, + int type, + int protocol, + DBusError *error) +{ + if (!_dbus_win_startup_winsock ()) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + *socket_p = socket (domain, type, protocol); + if (*socket_p == INVALID_SOCKET) + { + DBUS_SOCKET_SET_ERRNO (); + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to open socket: %s", + _dbus_strerror_from_errno ()); + return FALSE; + } + + _dbus_win_handle_set_close_on_exec ((HANDLE) *socket_p); + return TRUE; +} + +/** + * Opens a UNIX domain socket (as in the socket() call). + * Does not bind the socket. + * + * This will set CLOEXEC for the socket returned + * + * @param return location for socket descriptor + * @param error return location for an error + * @returns #FALSE if error is set + */ +static dbus_bool_t +_dbus_open_unix_socket (SOCKET *socket, + DBusError *error) +{ + return _dbus_open_socket (socket, AF_UNIX, SOCK_STREAM, 0, error); +} +#endif /* HAVE_AFUNIX_H */ + +/** + * Creates a socket and connects it to the UNIX domain socket at the + * given path. The socket is returned, and is set up as + * nonblocking. + * + * Abstract socket usage always fails. + * + * This will set FD_CLOEXEC for the socket returned. + * + * @param path the path to UNIX domain socket + * @param abstract #TRUE to use abstract namespace + * @param error return location for error code + * @returns a valid socket on success or an invalid socket on error + */ +DBusSocket +_dbus_connect_unix_socket (const char *path, + dbus_bool_t abstract, + DBusError *error) +{ + DBusSocket s = DBUS_SOCKET_INIT; + +#ifdef HAVE_AFUNIX_H + struct sockaddr_un addr; + size_t path_len; + + _DBUS_STATIC_ASSERT (sizeof (addr.sun_path) > _DBUS_MAX_SUN_PATH_LENGTH); + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_verbose ("connecting to unix socket %s abstract=%d\n", + path, abstract); + + if (abstract) + { + dbus_set_error (error, DBUS_ERROR_NOT_SUPPORTED, + "Failed to connect: UNIX abstract socket is not supported on this system"); + return s; + } + + path_len = strlen (path); + if (path_len > _DBUS_MAX_SUN_PATH_LENGTH) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "Failed to connect: socket name too long"); + return s; + } + + if (!_dbus_open_unix_socket (&s.sock, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return s; + } + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _DBUS_ZERO (addr); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, path, sizeof (addr.sun_path) - 1); + + if (connect (s.sock, (struct sockaddr *) &addr, _DBUS_STRUCT_OFFSET (struct sockaddr_un, sun_path) + path_len) < 0) + { + DBUS_SOCKET_SET_ERRNO (); + dbus_set_error (error, + _dbus_error_from_errno (errno), + "Failed to connect to socket %s: %s", + path, _dbus_strerror (errno)); + + _dbus_close_socket (&s, NULL); + return s; + } + + if (!_dbus_set_socket_nonblocking (s, error)) + _dbus_close_socket (&s, NULL); + +#else + dbus_set_error (error, DBUS_ERROR_NOT_SUPPORTED, + "Failed to connect: UNIX socket is not supported with this build"); +#endif + + return s; +} + +/** + * Creates a socket and binds it to the given path, + * then listens on the socket. The socket is + * set to be nonblocking. + * + * Abstract socket usage always fails. + * + * This will set CLOEXEC for the socket returned + * + * @param path the socket name + * @param abstract #TRUE to use abstract namespace + * @param error return location for errors + * @returns a valid socket on success or an invalid socket on error + */ +DBusSocket +_dbus_listen_unix_socket (const char *path, + dbus_bool_t abstract, + DBusError *error) +{ + DBusSocket s = DBUS_SOCKET_INIT; + +#ifdef HAVE_AFUNIX_H + struct sockaddr_un addr; + size_t path_len; + _DBUS_STATIC_ASSERT (sizeof (addr.sun_path) > _DBUS_MAX_SUN_PATH_LENGTH); + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_verbose ("listening on unix socket %s abstract=%d\n", + path, abstract); + + if (abstract) + { + dbus_set_error (error, DBUS_ERROR_NOT_SUPPORTED, + "Failed to listen: UNIX abstract socket is not supported on this system"); + return s; + } + + if (!_dbus_open_unix_socket (&s.sock, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return s; + } + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _DBUS_ZERO (addr); + addr.sun_family = AF_UNIX; + path_len = strlen (path); + + /* see related comment in dbus-sysdeps-unix.c */ + /* there is no S_ISSOCK on windows yet, so just unlink the path */ + unlink (path); + + if (path_len > _DBUS_MAX_SUN_PATH_LENGTH) + { + dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, + "Failed to listen: socket name too long"); + _dbus_close_socket (&s, NULL); + return s; + } + + strncpy (addr.sun_path, path, sizeof (addr.sun_path) - 1); + + if (bind (s.sock, (struct sockaddr *) &addr, _DBUS_STRUCT_OFFSET (struct sockaddr_un, sun_path) + path_len) < 0) + { + DBUS_SOCKET_SET_ERRNO (); + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to bind socket \"%s\": %s", + path, _dbus_strerror (errno)); + _dbus_close_socket (&s, NULL); + return s; + } + + if (listen (s.sock, SOMAXCONN /* backlog */) < 0) + { + DBUS_SOCKET_SET_ERRNO (); + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to listen on socket \"%s\": %s", + path, _dbus_strerror (errno)); + _dbus_close_socket (&s, NULL); + return s; + } + + if (!_dbus_set_socket_nonblocking (s, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_close_socket (&s, NULL); + return s; + } +#else + dbus_set_error (error, DBUS_ERROR_NOT_SUPPORTED, + "Failed to listen: UNIX socket is not supported with this build"); +#endif + + return s; +} + +/** @} end of sysdeps-win */ +/* tests in dbus-sysdeps-util.c */ diff --git a/src/3rdparty/libdbus/dbus/dbus-sysdeps-win.h b/src/3rdparty/libdbus/dbus/dbus-sysdeps-win.h new file mode 100644 index 00000000..f7be2101 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-sysdeps-win.h @@ -0,0 +1,125 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sysdeps.c Wrappers around system/libc features (internal to D-BUS implementation) + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2005 Novell, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_SYSDEPS_WIN_H +#define DBUS_SYSDEPS_WIN_H + +extern void *_dbus_win_get_dll_hmodule (void); +#define WIN32_LEAN_AND_MEAN + +#include "dbus-hash.h" +#include "dbus-string.h" +#include "dbus-threads-internal.h" +#include <ctype.h> +#include <malloc.h> +#include <windows.h> +#undef interface + +#define DBUS_CONSOLE_DIR "/var/run/console/" + + +void _dbus_win_set_errno (int err); +DBUS_PRIVATE_EXPORT +const char* _dbus_win_error_from_last_error (void); + +dbus_bool_t _dbus_win_startup_winsock (void); +void _dbus_win_warn_win_error (const char *message, + unsigned long code); +DBUS_PRIVATE_EXPORT +char * _dbus_win_error_string (int error_number); +DBUS_PRIVATE_EXPORT +void _dbus_win_free_error_string (char *string); + +extern const char* _dbus_lm_strerror (int error_number); + + +dbus_bool_t _dbus_win_account_to_sid (const wchar_t *waccount, + void **ppsid, + DBusError *error); + +dbus_bool_t +_dbus_win32_sid_to_name_and_domain (dbus_uid_t uid, + wchar_t **wname, + wchar_t **wdomain, + DBusError *error); + + +/* Don't define DBUS_CONSOLE_DIR on Win32 */ + +wchar_t *_dbus_win_utf8_to_utf16 (const char *str, + DBusError *error); +char *_dbus_win_utf16_to_utf8 (const wchar_t *str, + DBusError *error); + +DBUS_PRIVATE_EXPORT +void _dbus_win_set_error_from_win_error (DBusError *error, int code); + +dbus_bool_t +_dbus_win_sid_to_name_and_domain (dbus_uid_t uid, + wchar_t **wname, + wchar_t **wdomain, + DBusError *error); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_get_install_root (DBusString *str); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_getsid(char **sid, dbus_pid_t process_id); + +HANDLE _dbus_spawn_program (const char *name, + char **argv, + char **envp, + dbus_bool_t inherit_handles, + DBusError *error); + +DBUS_PRIVATE_EXPORT +void _dbus_win_set_error_from_last_error (DBusError *error, + const char *format, + ...) _DBUS_GNUC_PRINTF (2, 3); + +DBUS_PRIVATE_EXPORT +HANDLE _dbus_win_event_create_inheritable (DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_win_event_set (HANDLE handle, DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_win_event_wait (HANDLE handle, int timeout, DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_win_event_free (HANDLE handle, DBusError *error); + +dbus_bool_t _dbus_daemon_is_session_bus_address_published (const char *scope); +dbus_bool_t _dbus_daemon_publish_session_bus_address (const char *address, + const char *shm_name); +DBUS_PRIVATE_EXPORT +DBusRMutex *_dbus_win_rmutex_named_new (const char* name); + +DBUS_PRIVATE_EXPORT +void _dbus_test_win_autolaunch_set_command_line_parameter (const char *path); +DBUS_PRIVATE_EXPORT +void _dbus_test_win_set_autolaunch_handle_location (HANDLE *location); +#endif + +/** @} end of sysdeps-win.h */ diff --git a/src/3rdparty/libdbus/dbus/dbus-sysdeps.c b/src/3rdparty/libdbus/dbus/dbus-sysdeps.c new file mode 100644 index 00000000..8b82bfd5 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-sysdeps.c @@ -0,0 +1,1021 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sysdeps.c Wrappers around system/libc features shared between UNIX and Windows (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003, 2006 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-sysdeps.h" +#include "dbus-threads.h" +#include "dbus-protocol.h" +#include "dbus-string.h" +#include "dbus-list.h" +#include "dbus-misc.h" + +/* NOTE: If you include any unix/windows-specific headers here, you are probably doing something + * wrong and should be putting some code in dbus-sysdeps-unix.c or dbus-sysdeps-win.c. + * + * These are the standard ANSI C headers... + */ +#if HAVE_LOCALE_H +#include <locale.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif + +#ifdef DBUS_WIN + #include <stdlib.h> +#elif (defined __APPLE__) +# include <crt_externs.h> +# define environ (*_NSGetEnviron()) +#elif HAVE_DECL_ENVIRON && defined(HAVE_UNISTD_H) +# include <unistd.h> +#else +extern char **environ; +#endif + +#ifdef DBUS_WIN +#include "dbus-sockets-win.h" +#else +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> +#endif + +/** + * @defgroup DBusSysdeps Internal system-dependent API + * @ingroup DBusInternals + * @brief Internal system-dependent API available on UNIX and Windows + * + * The system-dependent API has a dual purpose. First, it encapsulates + * all usage of operating system APIs for ease of auditing and to + * avoid cluttering the rest of the code with bizarre OS quirks and + * headers. Second, it abstracts different operating system APIs for + * portability. + * + * @{ + */ + +/** + * Aborts the program with SIGABRT (dumping core). + */ +void +_dbus_abort (void) +{ + const char *s; + + _dbus_print_backtrace (); + + s = _dbus_getenv ("DBUS_BLOCK_ON_ABORT"); + if (s && *s) + { + /* don't use _dbus_warn here since it can _dbus_abort() */ + fprintf (stderr, " Process %lu sleeping for gdb attach\n", _dbus_pid_for_log ()); + _dbus_sleep_milliseconds (1000 * 180); + } + + abort (); + _dbus_exit (1); /* in case someone manages to ignore SIGABRT ? */ +} + +/** + * @ingroup DBusMisc + * + * Wrapper for setenv(). If the value is #NULL, unsets + * the environment variable. + * + * There is an unfixable memleak in that it is unsafe to + * free memory malloced for use with setenv. This is because + * we can not rely on internal implementation details of + * the underlying libc library. + * + * This function is not thread-safe, because altering the environment + * in Unix is not thread-safe in general. + * + * @param varname name of environment variable + * @param value value of environment variable, or #NULL to unset + * @returns #TRUE on success, #FALSE if not enough memory. + */ +dbus_bool_t +dbus_setenv (const char *varname, + const char *value) +{ + _dbus_assert (varname != NULL); + + if (value == NULL) + { +#ifdef HAVE_UNSETENV + unsetenv (varname); + return TRUE; +#else + char *putenv_value; + size_t len; + + len = strlen (varname); + + /* Use system malloc to avoid memleaks that dbus_malloc + * will get upset about. + */ + + putenv_value = malloc (len + 2); + if (putenv_value == NULL) + return FALSE; + + strcpy (putenv_value, varname); +#if defined(DBUS_WIN) + strcat (putenv_value, "="); +#endif + + return (putenv (putenv_value) == 0); +#endif + } + else + { +#ifdef HAVE_SETENV + return (setenv (varname, value, TRUE) == 0); +#else + char *putenv_value; + size_t len; + size_t varname_len; + size_t value_len; + + varname_len = strlen (varname); + value_len = strlen (value); + + len = varname_len + value_len + 1 /* '=' */ ; + + /* Use system malloc to avoid memleaks that dbus_malloc + * will get upset about. + */ + + putenv_value = malloc (len + 1); + if (putenv_value == NULL) + return FALSE; + + strcpy (putenv_value, varname); + strcpy (putenv_value + varname_len, "="); + strcpy (putenv_value + varname_len + 1, value); + + return (putenv (putenv_value) == 0); +#endif + } +} + +/** + * Wrapper for getenv(). + * + * @param varname name of environment variable + * @returns value of environment variable or #NULL if unset + */ +const char* +_dbus_getenv (const char *varname) +{ + /* Don't respect any environment variables if the current process is + * setuid. This is the equivalent of glibc's __secure_getenv(). + */ + if (_dbus_check_setuid ()) + return NULL; + return getenv (varname); +} + +/** + * Wrapper for clearenv(). + * + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_clearenv (void) +{ + dbus_bool_t rc = TRUE; + +#ifdef HAVE_CLEARENV + if (clearenv () != 0) + rc = FALSE; +#else + + if (environ != NULL) + environ[0] = NULL; +#endif + + return rc; +} + +/** + * Split paths into a list of char strings + * + * @param dirs string with pathes + * @param suffix string concated to each path in dirs + * @param dir_list contains a list of splitted pathes + * return #TRUE is pathes could be splittes,#FALSE in oom case + */ +dbus_bool_t +_dbus_split_paths_and_append (DBusString *dirs, + const char *suffix, + DBusList **dir_list) +{ + int start; + int i; + int len; + char *cpath; + DBusString file_suffix; + + start = 0; + i = 0; + + _dbus_string_init_const (&file_suffix, suffix); + + len = _dbus_string_get_length (dirs); + + while (_dbus_string_find (dirs, start, _DBUS_PATH_SEPARATOR, &i)) + { + DBusString path; + + if (!_dbus_string_init (&path)) + goto oom; + + if (!_dbus_string_copy_len (dirs, + start, + i - start, + &path, + 0)) + { + _dbus_string_free (&path); + goto oom; + } + + _dbus_string_chop_white (&path); + + /* check for an empty path */ + if (_dbus_string_get_length (&path) == 0) + goto next; + + if (!_dbus_concat_dir_and_file (&path, + &file_suffix)) + { + _dbus_string_free (&path); + goto oom; + } + + if (!_dbus_string_copy_data(&path, &cpath)) + { + _dbus_string_free (&path); + goto oom; + } + + if (!_dbus_list_append (dir_list, cpath)) + { + _dbus_string_free (&path); + dbus_free (cpath); + goto oom; + } + + next: + _dbus_string_free (&path); + start = i + 1; + } + + if (start != len) + { + DBusString path; + + if (!_dbus_string_init (&path)) + goto oom; + + if (!_dbus_string_copy_len (dirs, + start, + len - start, + &path, + 0)) + { + _dbus_string_free (&path); + goto oom; + } + + if (!_dbus_concat_dir_and_file (&path, + &file_suffix)) + { + _dbus_string_free (&path); + goto oom; + } + + if (!_dbus_string_copy_data(&path, &cpath)) + { + _dbus_string_free (&path); + goto oom; + } + + if (!_dbus_list_append (dir_list, cpath)) + { + _dbus_string_free (&path); + dbus_free (cpath); + goto oom; + } + + _dbus_string_free (&path); + } + + return TRUE; + + oom: + _dbus_list_clear_full (dir_list, dbus_free); + return FALSE; +} + +/** @} */ + +/** + * @addtogroup DBusString + * + * @{ + */ +/** + * Appends an integer to a DBusString. + * + * @param str the string + * @param value the integer value + * @returns #FALSE if not enough memory or other failure. + */ +dbus_bool_t +_dbus_string_append_int (DBusString *str, + long value) +{ + /* this calculation is from comp.lang.c faq */ +#define MAX_LONG_LEN ((sizeof (long) * 8 + 2) / 3 + 1) /* +1 for '-' */ + int orig_len; + int i; + char *buf; + + orig_len = _dbus_string_get_length (str); + + if (!_dbus_string_lengthen (str, MAX_LONG_LEN)) + return FALSE; + + buf = _dbus_string_get_data_len (str, orig_len, MAX_LONG_LEN); + + snprintf (buf, MAX_LONG_LEN, "%ld", value); + + i = 0; + while (*buf) + { + ++buf; + ++i; + } + + _dbus_string_shorten (str, MAX_LONG_LEN - i); + + return TRUE; +} + +/** + * Appends an unsigned integer to a DBusString. + * + * @param str the string + * @param value the integer value + * @returns #FALSE if not enough memory or other failure. + */ +dbus_bool_t +_dbus_string_append_uint (DBusString *str, + unsigned long value) +{ + /* this is wrong, but definitely on the high side. */ +#define MAX_ULONG_LEN (MAX_LONG_LEN * 2) + int orig_len; + int i; + char *buf; + + orig_len = _dbus_string_get_length (str); + + if (!_dbus_string_lengthen (str, MAX_ULONG_LEN)) + return FALSE; + + buf = _dbus_string_get_data_len (str, orig_len, MAX_ULONG_LEN); + + snprintf (buf, MAX_ULONG_LEN, "%lu", value); + + i = 0; + while (*buf) + { + ++buf; + ++i; + } + + _dbus_string_shorten (str, MAX_ULONG_LEN - i); + + return TRUE; +} + +/** + * Parses an integer contained in a DBusString. Either return parameter + * may be #NULL if you aren't interested in it. The integer is parsed + * and stored in value_return. Return parameters are not initialized + * if the function returns #FALSE. + * + * @param str the string + * @param start the byte index of the start of the integer + * @param value_return return location of the integer value or #NULL + * @param end_return return location of the end of the integer, or #NULL + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_string_parse_int (const DBusString *str, + int start, + long *value_return, + int *end_return) +{ + long v; + const char *p; + char *end; + + p = _dbus_string_get_const_data_len (str, start, + _dbus_string_get_length (str) - start); + + end = NULL; + _dbus_set_errno_to_zero (); + v = strtol (p, &end, 0); + if (end == NULL || end == p || errno != 0) + return FALSE; + + if (value_return) + *value_return = v; + if (end_return) + *end_return = start + (end - p); + + return TRUE; +} + +/** + * Parses an unsigned integer contained in a DBusString. Either return + * parameter may be #NULL if you aren't interested in it. The integer + * is parsed and stored in value_return. Return parameters are not + * initialized if the function returns #FALSE. + * + * @param str the string + * @param start the byte index of the start of the integer + * @param value_return return location of the integer value or #NULL + * @param end_return return location of the end of the integer, or #NULL + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_string_parse_uint (const DBusString *str, + int start, + unsigned long *value_return, + int *end_return) +{ + unsigned long v; + const char *p; + char *end; + + p = _dbus_string_get_const_data_len (str, start, + _dbus_string_get_length (str) - start); + + end = NULL; + _dbus_set_errno_to_zero (); + v = strtoul (p, &end, 0); + if (end == NULL || end == p || errno != 0) + return FALSE; + + if (value_return) + *value_return = v; + if (end_return) + *end_return = start + (end - p); + + return TRUE; +} + +/** + * Parses a dbus_int64_t integer contained in a DBusString. Either return parameter + * may be #NULL if you aren't interested in it. The integer is parsed + * and stored in value_return. Return parameters are not initialized + * if the function returns #FALSE. + * + * @param str the string + * @param start the byte index of the start of the integer + * @param value_return return location of the integer value or #NULL + * @param end_return return location of the end of the integer, or #NULL + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_string_parse_int64 (const DBusString *str, + int start, + dbus_int64_t *value_return, + int *end_return) +{ + dbus_int64_t v; + const char *p; + char *end; + + p = _dbus_string_get_const_data_len (str, start, + _dbus_string_get_length (str) - start); + + end = NULL; + _dbus_set_errno_to_zero (); + v = strtoll (p, &end, 0); + if (end == NULL || end == p || errno != 0) + return FALSE; + + if (value_return) + *value_return = v; + if (end_return) + *end_return = start + (end - p); + + return TRUE; +} + +/** @} */ /* DBusString group */ + +/** + * @addtogroup DBusInternalsUtils + * @{ + */ + +/** + * Fills n_bytes of the given buffer with random bytes. + * + * @param buffer an allocated buffer + * @param n_bytes the number of bytes in buffer to write to + * @param error location to store reason for failure + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_generate_random_bytes_buffer (char *buffer, + int n_bytes, + DBusError *error) +{ + DBusString str; + + if (!_dbus_string_init (&str)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_generate_random_bytes (&str, n_bytes, error)) + { + _dbus_string_free (&str); + return FALSE; + } + + _dbus_string_copy_to_buffer (&str, buffer, n_bytes); + + _dbus_string_free (&str); + return TRUE; +} + +/** + * Generates the given number of random bytes, where the bytes are + * chosen from the alphanumeric ASCII subset. + * + * @param str the string + * @param n_bytes the number of random ASCII bytes to append to string + * @param error location to store reason for failure + * @returns #TRUE on success, #FALSE if no memory or other failure + */ +dbus_bool_t +_dbus_generate_random_ascii (DBusString *str, + int n_bytes, + DBusError *error) +{ + static const char letters[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; + int i; + int len; + + if (!_dbus_generate_random_bytes (str, n_bytes, error)) + return FALSE; + + len = _dbus_string_get_length (str); + i = len - n_bytes; + while (i < len) + { + _dbus_string_set_byte (str, i, + letters[_dbus_string_get_byte (str, i) % + (sizeof (letters) - 1)]); + + ++i; + } + + _dbus_assert (_dbus_string_validate_ascii (str, len - n_bytes, + n_bytes)); + + return TRUE; +} + +/** + * Converts a UNIX errno, or Windows errno or WinSock error value into + * a #DBusError name. + * + * @todo should cover more errnos, specifically those + * from open(). + * + * @param error_number the errno. + * @returns an error name + */ +const char* +_dbus_error_from_errno (int error_number) +{ + switch (error_number) + { + case 0: + return DBUS_ERROR_FAILED; + +#ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: + return DBUS_ERROR_NOT_SUPPORTED; +#elif defined(WSAEPROTONOSUPPORT) + case WSAEPROTONOSUPPORT: + return DBUS_ERROR_NOT_SUPPORTED; +#endif +#ifdef EAFNOSUPPORT + case EAFNOSUPPORT: + return DBUS_ERROR_NOT_SUPPORTED; +#elif defined(WSAEAFNOSUPPORT) + case WSAEAFNOSUPPORT: + return DBUS_ERROR_NOT_SUPPORTED; +#endif +#ifdef ENFILE + case ENFILE: + return DBUS_ERROR_LIMITS_EXCEEDED; /* kernel out of memory */ +#endif +#ifdef EMFILE + case EMFILE: + return DBUS_ERROR_LIMITS_EXCEEDED; +#endif +#ifdef EACCES + case EACCES: + return DBUS_ERROR_ACCESS_DENIED; +#endif +#ifdef EPERM + case EPERM: + return DBUS_ERROR_ACCESS_DENIED; +#endif +#ifdef ENOBUFS + case ENOBUFS: + return DBUS_ERROR_NO_MEMORY; +#endif +#ifdef ENOMEM + case ENOMEM: + return DBUS_ERROR_NO_MEMORY; +#endif +#ifdef ECONNREFUSED + case ECONNREFUSED: + return DBUS_ERROR_NO_SERVER; +#elif defined(WSAECONNREFUSED) + case WSAECONNREFUSED: + return DBUS_ERROR_NO_SERVER; +#endif +#ifdef ETIMEDOUT + case ETIMEDOUT: + return DBUS_ERROR_TIMEOUT; +#elif defined(WSAETIMEDOUT) + case WSAETIMEDOUT: + return DBUS_ERROR_TIMEOUT; +#endif +#ifdef ENETUNREACH + case ENETUNREACH: + return DBUS_ERROR_NO_NETWORK; +#elif defined(WSAENETUNREACH) + case WSAENETUNREACH: + return DBUS_ERROR_NO_NETWORK; +#endif +#ifdef EADDRINUSE + case EADDRINUSE: + return DBUS_ERROR_ADDRESS_IN_USE; +#elif defined(WSAEADDRINUSE) + case WSAEADDRINUSE: + return DBUS_ERROR_ADDRESS_IN_USE; +#endif +#ifdef EEXIST + case EEXIST: + return DBUS_ERROR_FILE_EXISTS; +#endif +#ifdef ENOENT + case ENOENT: + return DBUS_ERROR_FILE_NOT_FOUND; +#endif + default: + return DBUS_ERROR_FAILED; + } +} + +/** + * Converts the current system errno value into a #DBusError name. + * + * @returns an error name + */ +const char* +_dbus_error_from_system_errno (void) +{ + return _dbus_error_from_errno (errno); +} + +/** + * Assign 0 to the global errno variable + */ +void +_dbus_set_errno_to_zero (void) +{ +#ifdef DBUS_WINCE + SetLastError (0); +#else + errno = 0; +#endif +} + +/** + * See if errno is ENOMEM + * @returns #TRUE if e == ENOMEM + */ +dbus_bool_t +_dbus_get_is_errno_enomem (int e) +{ + return e == ENOMEM; +} + +/** + * See if errno is EINTR + * @returns #TRUE if e == EINTR + */ +dbus_bool_t +_dbus_get_is_errno_eintr (int e) +{ + return e == EINTR; +} + +/** + * See if errno is EPIPE + * @returns #TRUE if errno == EPIPE + */ +dbus_bool_t +_dbus_get_is_errno_epipe (int e) +{ + return e == EPIPE; +} + +/** + * See if errno is ETOOMANYREFS + * @returns #TRUE if errno == ETOOMANYREFS + */ +dbus_bool_t +_dbus_get_is_errno_etoomanyrefs (int e) +{ +#ifdef ETOOMANYREFS + return e == ETOOMANYREFS; +#else + return FALSE; +#endif +} + +/** + * Get error message from errno + * @returns _dbus_strerror(errno) + */ +const char* +_dbus_strerror_from_errno (void) +{ + return _dbus_strerror (errno); +} + +/** + * Log a message to the system log file (e.g. syslog on Unix) and/or stderr. + * + * @param severity a severity value + * @param msg a printf-style format string + */ +void +_dbus_log (DBusSystemLogSeverity severity, + const char *msg, + ...) +{ + va_list args; + + va_start (args, msg); + + _dbus_logv (severity, msg, args); + + va_end (args); +} + +/* + * Try to convert the IPv4 or IPv6 address pointed to by + * sockaddr_pointer into a string. + * + * @param sockaddr_pointer A struct sockaddr_in or struct sockaddr_in6 + * @param len The length of the struct pointed to by sockaddr_pointer + * @param string An array to write the address into + * @param string_len Length of string (should usually be at least INET6_ADDRSTRLEN) + * @param family_name Used to return "ipv4" or "ipv6", or NULL to ignore + * @param port Used to return the port number, or NULL to ignore + * @returns #FALSE with errno set if the address family was not understood + */ +dbus_bool_t +_dbus_inet_sockaddr_to_string (const void *sockaddr_pointer, + size_t len, + char *string, + size_t string_len, + const char **family_name, + dbus_uint16_t *port, + DBusError *error) +{ + union + { + struct sockaddr sa; + struct sockaddr_storage storage; + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } addr; + int saved_errno; + + if (len > sizeof (addr)) + return FALSE; + + _DBUS_ZERO (addr); + memcpy (&addr, sockaddr_pointer, len); + + switch (addr.sa.sa_family) + { + case AF_INET: + if (inet_ntop (AF_INET, &addr.ipv4.sin_addr, string, string_len) != NULL) + { + if (family_name != NULL) + *family_name = "ipv4"; + + if (port != NULL) + *port = ntohs (addr.ipv4.sin_port); + + return TRUE; + } + else + { + saved_errno = _dbus_get_low_level_socket_errno (); + dbus_set_error (error, _dbus_error_from_errno (saved_errno), + "Failed to get identity of IPv4 socket: %s", + _dbus_strerror (saved_errno)); + } + + return FALSE; + +#ifdef AF_INET6 + case AF_INET6: + if (inet_ntop (AF_INET6, &addr.ipv6.sin6_addr, string, string_len) != NULL) + { + if (family_name != NULL) + *family_name = "ipv6"; + + if (port != NULL) + *port = ntohs (addr.ipv6.sin6_port); + + return TRUE; + } + else + { + saved_errno = _dbus_get_low_level_socket_errno (); + dbus_set_error (error, _dbus_error_from_errno (saved_errno), + "Failed to get identity of IPv6 socket: %s", + _dbus_strerror (saved_errno)); + } + + return FALSE; +#endif + + default: + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to get identity of socket: unknown family"); + return FALSE; + } +} + +/* + * Format an error appropriate for saved_errno for the IPv4 or IPv6 + * address pointed to by sockaddr_pointer of length sockaddr_len. + * + * @param error The error to set + * @param sockaddr_pointer A struct sockaddr_in or struct sockaddr_in6 + * @param len The length of the struct pointed to by sockaddr_pointer + * @param description A prefix like "Failed to listen on socket" + * @param saved_errno The OS-level error number to use + */ +void +_dbus_set_error_with_inet_sockaddr (DBusError *error, + const void *sockaddr_pointer, + size_t len, + const char *description, + int saved_errno) +{ + char string[INET6_ADDRSTRLEN]; + dbus_uint16_t port; + const struct sockaddr *addr = sockaddr_pointer; + + if (_dbus_inet_sockaddr_to_string (sockaddr_pointer, len, + string, sizeof (string), NULL, &port, + NULL)) + { + dbus_set_error (error, _dbus_error_from_errno (saved_errno), + "%s \"%s\" port %u: %s", + description, string, port, _dbus_strerror (saved_errno)); + } + else + { + dbus_set_error (error, _dbus_error_from_errno (saved_errno), + "%s <address of unknown family %d>: %s", + description, addr->sa_family, + _dbus_strerror (saved_errno)); + } +} + +void +_dbus_combine_tcp_errors (DBusList **sources, + const char *summary, + const char *host, + const char *port, + DBusError *dest) +{ + DBusString message = _DBUS_STRING_INIT_INVALID; + + if (_dbus_list_length_is_one (sources)) + { + /* If there was exactly one error, just use it */ + dbus_move_error (_dbus_list_get_first (sources), dest); + } + else + { + DBusList *iter; + const char *name = NULL; + + /* If there was more than one error, concatenate all the + * errors' diagnostic messages, and use their common error + * name, or DBUS_ERROR_FAILED if more than one name is + * represented */ + if (!_dbus_string_init (&message)) + { + _DBUS_SET_OOM (dest); + goto out; + } + + for (iter = _dbus_list_get_first_link (sources); + iter != NULL; + iter = _dbus_list_get_next_link (sources, iter)) + { + DBusError *error = iter->data; + + if (name == NULL) + { + /* no error names known yet, try to use this one */ + name = error->name; + } + else if (strcmp (name, error->name) != 0) + { + /* errors of two different names exist, reconcile by + * using FAILED */ + name = DBUS_ERROR_FAILED; + } + + if ((_dbus_string_get_length (&message) > 0 && + !_dbus_string_append (&message, "; ")) || + !_dbus_string_append (&message, error->message)) + { + _DBUS_SET_OOM (dest); + goto out; + } + } + + if (name == NULL) + name = DBUS_ERROR_FAILED; + + dbus_set_error (dest, name, "%s to \"%s\":%s (%s)", + summary, host ? host : "*", port, + _dbus_string_get_const_data (&message)); + } + +out: + _dbus_string_free (&message); +} + +/** @} end of sysdeps */ + +/* tests in dbus-sysdeps-util.c */ diff --git a/src/3rdparty/libdbus/dbus/dbus-sysdeps.h b/src/3rdparty/libdbus/dbus/dbus-sysdeps.h new file mode 100644 index 00000000..27053a43 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-sysdeps.h @@ -0,0 +1,775 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-sysdeps.h Wrappers around system/libc features (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_SYSDEPS_H +#define DBUS_SYSDEPS_H + +#ifndef VERSION +#warning Please include config.h before dbus-sysdeps.h +#include "config.h" +#endif + +#include <stdint.h> + +#ifdef HAVE_INTTYPES_H +#include <inttypes.h> +#endif + +#ifdef HAVE_STDATOMIC_H +#include <stdatomic.h> +#endif + +#include <dbus/dbus-errors.h> +#include <dbus/dbus-file.h> +#include <dbus/dbus-string.h> + +/* this is perhaps bogus, but strcmp() etc. are faster if we use the + * stuff straight out of string.h, so have this here for now. + */ +#include <string.h> +#include <stdarg.h> + +#if !defined(BROKEN_POLL) && (defined(__APPLE__) || defined(__INTERIX)) + /* Following libcurl's example, we blacklist poll() on Darwin + * (macOS, iOS, etc.) and Interix due to a history of implementation + * issues. + * https://github.com/curl/curl/blob/master/m4/curl-functions.m4 + * + * On unspecified older macOS versions, poll() failed if given a + * device node to poll. + * + * On macOS < 10.9, poll() with nfds=0 failed instead of waiting for + * the timeout and then succeeding. + * + * On macOS >= 10.12, poll() with nfds=0 succeeded immediately + * instead of waiting for the timeout, resulting in busy-looping. + * + * On Interix, poll() apparently only works for files in /proc. + * + * The "legacy" build flavour in our CI machinery defines BROKEN_POLL + * on whatever platform is in use (normally Linux) to force use of the + * same select()-based poll() emulation that we use for macOS, Interix, + * and any platform that lacks a real poll(), so that we can test it + * more regularly. + */ +# define BROKEN_POLL +#endif + +/* Normally we'd only include this in dbus-sysdeps-unix.c. + * However, the member names in DBusPollFD are (deliberately) the same as + * in POSIX struct pollfd, and AIX's poll() implementation is known to + * do things like "#define events reqevents", which would break that approach. + * Defend against that by ensuring that if it's renamed anywhere, it's renamed + * everywhere. + */ +#ifdef HAVE_POLL +#include <poll.h> +#endif + +#ifdef DBUS_WINCE +/* Windows CE lacks some system functions (such as errno and clock). + We bring them in here. */ +#include "dbus-sysdeps-wince-glue.h" +#endif + +#ifdef DBUS_WIN +#include <ws2tcpip.h> +#endif + +DBUS_BEGIN_DECLS + +#ifdef DBUS_WIN +#define _DBUS_PATH_SEPARATOR ";" +#else +#define _DBUS_PATH_SEPARATOR ":" +#endif + +/* Forward declarations */ + + +/** An opaque list type */ +typedef struct DBusList DBusList; + +/** Object that contains a list of credentials such as UNIX or Windows user ID */ +typedef struct DBusCredentials DBusCredentials; + +/** A wrapper around a pipe descriptor or handle */ +typedef struct DBusPipe DBusPipe; + +/** + * @addtogroup DBusSysdeps + * + * @{ + */ + +DBUS_PRIVATE_EXPORT +void _dbus_abort (void) _DBUS_GNUC_NORETURN; + +dbus_bool_t _dbus_check_setuid (void); +DBUS_PRIVATE_EXPORT +const char* _dbus_getenv (const char *varname); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_clearenv (void); +char ** _dbus_get_environment (void); + +/** A process ID */ +typedef unsigned long dbus_pid_t; +/** A user ID */ +typedef unsigned long dbus_uid_t; +/** A group ID */ +typedef unsigned long dbus_gid_t; + +/** an invalid PID used to represent an uninitialized dbus_pid_t field */ +#define DBUS_PID_UNSET ((dbus_pid_t) -1) +/** an invalid UID used to represent an uninitialized dbus_uid_t field */ +#define DBUS_UID_UNSET ((dbus_uid_t) -1) +/** an invalid GID used to represent an uninitialized dbus_gid_t field */ +#define DBUS_GID_UNSET ((dbus_gid_t) -1) + +/** an appropriate printf format for dbus_pid_t */ +#define DBUS_PID_FORMAT "%lu" +/** an appropriate printf format for dbus_uid_t */ +#define DBUS_UID_FORMAT "%lu" +/** an appropriate printf format for dbus_gid_t */ +#define DBUS_GID_FORMAT "%lu" + +/** + * Socket interface + */ +#ifdef DBUS_WIN + +typedef struct { SOCKET sock; } DBusSocket; +# define DBUS_SOCKET_FORMAT "Iu" +# define DBUS_SOCKET_INIT { INVALID_SOCKET } + +_DBUS_WARN_UNUSED_RESULT +static inline SOCKET +_dbus_socket_printable (DBusSocket s) { return s.sock; } + +_DBUS_WARN_UNUSED_RESULT +static inline dbus_bool_t +_dbus_socket_is_valid (DBusSocket s) { return s.sock != INVALID_SOCKET; } + +static inline void +_dbus_socket_invalidate (DBusSocket *s) { s->sock = INVALID_SOCKET; } + +_DBUS_WARN_UNUSED_RESULT +static inline int +_dbus_socket_get_int (DBusSocket s) { return (int)s.sock; } + +#else /* not DBUS_WIN */ + +typedef struct { int fd; } DBusSocket; +# define DBUS_SOCKET_FORMAT "d" +# define DBUS_SOCKET_INIT { -1 } + +_DBUS_WARN_UNUSED_RESULT +static inline int +_dbus_socket_printable (DBusSocket s) { return s.fd; } + +_DBUS_WARN_UNUSED_RESULT +static inline dbus_bool_t +_dbus_socket_is_valid (DBusSocket s) { return s.fd >= 0; } + +static inline void +_dbus_socket_invalidate (DBusSocket *s) { s->fd = -1; } + +_DBUS_WARN_UNUSED_RESULT +static inline int +_dbus_socket_get_int (DBusSocket s) { return s.fd; } + +#endif /* not DBUS_WIN */ + +_DBUS_WARN_UNUSED_RESULT +static inline DBusSocket +_dbus_socket_get_invalid (void) +{ + DBusSocket s = DBUS_SOCKET_INIT; + + return s; +} + +dbus_bool_t _dbus_set_socket_nonblocking (DBusSocket fd, + DBusError *error); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_close_socket (DBusSocket *fd, + DBusError *error); +DBUS_PRIVATE_EXPORT +int _dbus_read_socket (DBusSocket fd, + DBusString *buffer, + int count); +DBUS_PRIVATE_EXPORT +int _dbus_write_socket (DBusSocket fd, + const DBusString *buffer, + int start, + int len); +int _dbus_write_socket_two (DBusSocket fd, + const DBusString *buffer1, + int start1, + int len1, + const DBusString *buffer2, + int start2, + int len2); + +int _dbus_read_socket_with_unix_fds (DBusSocket fd, + DBusString *buffer, + int count, + int *fds, + unsigned int *n_fds); +DBUS_PRIVATE_EXPORT +int _dbus_write_socket_with_unix_fds (DBusSocket fd, + const DBusString *buffer, + int start, + int len, + const int *fds, + int n_fds); +int _dbus_write_socket_with_unix_fds_two (DBusSocket fd, + const DBusString *buffer1, + int start1, + int len1, + const DBusString *buffer2, + int start2, + int len2, + const int *fds, + int n_fds); + +DBusSocket _dbus_connect_tcp_socket (const char *host, + const char *port, + const char *family, + DBusError *error); +DBusSocket _dbus_connect_tcp_socket_with_nonce (const char *host, + const char *port, + const char *family, + const char *noncefile, + DBusError *error); +int _dbus_listen_tcp_socket (const char *host, + const char *port, + const char *family, + DBusString *retport, + const char **retfamily, + DBusSocket **fds_p, + DBusError *error); +DBusSocket _dbus_accept (DBusSocket listen_fd); + +dbus_bool_t _dbus_read_credentials_socket (DBusSocket client_fd, + DBusCredentials *credentials, + DBusError *error); +dbus_bool_t _dbus_send_credentials_socket (DBusSocket server_fd, + DBusError *error); + +typedef enum +{ + DBUS_CREDENTIALS_ADD_FLAGS_USER_DATABASE = (1 << 0), + DBUS_CREDENTIALS_ADD_FLAGS_NONE = 0 +} DBusCredentialsAddFlags; + +dbus_bool_t _dbus_credentials_add_from_user (DBusCredentials *credentials, + const DBusString *username, + DBusCredentialsAddFlags flags, + DBusError *error); + +dbus_bool_t _dbus_credentials_add_from_current_process (DBusCredentials *credentials); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_append_user_from_current_process (DBusString *str); + +dbus_bool_t _dbus_parse_unix_user_from_config (const DBusString *username, + dbus_uid_t *uid_p); +dbus_bool_t _dbus_parse_unix_group_from_config (const DBusString *groupname, + dbus_gid_t *gid_p); +dbus_bool_t _dbus_unix_groups_from_uid (dbus_uid_t uid, + dbus_gid_t **group_ids, + int *n_group_ids, + DBusError *error); +dbus_bool_t _dbus_unix_user_is_at_console (dbus_uid_t uid, + DBusError *error); +dbus_bool_t _dbus_unix_user_is_process_owner (dbus_uid_t uid); +dbus_bool_t _dbus_windows_user_is_process_owner (const char *windows_sid); + +dbus_bool_t _dbus_append_keyring_directory_for_credentials (DBusString *directory, + DBusCredentials *credentials); + +dbus_bool_t _dbus_daemon_unpublish_session_bus_address (void); + +dbus_bool_t _dbus_socket_can_pass_unix_fd(DBusSocket fd); + +/* PID FDs are Linux-specific. */ +#ifdef DBUS_WIN +static inline dbus_pid_t _dbus_resolve_pid_fd (int pid_fd) +{ + return DBUS_PID_UNSET; +} + +#else +DBUS_PRIVATE_EXPORT +dbus_pid_t _dbus_resolve_pid_fd (int pid_fd); +#endif + +/** Opaque type representing an atomically-modifiable integer + * that can be used from multiple threads. + */ +typedef struct DBusAtomic DBusAtomic; + +/** + * An atomic integer safe to increment or decrement from multiple threads. + */ +struct DBusAtomic +{ +#ifdef HAVE_STDATOMIC_H + atomic_int value; /**< Value of the atomic integer. */ +#elif defined(DBUS_WIN) + volatile long value; /**< Value of the atomic integer. */ +#else + volatile dbus_int32_t value; /**< Value of the atomic integer. */ +#endif +}; + +DBUS_PRIVATE_EXPORT +dbus_int32_t _dbus_atomic_inc (DBusAtomic *atomic); +DBUS_PRIVATE_EXPORT +dbus_int32_t _dbus_atomic_dec (DBusAtomic *atomic); +DBUS_PRIVATE_EXPORT +dbus_int32_t _dbus_atomic_get (DBusAtomic *atomic); +DBUS_PRIVATE_EXPORT +void _dbus_atomic_set_zero (DBusAtomic *atomic); +DBUS_PRIVATE_EXPORT +void _dbus_atomic_set_nonzero (DBusAtomic *atomic); + +#ifdef DBUS_WIN + +/* On Windows, you can only poll sockets. We emulate Unix poll() using + * select(), so it doesn't matter what precise type we put in DBusPollFD; + * use DBusSocket so that the compiler can check we are doing it right. + */ +typedef DBusSocket DBusPollable; +# define DBUS_POLLABLE_FORMAT "Iu" + +static inline DBusPollable +_dbus_socket_get_pollable (DBusSocket s) { return s; } + +static inline SOCKET +_dbus_pollable_printable (DBusPollable p) { return p.sock; } + +static inline dbus_bool_t +_dbus_pollable_is_valid (DBusPollable p) { return _dbus_socket_is_valid (p); } + +static inline void +_dbus_pollable_invalidate (DBusPollable *p) { _dbus_socket_invalidate (p); } + +static inline dbus_bool_t +_dbus_pollable_equals (DBusPollable a, DBusPollable b) { return a.sock == b.sock; } + +#else /* !DBUS_WIN */ + +/* On Unix, you can poll sockets, pipes, etc., and we must put exactly + * "int" in DBusPollFD because we're relying on its layout exactly matching + * struct pollfd. (This is silly, and one day we should use a better + * abstraction.) + */ +typedef int DBusPollable; +# define DBUS_POLLABLE_FORMAT "d" + +static inline DBusPollable +_dbus_socket_get_pollable (DBusSocket s) { return s.fd; } + +static inline int +_dbus_pollable_printable (DBusPollable p) { return p; } + +static inline dbus_bool_t +_dbus_pollable_is_valid (DBusPollable p) { return p >= 0; } + +static inline void +_dbus_pollable_invalidate (DBusPollable *p) { *p = -1; } + +static inline dbus_bool_t +_dbus_pollable_equals (DBusPollable a, DBusPollable b) { return a == b; } + +#endif /* !DBUS_WIN */ + +#if defined(HAVE_POLL) && !defined(BROKEN_POLL) +/** + * A portable struct pollfd wrapper, or an emulation of struct pollfd + * on platforms where poll() is missing or broken. + */ +typedef struct pollfd DBusPollFD; + +/** There is data to read */ +#define _DBUS_POLLIN POLLIN +/** There is urgent data to read */ +#define _DBUS_POLLPRI POLLPRI +/** Writing now will not block */ +#define _DBUS_POLLOUT POLLOUT +/** Error condition */ +#define _DBUS_POLLERR POLLERR +/** Hung up */ +#define _DBUS_POLLHUP POLLHUP +/** Invalid request: fd not open */ +#define _DBUS_POLLNVAL POLLNVAL +#else +/* Emulate poll() via select(). Because we aren't really going to call + * poll(), any similarly-shaped struct is acceptable, and any power of 2 + * will do for the events/revents; these values happen to match Linux + * and *BSD. */ +typedef struct +{ + DBusPollable fd; /**< File descriptor */ + short events; /**< Events to poll for */ + short revents; /**< Events that occurred */ +} DBusPollFD; + +/** There is data to read */ +#define _DBUS_POLLIN 0x0001 +/** There is urgent data to read */ +#define _DBUS_POLLPRI 0x0002 +/** Writing now will not block */ +#define _DBUS_POLLOUT 0x0004 +/** Error condition */ +#define _DBUS_POLLERR 0x0008 +/** Hung up */ +#define _DBUS_POLLHUP 0x0010 +/** Invalid request: fd not open */ +#define _DBUS_POLLNVAL 0x0020 +#endif + +DBUS_PRIVATE_EXPORT +int _dbus_poll (DBusPollFD *fds, + int n_fds, + int timeout_milliseconds); + +DBUS_PRIVATE_EXPORT +void _dbus_sleep_milliseconds (int milliseconds); + +DBUS_PRIVATE_EXPORT +void _dbus_get_monotonic_time (dbus_int64_t *tv_sec, + long *tv_usec); + +DBUS_PRIVATE_EXPORT +void _dbus_get_real_time (dbus_int64_t *tv_sec, + long *tv_usec); + +/** + * directory interface + */ +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_create_directory (const DBusString *filename, + DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_ensure_directory (const DBusString *filename, + DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_delete_directory (const DBusString *filename, + DBusError *error); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_concat_dir_and_file (DBusString *dir, + const DBusString *next_component); +dbus_bool_t _dbus_string_get_dirname (const DBusString *filename, + DBusString *dirname); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_path_is_absolute (const DBusString *filename); + +dbus_bool_t _dbus_get_standard_session_servicedirs (DBusList **dirs); +dbus_bool_t _dbus_get_standard_system_servicedirs (DBusList **dirs); +dbus_bool_t _dbus_set_up_transient_session_servicedirs (DBusList **dirs, + DBusError *error); + +dbus_bool_t _dbus_get_system_config_file (DBusString *str); +dbus_bool_t _dbus_get_session_config_file (DBusString *str); + +/** Opaque type for reading a directory listing */ +typedef struct DBusDirIter DBusDirIter; + +DBusDirIter* _dbus_directory_open (const DBusString *filename, + DBusError *error); +dbus_bool_t _dbus_directory_get_next_file (DBusDirIter *iter, + DBusString *filename, + DBusError *error); +void _dbus_directory_close (DBusDirIter *iter); + +dbus_bool_t _dbus_check_dir_is_private_to_user (DBusString *dir, + DBusError *error); + +DBUS_PRIVATE_EXPORT +const char* _dbus_get_tmpdir (void); + +/** + * Random numbers + */ +_DBUS_WARN_UNUSED_RESULT +dbus_bool_t _dbus_generate_random_bytes_buffer (char *buffer, + int n_bytes, + DBusError *error); +dbus_bool_t _dbus_generate_random_bytes (DBusString *str, + int n_bytes, + DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_generate_random_ascii (DBusString *str, + int n_bytes, + DBusError *error); + +DBUS_PRIVATE_EXPORT +const char* _dbus_error_from_errno (int error_number); +DBUS_PRIVATE_EXPORT +const char* _dbus_error_from_system_errno (void); + +int _dbus_get_low_level_socket_errno (void); + +int _dbus_save_socket_errno (void); +void _dbus_restore_socket_errno (int saved_errno); +void _dbus_set_errno_to_zero (void); +dbus_bool_t _dbus_get_is_errno_eagain_or_ewouldblock (int e); +dbus_bool_t _dbus_get_is_errno_enomem (int e); +dbus_bool_t _dbus_get_is_errno_eintr (int e); +dbus_bool_t _dbus_get_is_errno_epipe (int e); +dbus_bool_t _dbus_get_is_errno_etoomanyrefs (int e); +DBUS_PRIVATE_EXPORT +const char* _dbus_strerror_from_errno (void); + +void _dbus_disable_sigpipe (void); + +DBUS_PRIVATE_EXPORT +void _dbus_exit (int code) _DBUS_GNUC_NORETURN; + +DBUS_PRIVATE_EXPORT +int _dbus_printf_string_upper_bound (const char *format, + va_list args) _DBUS_GNUC_PRINTF (1, 0); + +#ifdef DBUS_ENABLE_VERBOSE_MODE +DBUS_PRIVATE_EXPORT +void _dbus_print_thread (void); +#endif + +/** + * Portable struct with stat() results + */ +typedef struct +{ + unsigned long mode; /**< File mode */ + unsigned long nlink; /**< Number of hard links */ + dbus_uid_t uid; /**< User owning file */ + dbus_gid_t gid; /**< Group owning file */ + unsigned long size; /**< Size of file */ + unsigned long atime; /**< Access time */ + unsigned long mtime; /**< Modify time */ + unsigned long ctime; /**< Creation time */ +} DBusStat; + +dbus_bool_t _dbus_stat (const DBusString *filename, + DBusStat *statbuf, + DBusError *error); + +DBusSocket _dbus_connect_unix_socket (const char *path, + dbus_bool_t abstract, + DBusError *error); +DBusSocket _dbus_listen_unix_socket (const char *path, + dbus_bool_t abstract, + DBusError *error); + +DBusSocket _dbus_connect_exec (const char *path, + char *const argv[], + DBusError *error); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_socketpair (DBusSocket *fd1, + DBusSocket *fd2, + dbus_bool_t blocking, + DBusError *error); + +DBUS_PRIVATE_EXPORT +void _dbus_print_backtrace (void); + +dbus_bool_t _dbus_become_daemon (const DBusString *pidfile, + DBusPipe *print_pid_pipe, + DBusError *error, + dbus_bool_t keep_umask); + +dbus_bool_t _dbus_verify_daemon_user (const char *user); +dbus_bool_t _dbus_change_to_daemon_user (const char *user, + DBusError *error); + +dbus_bool_t _dbus_write_pid_to_file_and_pipe (const DBusString *pidfile, + DBusPipe *print_pid_pipe, + dbus_pid_t pid_to_write, + DBusError *error); + +dbus_bool_t _dbus_command_for_pid (unsigned long pid, + DBusString *str, + int max_len, + DBusError *error); + +typedef enum { + DBUS_LOG_FLAGS_STDERR = (1 << 0), + DBUS_LOG_FLAGS_SYSTEM_LOG = (1 << 1) +} DBusLogFlags; + +DBUS_PRIVATE_EXPORT +void _dbus_init_system_log (const char *tag, + DBusLogFlags flags); + +typedef enum { + DBUS_SYSTEM_LOG_INFO, + DBUS_SYSTEM_LOG_WARNING, + DBUS_SYSTEM_LOG_SECURITY, + DBUS_SYSTEM_LOG_ERROR +} DBusSystemLogSeverity; + +DBUS_PRIVATE_EXPORT +void _dbus_log (DBusSystemLogSeverity severity, + const char *msg, + ...) _DBUS_GNUC_PRINTF (2, 3); +DBUS_PRIVATE_EXPORT +void _dbus_logv (DBusSystemLogSeverity severity, + const char *msg, + va_list args) _DBUS_GNUC_PRINTF (2, 0); + +/** On x86 there is an 80-bit FPU, and if you do "a == b" it may have a + * or b in an 80-bit register, thus failing to compare the two 64-bit + * doubles for bitwise equality. So this macro compares the two doubles + * bitwise. + */ +#define _DBUS_DOUBLES_BITWISE_EQUAL(a, b) (memcmp (&(a), &(b), sizeof (double)) == 0) + +dbus_bool_t _dbus_get_autolaunch_address (const char *scope, + DBusString *address, + DBusError *error); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_lookup_session_address (dbus_bool_t *supported, + DBusString *address, + DBusError *error); + +/** Type representing a universally unique ID + * @todo rename to UUID instead of GUID + */ +typedef union DBusGUID DBusGUID; + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_read_local_machine_uuid (DBusGUID *machine_id, + dbus_bool_t create_if_not_found, + DBusError *error); + +/** + * Initialize threads as in dbus_threads_init_default(), appropriately + * for the platform. + * @returns #FALSE if no memory + */ +dbus_bool_t _dbus_threads_init_platform_specific (void); + +/** + * Lock a static mutex used to protect _dbus_threads_init_platform_specific(). + */ +void _dbus_threads_lock_platform_specific (void); + +/** + * Undo _dbus_threads_lock_platform_specific(). + */ +void _dbus_threads_unlock_platform_specific (void); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_split_paths_and_append (DBusString *dirs, + const char *suffix, + DBusList **dir_list); + +unsigned long _dbus_pid_for_log (void); + +/* FIXME move back to dbus-sysdeps-unix.h probably - + * the PID file handling just needs a little more abstraction + * in the bus daemon first. + */ +DBUS_PRIVATE_EXPORT +dbus_pid_t _dbus_getpid (void); + +DBUS_PRIVATE_EXPORT +dbus_uid_t _dbus_getuid (void); + +DBUS_PRIVATE_EXPORT +void _dbus_flush_caches (void); + +dbus_bool_t _dbus_replace_install_prefix (DBusString *path); + +/* Do not set this too high: it is a denial-of-service risk. + * See <https://bugs.freedesktop.org/show_bug.cgi?id=82820> + * + * (This needs to be in the non-Unix-specific header so that + * the config-parser can use it.) + */ +#define DBUS_DEFAULT_MESSAGE_UNIX_FDS 16 + +typedef struct DBusRLimit DBusRLimit; + +DBusRLimit *_dbus_rlimit_save_fd_limit (DBusError *error); +dbus_bool_t _dbus_rlimit_raise_fd_limit (DBusError *error); +dbus_bool_t _dbus_rlimit_restore_fd_limit (DBusRLimit *saved, + DBusError *error); +void _dbus_rlimit_free (DBusRLimit *lim); + +void _dbus_daemon_report_ready (void); +void _dbus_daemon_report_reloading (void); +void _dbus_daemon_report_reloaded (void); +void _dbus_daemon_report_stopping (void); + +dbus_bool_t _dbus_inet_sockaddr_to_string (const void *sockaddr_pointer, + size_t len, + char *string, + size_t string_len, + const char **family_name, + dbus_uint16_t *port, + DBusError *error); +void _dbus_set_error_with_inet_sockaddr (DBusError *error, + const void *sockaddr_pointer, + size_t len, + const char *description, + int saved_errno); +void _dbus_combine_tcp_errors (DBusList **sources, + const char *summary, + const char *host, + const char *port, + DBusError *dest); + +/** + * @def _DBUS_MAX_SUN_PATH_LENGTH + * + * Maximum length of the path to a UNIX domain socket, + * sockaddr_un::sun_path member. POSIX requires that all systems + * support at least 100 bytes here, including the nul termination. + * We use 99 for the max value to allow for the nul. + * + * We could probably also do sizeof (addr.sun_path) + * but this way we are the same on all platforms + * which is probably a good idea. + */ +#define _DBUS_MAX_SUN_PATH_LENGTH 99 + +/** @} */ + +DBUS_END_DECLS + + +#ifdef DBUS_WIN +#include "dbus-sysdeps-win.h" +#endif + +#endif /* DBUS_SYSDEPS_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-test-tap.h b/src/3rdparty/libdbus/dbus/dbus-test-tap.h new file mode 100644 index 00000000..157942e1 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-test-tap.h @@ -0,0 +1,66 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-test-tap — TAP helpers for "embedded tests" + * + * Copyright © 2017 Collabora Ltd. + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef DBUS_TEST_TAP_H +#define DBUS_TEST_TAP_H + +#include <dbus/dbus-internals.h> + +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_test_fatal (const char *format, + ...) _DBUS_GNUC_NORETURN _DBUS_GNUC_PRINTF (1, 2); + +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_test_diag (const char *format, + ...) _DBUS_GNUC_PRINTF (1, 2); + +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_test_skip_all (const char *format, + ...) _DBUS_GNUC_NORETURN _DBUS_GNUC_PRINTF (1, 2); + +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_test_ok (const char *format, + ...) _DBUS_GNUC_PRINTF (1, 2); +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_test_not_ok (const char *format, + ...) _DBUS_GNUC_PRINTF (1, 2); +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_test_skip (const char *format, + ...) _DBUS_GNUC_PRINTF (1, 2); + +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_test_check_memleaks (const char *test_name); + +DBUS_EMBEDDED_TESTS_EXPORT +int _dbus_test_done_testing (void); + +#define _dbus_test_check(a) do { \ + if (!(a)) \ + _dbus_test_not_ok ("%s:%d - '%s' failed\n", __FILE__, __LINE__, #a); \ + } while (0) + +#endif diff --git a/src/3rdparty/libdbus/dbus/dbus-test.h b/src/3rdparty/libdbus/dbus/dbus-test.h new file mode 100644 index 00000000..8eac0041 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-test.h @@ -0,0 +1,57 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-test.h Declarations of test functions. + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_TEST_H +#define DBUS_TEST_H + +#include <dbus/dbus-types.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-marshal-validate.h> + +/* Only things that are in libdbus-1.la and used from libdbus-internal.la + * need to have DBUS_PRIVATE_EXPORT. If you get + * + * warning: 'foo' redeclared without dllimport attribute: previous + * dllimport ignored [-Wattributes] + * + * then you have added too many. + */ + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_marshal_test (const char *test_data_dir); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_keyring_test (const char *test_data_dir); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_data_slot_test (const char *test_data_dir); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_memory_test (const char *test_data_dir); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_object_tree_test (const char *test_data_dir); + +#endif /* DBUS_TEST_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-threads-internal.h b/src/3rdparty/libdbus/dbus/dbus-threads-internal.h new file mode 100644 index 00000000..4c7bde63 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-threads-internal.h @@ -0,0 +1,168 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-threads-internal.h D-Bus thread primitives + * + * Copyright (C) 2002, 2005 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_THREADS_INTERNAL_H +#define DBUS_THREADS_INTERNAL_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-threads.h> + +/** + * @addtogroup DBusThreadsInternals + * @{ + */ + +/** + * A mutex which is recursive if possible, else non-recursive. + * This is typically recursive, but that cannot be relied upon. + */ +typedef struct DBusRMutex DBusRMutex; + +/** + * A mutex suitable for use with condition variables. + * This is typically non-recursive. + */ +typedef struct DBusCMutex DBusCMutex; + +/** @} */ + +DBUS_BEGIN_DECLS + +DBUS_PRIVATE_EXPORT +void _dbus_rmutex_lock (DBusRMutex *mutex); +DBUS_PRIVATE_EXPORT +void _dbus_rmutex_unlock (DBusRMutex *mutex); +void _dbus_rmutex_new_at_location (DBusRMutex **location_p); +void _dbus_rmutex_free_at_location (DBusRMutex **location_p); + +void _dbus_cmutex_lock (DBusCMutex *mutex); +void _dbus_cmutex_unlock (DBusCMutex *mutex); +void _dbus_cmutex_new_at_location (DBusCMutex **location_p); +void _dbus_cmutex_free_at_location (DBusCMutex **location_p); + +DBusCondVar* _dbus_condvar_new (void); +void _dbus_condvar_free (DBusCondVar *cond); +void _dbus_condvar_wait (DBusCondVar *cond, + DBusCMutex *mutex); +dbus_bool_t _dbus_condvar_wait_timeout (DBusCondVar *cond, + DBusCMutex *mutex, + int timeout_milliseconds); +void _dbus_condvar_wake_one (DBusCondVar *cond); +void _dbus_condvar_new_at_location (DBusCondVar **location_p); +void _dbus_condvar_free_at_location (DBusCondVar **location_p); + +/* Private to threading implementations and dbus-threads.c */ + +/** + * Creates a new mutex which is recursive if possible + * + * This mutex is used to avoid deadlocking if we hold them while + * calling user code. + * + * @return mutex instance or #NULL on OOM + */ +DBUS_EMBEDDED_TESTS_EXPORT +DBusRMutex *_dbus_platform_rmutex_new (void); + +/** + * Free a recursive usable mutex + * + * @param mutex the mutex instance to free + */ +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_platform_rmutex_free (DBusRMutex *mutex); + +/** + * Locks a recursively usable mutex + * + * @param mutex the mutex instance to lock + * + * Unlike _dbus_cmutex_lock(), it is valid for the same thread + * to lock a recursive mutex more than once, and it will not + * deadlock. Each call to this function must be paired with a + * corresponding call to _dbus_rmutex_unlock(). + */ +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_platform_rmutex_lock (DBusRMutex *mutex); + +/** + * Release a recursively usable mutex + * + * @param mutex the mutex instance to release + */ +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_platform_rmutex_unlock (DBusRMutex *mutex); + +/** + * Creates a new mutex suitable for use with condition variables + * + * @return mutex instance or #NULL on OOM + */ +DBUS_EMBEDDED_TESTS_EXPORT +DBusCMutex *_dbus_platform_cmutex_new (void); + +/** + * Implementation of _dbus_rmutex_new_at_location(). + * This should only be called internally by the threading implementation. + */ +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_platform_cmutex_free (DBusCMutex *mutex); + +/** + * Locks a mutex suitable for use with condition variables + * + * @param mutex the mutex instance to lock + * + * @note On Windows, after a thread obtains ownership of a mutex, + * it can specify the same mutex in repeated calls to the dbus + * platform related mutex lock functions without blocking its + * execution. This prevents a thread from deadlocking itself + * while waiting for a mutex that it already owns. On unix + * like os, calling the dbus platform related mutex lock + * functions the second time is a programming error. + */ +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_platform_cmutex_lock (DBusCMutex *mutex); + +/** + * Release a mutex suitable for use with condition variables + * + * @param mutex the mutex instance to release + */ +DBUS_EMBEDDED_TESTS_EXPORT +void _dbus_platform_cmutex_unlock (DBusCMutex *mutex); + +DBusCondVar* _dbus_platform_condvar_new (void); +void _dbus_platform_condvar_free (DBusCondVar *cond); +void _dbus_platform_condvar_wait (DBusCondVar *cond, + DBusCMutex *mutex); +dbus_bool_t _dbus_platform_condvar_wait_timeout (DBusCondVar *cond, + DBusCMutex *mutex, + int timeout_milliseconds); +void _dbus_platform_condvar_wake_one (DBusCondVar *cond); + +DBUS_END_DECLS + +#endif /* DBUS_THREADS_INTERNAL_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-threads.c b/src/3rdparty/libdbus/dbus/dbus-threads.c new file mode 100644 index 00000000..b22cc031 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-threads.c @@ -0,0 +1,452 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-threads.h D-Bus threads handling + * + * Copyright (C) 2002, 2003, 2006 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <config.h> +#include "dbus-threads.h" +#include "dbus-internals.h" +#include "dbus-threads-internal.h" +#include "dbus-list.h" + +/* Protected by _dbus_threads_lock_platform_specific() */ +static int thread_init_generation = 0; + +/** + * @defgroup DBusThreadsInternals Thread functions + * @ingroup DBusInternals + * @brief _dbus_rmutex_lock(), etc. + * + * Functions and macros related to threads and thread locks. + * + * @{ + */ + +/** + * Creates a new mutex + * or creates a no-op mutex if threads are not initialized. + * May return #NULL even if threads are initialized, indicating + * out-of-memory. + * + * If possible, the mutex returned by this function is recursive, to + * avoid deadlocks. However, that cannot be relied on. + * + * @param location_p the location of the new mutex, can return #NULL on OOM + */ +void +_dbus_rmutex_new_at_location (DBusRMutex **location_p) +{ + _dbus_assert (location_p != NULL); + + if (!dbus_threads_init_default ()) + { + *location_p = NULL; + return; + } + + *location_p = _dbus_platform_rmutex_new (); +} + +/** + * Creates a new mutex + * or creates a no-op mutex if threads are not initialized. + * May return #NULL even if threads are initialized, indicating + * out-of-memory. + * + * The returned mutex is suitable for use with condition variables. + * + * @param location_p the location of the new mutex, can return #NULL on OOM + */ +void +_dbus_cmutex_new_at_location (DBusCMutex **location_p) +{ + _dbus_assert (location_p != NULL); + + if (!dbus_threads_init_default ()) + { + *location_p = NULL; + return; + } + + *location_p = _dbus_platform_cmutex_new (); +} + +/** + * Frees a DBusRMutex; does nothing if passed a #NULL pointer. + */ +void +_dbus_rmutex_free_at_location (DBusRMutex **location_p) +{ + if (location_p == NULL) + return; + + if (*location_p != NULL) + _dbus_platform_rmutex_free (*location_p); +} + +/** + * Frees a DBusCMutex; does nothing if passed a #NULL pointer. + */ +void +_dbus_cmutex_free_at_location (DBusCMutex **location_p) +{ + if (location_p == NULL) + return; + + if (*location_p != NULL) + _dbus_platform_cmutex_free (*location_p); +} + +/** + * Locks a mutex. Does nothing if passed a #NULL pointer. + * Locks may be recursive if threading implementation initialized + * recursive locks. + */ +void +_dbus_rmutex_lock (DBusRMutex *mutex) +{ + if (mutex == NULL) + return; + + _dbus_platform_rmutex_lock (mutex); +} + +/** + * Locks a mutex. Does nothing if passed a #NULL pointer. + * Locks may be recursive if threading implementation initialized + * recursive locks. + */ +void +_dbus_cmutex_lock (DBusCMutex *mutex) +{ + if (mutex == NULL) + return; + + _dbus_platform_cmutex_lock (mutex); +} + +/** + * Unlocks a mutex. Does nothing if passed a #NULL pointer. + * + * @returns #TRUE on success + */ +void +_dbus_rmutex_unlock (DBusRMutex *mutex) +{ + if (mutex == NULL) + return; + + _dbus_platform_rmutex_unlock (mutex); +} + +/** + * Unlocks a mutex. Does nothing if passed a #NULL pointer. + * + * @returns #TRUE on success + */ +void +_dbus_cmutex_unlock (DBusCMutex *mutex) +{ + if (mutex == NULL) + return; + + _dbus_platform_cmutex_unlock (mutex); +} + +/** + * Creates a new condition variable using the function supplied + * to dbus_threads_init(), or creates a no-op condition variable + * if threads are not initialized. May return #NULL even if + * threads are initialized, indicating out-of-memory. + * + * @returns new mutex or #NULL + */ +DBusCondVar * +_dbus_condvar_new (void) +{ + if (!dbus_threads_init_default ()) + return NULL; + + return _dbus_platform_condvar_new (); +} + + +/** + * This does the same thing as _dbus_condvar_new. It however + * gives another level of indirection by allocating a pointer + * to point to the condvar location; this used to be useful. + * + * @returns the location of a new condvar or #NULL on OOM + */ + +void +_dbus_condvar_new_at_location (DBusCondVar **location_p) +{ + _dbus_assert (location_p != NULL); + + *location_p = _dbus_condvar_new(); +} + + +/** + * Frees a conditional variable created with dbus_condvar_new(); does + * nothing if passed a #NULL pointer. + */ +void +_dbus_condvar_free (DBusCondVar *cond) +{ + if (cond == NULL) + return; + + _dbus_platform_condvar_free (cond); +} + +/** + * Frees a condition variable; does nothing if passed a #NULL pointer. + */ +void +_dbus_condvar_free_at_location (DBusCondVar **location_p) +{ + if (location_p == NULL) + return; + + if (*location_p != NULL) + _dbus_platform_condvar_free (*location_p); +} + +/** + * Atomically unlocks the mutex and waits for the conditions + * variable to be signalled. Locks the mutex again before + * returning. + * Does nothing if passed a #NULL pointer. + */ +void +_dbus_condvar_wait (DBusCondVar *cond, + DBusCMutex *mutex) +{ + if (cond == NULL || mutex == NULL) + return; + + _dbus_platform_condvar_wait (cond, mutex); +} + +/** + * Atomically unlocks the mutex and waits for the conditions variable + * to be signalled, or for a timeout. Locks the mutex again before + * returning. Does nothing if passed a #NULL pointer. Return value + * is #FALSE if we timed out, #TRUE otherwise. + * + * @param cond the condition variable + * @param mutex the mutex + * @param timeout_milliseconds the maximum time to wait + * @returns #FALSE if the timeout occurred, #TRUE if not + */ +dbus_bool_t +_dbus_condvar_wait_timeout (DBusCondVar *cond, + DBusCMutex *mutex, + int timeout_milliseconds) +{ + if (cond == NULL || mutex == NULL) + return TRUE; + + return _dbus_platform_condvar_wait_timeout (cond, mutex, + timeout_milliseconds); +} + +/** + * If there are threads waiting on the condition variable, wake + * up exactly one. + * Does nothing if passed a #NULL pointer. + */ +void +_dbus_condvar_wake_one (DBusCondVar *cond) +{ + if (cond == NULL) + return; + + _dbus_platform_condvar_wake_one (cond); +} + +/* Protected by _dbus_threads_lock_platform_specific() */ +static DBusRMutex *global_locks[_DBUS_N_GLOBAL_LOCKS] = { NULL }; + +static void +shutdown_global_locks (void *nil) +{ + int i; + + for (i = 0; i < _DBUS_N_GLOBAL_LOCKS; i++) + { + _dbus_assert (global_locks[i] != NULL); + _dbus_platform_rmutex_free (global_locks[i]); + global_locks[i] = NULL; + } +} + +static dbus_bool_t +init_global_locks (void) +{ + int i; + dbus_bool_t ok; + + for (i = 0; i < _DBUS_N_GLOBAL_LOCKS; i++) + { + _dbus_assert (global_locks[i] == NULL); + + global_locks[i] = _dbus_platform_rmutex_new (); + + if (global_locks[i] == NULL) + goto failed; + } + + _dbus_platform_rmutex_lock (global_locks[_DBUS_LOCK_shutdown_funcs]); + ok = _dbus_register_shutdown_func_unlocked (shutdown_global_locks, NULL); + _dbus_platform_rmutex_unlock (global_locks[_DBUS_LOCK_shutdown_funcs]); + + if (!ok) + goto failed; + + return TRUE; + + failed: + for (i = i - 1; i >= 0; i--) + { + _dbus_platform_rmutex_free (global_locks[i]); + global_locks[i] = NULL; + } + + return FALSE; +} + +dbus_bool_t +_dbus_lock (DBusGlobalLock lock) +{ + _dbus_assert (lock >= 0); + _dbus_assert (lock < _DBUS_N_GLOBAL_LOCKS); + + if (thread_init_generation != _dbus_current_generation && + !dbus_threads_init_default ()) + return FALSE; + + _dbus_platform_rmutex_lock (global_locks[lock]); + return TRUE; +} + +void +_dbus_unlock (DBusGlobalLock lock) +{ + _dbus_assert (lock >= 0); + _dbus_assert (lock < _DBUS_N_GLOBAL_LOCKS); + + _dbus_platform_rmutex_unlock (global_locks[lock]); +} + +/** @} */ /* end of internals */ + +/** + * @defgroup DBusThreads Thread functions + * @ingroup DBus + * @brief dbus_threads_init() and dbus_threads_init_default() + * + * Functions and macros related to threads and thread locks. + * + * If threads are initialized, the D-Bus library has locks on all + * global data structures. In addition, each #DBusConnection has a + * lock, so only one thread at a time can touch the connection. (See + * @ref DBusConnection for more on connection locking.) + * + * Most other objects, however, do not have locks - they can only be + * used from a single thread at a time, unless you lock them yourself. + * For example, a #DBusMessage can't be modified from two threads + * at once. + * + * @{ + */ + +/** + * Initializes threads, like dbus_threads_init_default(). + * This version previously allowed user-specified threading + * primitives, but since D-Bus 1.6 it ignores them and behaves + * exactly like dbus_threads_init_default(). + * + * @param functions ignored, formerly functions for using threads + * @returns #TRUE on success, #FALSE if no memory + */ +dbus_bool_t +dbus_threads_init (const DBusThreadFunctions *functions) +{ + _dbus_threads_lock_platform_specific (); + + if (thread_init_generation == _dbus_current_generation) + { + _dbus_threads_unlock_platform_specific (); + return TRUE; + } + + if (!_dbus_threads_init_platform_specific() || + !init_global_locks ()) + { + _dbus_threads_unlock_platform_specific (); + return FALSE; + } + + thread_init_generation = _dbus_current_generation; + + _dbus_threads_unlock_platform_specific (); + return TRUE; +} + + + +/* Default thread implemenation */ + +/** + * Initializes threads. If this function is not called, the D-Bus + * library will not lock any data structures. If it is called, D-Bus + * will do locking, at some cost in efficiency. + * + * Since D-Bus 1.7 it is safe to call this function from any thread, + * any number of times (but it must be called before any other + * libdbus API is used). + * + * In D-Bus 1.6 or older, this function must be called in the main thread + * before any other thread starts. As a result, it is not sufficient to + * call this function in a library or plugin, unless the library or plugin + * imposes a similar requirement on its callers. + * + * dbus_shutdown() reverses the effects of this function when it + * resets all global state in libdbus. + * + * @returns #TRUE on success, #FALSE if not enough memory + */ +dbus_bool_t +dbus_threads_init_default (void) +{ + return dbus_threads_init (NULL); +} + + +/** @} */ + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + +#endif /* DBUS_ENABLE_EMBEDDED_TESTS */ diff --git a/src/3rdparty/libdbus/dbus/dbus-threads.h b/src/3rdparty/libdbus/dbus/dbus-threads.h new file mode 100644 index 00000000..33e1e327 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-threads.h @@ -0,0 +1,191 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-threads.h D-Bus threads handling + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_THREADS_H +#define DBUS_THREADS_H + +#include <dbus/dbus-macros.h> +#include <dbus/dbus-types.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusThreads + * @{ + */ + +/** An opaque mutex type provided by the #DBusThreadFunctions implementation installed by dbus_threads_init(). */ +typedef struct DBusMutex DBusMutex; +/** An opaque condition variable type provided by the #DBusThreadFunctions implementation installed by dbus_threads_init(). */ +typedef struct DBusCondVar DBusCondVar; + +/** Deprecated, provide DBusRecursiveMutexNewFunction instead. */ +typedef DBusMutex* (* DBusMutexNewFunction) (void); +/** Deprecated, provide DBusRecursiveMutexFreeFunction instead. */ +typedef void (* DBusMutexFreeFunction) (DBusMutex *mutex); +/** Deprecated, provide DBusRecursiveMutexLockFunction instead. Return value is lock success, but gets ignored in practice. */ +typedef dbus_bool_t (* DBusMutexLockFunction) (DBusMutex *mutex); +/** Deprecated, provide DBusRecursiveMutexUnlockFunction instead. Return value is unlock success, but gets ignored in practice. */ +typedef dbus_bool_t (* DBusMutexUnlockFunction) (DBusMutex *mutex); + +/** Creates a new recursively-lockable mutex, or returns #NULL if not + * enough memory. Can only fail due to lack of memory. Found in + * #DBusThreadFunctions. Do not just use PTHREAD_MUTEX_RECURSIVE for + * this, because it does not save/restore the recursion count when + * waiting on a condition. libdbus requires the Java-style behavior + * where the mutex is fully unlocked to wait on a condition. + */ +typedef DBusMutex* (* DBusRecursiveMutexNewFunction) (void); +/** Frees a recursively-lockable mutex. Found in #DBusThreadFunctions. + */ +typedef void (* DBusRecursiveMutexFreeFunction) (DBusMutex *mutex); +/** Locks a recursively-lockable mutex. Found in #DBusThreadFunctions. + * Can only fail due to lack of memory. + */ +typedef void (* DBusRecursiveMutexLockFunction) (DBusMutex *mutex); +/** Unlocks a recursively-lockable mutex. Found in #DBusThreadFunctions. + * Can only fail due to lack of memory. + */ +typedef void (* DBusRecursiveMutexUnlockFunction) (DBusMutex *mutex); + +/** Creates a new condition variable. Found in #DBusThreadFunctions. + * Can only fail (returning #NULL) due to lack of memory. + */ +typedef DBusCondVar* (* DBusCondVarNewFunction) (void); +/** Frees a condition variable. Found in #DBusThreadFunctions. + */ +typedef void (* DBusCondVarFreeFunction) (DBusCondVar *cond); + +/** Waits on a condition variable. Found in + * #DBusThreadFunctions. Must work with either a recursive or + * nonrecursive mutex, whichever the thread implementation + * provides. Note that PTHREAD_MUTEX_RECURSIVE does not work with + * condition variables (does not save/restore the recursion count) so + * don't try using simply pthread_cond_wait() and a + * PTHREAD_MUTEX_RECURSIVE to implement this, it won't work right. + * + * Has no error conditions. Must succeed if it returns. + */ +typedef void (* DBusCondVarWaitFunction) (DBusCondVar *cond, + DBusMutex *mutex); + +/** Waits on a condition variable with a timeout. Found in + * #DBusThreadFunctions. Returns #TRUE if the wait did not + * time out, and #FALSE if it did. + * + * Has no error conditions. Must succeed if it returns. + */ +typedef dbus_bool_t (* DBusCondVarWaitTimeoutFunction) (DBusCondVar *cond, + DBusMutex *mutex, + int timeout_milliseconds); +/** Wakes one waiting thread on a condition variable. Found in #DBusThreadFunctions. + * + * Has no error conditions. Must succeed if it returns. + */ +typedef void (* DBusCondVarWakeOneFunction) (DBusCondVar *cond); + +/** Wakes all waiting threads on a condition variable. Found in #DBusThreadFunctions. + * + * Has no error conditions. Must succeed if it returns. + */ +typedef void (* DBusCondVarWakeAllFunction) (DBusCondVar *cond); + +/** + * Flags indicating which functions are present in #DBusThreadFunctions. Used to allow + * the library to detect older callers of dbus_threads_init() if new possible functions + * are added to #DBusThreadFunctions. + */ +typedef enum +{ + DBUS_THREAD_FUNCTIONS_MUTEX_NEW_MASK = 1 << 0, + DBUS_THREAD_FUNCTIONS_MUTEX_FREE_MASK = 1 << 1, + DBUS_THREAD_FUNCTIONS_MUTEX_LOCK_MASK = 1 << 2, + DBUS_THREAD_FUNCTIONS_MUTEX_UNLOCK_MASK = 1 << 3, + DBUS_THREAD_FUNCTIONS_CONDVAR_NEW_MASK = 1 << 4, + DBUS_THREAD_FUNCTIONS_CONDVAR_FREE_MASK = 1 << 5, + DBUS_THREAD_FUNCTIONS_CONDVAR_WAIT_MASK = 1 << 6, + DBUS_THREAD_FUNCTIONS_CONDVAR_WAIT_TIMEOUT_MASK = 1 << 7, + DBUS_THREAD_FUNCTIONS_CONDVAR_WAKE_ONE_MASK = 1 << 8, + DBUS_THREAD_FUNCTIONS_CONDVAR_WAKE_ALL_MASK = 1 << 9, + DBUS_THREAD_FUNCTIONS_RECURSIVE_MUTEX_NEW_MASK = 1 << 10, + DBUS_THREAD_FUNCTIONS_RECURSIVE_MUTEX_FREE_MASK = 1 << 11, + DBUS_THREAD_FUNCTIONS_RECURSIVE_MUTEX_LOCK_MASK = 1 << 12, + DBUS_THREAD_FUNCTIONS_RECURSIVE_MUTEX_UNLOCK_MASK = 1 << 13, + DBUS_THREAD_FUNCTIONS_ALL_MASK = (1 << 14) - 1 +} DBusThreadFunctionsMask; + +/** + * Functions that must be implemented to make the D-Bus library + * thread-aware. + * + * If you supply both recursive and non-recursive mutexes, + * libdbus will use the non-recursive version for condition variables, + * and the recursive version in other contexts. + * + * The condition variable functions have to work with nonrecursive + * mutexes if you provide those, or with recursive mutexes if you + * don't. + */ +typedef struct +{ + unsigned int mask; /**< Mask indicating which functions are present. */ + + DBusMutexNewFunction mutex_new; /**< Function to create a mutex; optional and deprecated. */ + DBusMutexFreeFunction mutex_free; /**< Function to free a mutex; optional and deprecated. */ + DBusMutexLockFunction mutex_lock; /**< Function to lock a mutex; optional and deprecated. */ + DBusMutexUnlockFunction mutex_unlock; /**< Function to unlock a mutex; optional and deprecated. */ + + DBusCondVarNewFunction condvar_new; /**< Function to create a condition variable */ + DBusCondVarFreeFunction condvar_free; /**< Function to free a condition variable */ + DBusCondVarWaitFunction condvar_wait; /**< Function to wait on a condition */ + DBusCondVarWaitTimeoutFunction condvar_wait_timeout; /**< Function to wait on a condition with a timeout */ + DBusCondVarWakeOneFunction condvar_wake_one; /**< Function to wake one thread waiting on the condition */ + DBusCondVarWakeAllFunction condvar_wake_all; /**< Function to wake all threads waiting on the condition */ + + DBusRecursiveMutexNewFunction recursive_mutex_new; /**< Function to create a recursive mutex */ + DBusRecursiveMutexFreeFunction recursive_mutex_free; /**< Function to free a recursive mutex */ + DBusRecursiveMutexLockFunction recursive_mutex_lock; /**< Function to lock a recursive mutex */ + DBusRecursiveMutexUnlockFunction recursive_mutex_unlock; /**< Function to unlock a recursive mutex */ + + void (* padding1) (void); /**< Reserved for future expansion */ + void (* padding2) (void); /**< Reserved for future expansion */ + void (* padding3) (void); /**< Reserved for future expansion */ + void (* padding4) (void); /**< Reserved for future expansion */ + +} DBusThreadFunctions; + +DBUS_EXPORT +dbus_bool_t dbus_threads_init (const DBusThreadFunctions *functions); +DBUS_EXPORT +dbus_bool_t dbus_threads_init_default (void); + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_THREADS_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-timeout.c b/src/3rdparty/libdbus/dbus/dbus-timeout.c new file mode 100644 index 00000000..5ee9808a --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-timeout.c @@ -0,0 +1,519 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-timeout.c DBusTimeout implementation + * + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-timeout.h" +#include "dbus-list.h" + +/** + * @defgroup DBusTimeoutInternals DBusTimeout implementation details + * @ingroup DBusInternals + * @brief implementation details for DBusTimeout + * + * @{ + */ + +/** + * Internals of DBusTimeout + */ +struct DBusTimeout +{ + int refcount; /**< Reference count */ + int interval; /**< Timeout interval in milliseconds. */ + + DBusTimeoutHandler handler; /**< Timeout handler. */ + void *handler_data; /**< Timeout handler data. */ + DBusFreeFunction free_handler_data_function; /**< Free the timeout handler data. */ + + void *data; /**< Application data. */ + DBusFreeFunction free_data_function; /**< Free the application data. */ + unsigned int enabled : 1; /**< True if timeout is active. */ + unsigned int needs_restart : 1; /**< Flag that timeout should be restarted after re-enabling. */ +}; + +/** + * Creates a new DBusTimeout, enabled by default. + * @param interval the timeout interval in milliseconds. + * @param handler function to call when the timeout occurs. + * @param data data to pass to the handler + * @param free_data_function function to be called to free the data. + * @returns the new DBusTimeout object, + */ +DBusTimeout* +_dbus_timeout_new (int interval, + DBusTimeoutHandler handler, + void *data, + DBusFreeFunction free_data_function) +{ + DBusTimeout *timeout; + + timeout = dbus_new0 (DBusTimeout, 1); + if (timeout == NULL) + return NULL; + + timeout->refcount = 1; + timeout->interval = interval; + + timeout->handler = handler; + timeout->handler_data = data; + timeout->free_handler_data_function = free_data_function; + + timeout->enabled = TRUE; + timeout->needs_restart = FALSE; + + return timeout; +} + +/** + * Increments the reference count of a DBusTimeout object. + * + * @param timeout the timeout object. + * @returns the timeout object. + */ +DBusTimeout * +_dbus_timeout_ref (DBusTimeout *timeout) +{ + timeout->refcount += 1; + + return timeout; +} + +/** + * Decrements the reference count of a DBusTimeout object + * and finalizes the object if the count reaches zero. + * + * @param timeout the timeout object. + */ +void +_dbus_timeout_unref (DBusTimeout *timeout) +{ + _dbus_assert (timeout != NULL); + _dbus_assert (timeout->refcount > 0); + + timeout->refcount -= 1; + if (timeout->refcount == 0) + { + dbus_timeout_set_data (timeout, NULL, NULL); /* call free_data_function */ + + if (timeout->free_handler_data_function) + (* timeout->free_handler_data_function) (timeout->handler_data); + + dbus_free (timeout); + } +} + +/** + * Change the timeout interval to be interval milliseconds from now + * (forgetting when the timeout was initially started), and enable it. + * + * This function is only valid when used in conjunction with DBusLoop: + * it can be used in the message bus daemon implementation or in unit tests, + * but it cannot be used in conjunction with an application main loop. + * + * @param timeout the timeout + * @param interval the new interval + */ +void +_dbus_timeout_restart (DBusTimeout *timeout, + int interval) +{ + _dbus_assert (interval >= 0); + + timeout->interval = interval; + timeout->enabled = TRUE; + timeout->needs_restart = TRUE; +} + +/** + * Disable the timeout. Note that you should use + * _dbus_connection_toggle_timeout_unlocked() etc. instead, if + * the timeout is passed out to an application main loop. + * i.e. you can't use this function in the D-Bus library, it's + * only used in the message bus daemon implementation. + * + * @param timeout the timeout + * @param enabled #TRUE if timeout should be enabled. + */ +void +_dbus_timeout_disable (DBusTimeout *timeout) +{ + timeout->enabled = FALSE; +} + +/** + * @typedef DBusTimeoutList + * + * Opaque data type representing a list of timeouts + * and a set of DBusAddTimeoutFunction/DBusRemoveTimeoutFunction. + * Automatically handles removing/re-adding timeouts + * when the DBusAddTimeoutFunction is updated or changed. + * Holds a reference count to each timeout. + * + */ + +/** + * DBusTimeoutList implementation details. All fields + * are private. + * + */ +struct DBusTimeoutList +{ + DBusList *timeouts; /**< Timeout objects. */ + + DBusAddTimeoutFunction add_timeout_function; /**< Callback for adding a timeout. */ + DBusRemoveTimeoutFunction remove_timeout_function; /**< Callback for removing a timeout. */ + DBusTimeoutToggledFunction timeout_toggled_function; /**< Callback when timeout is enabled/disabled or changes interval */ + void *timeout_data; /**< Data for timeout callbacks */ + DBusFreeFunction timeout_free_data_function; /**< Free function for timeout callback data */ +}; + +/** + * Creates a new timeout list. Returns #NULL if insufficient + * memory exists. + * + * @returns the new timeout list, or #NULL on failure. + */ +DBusTimeoutList* +_dbus_timeout_list_new (void) +{ + DBusTimeoutList *timeout_list; + + timeout_list = dbus_new0 (DBusTimeoutList, 1); + if (timeout_list == NULL) + return NULL; + + return timeout_list; +} + +/** + * Frees a DBusTimeoutList. + * + * @param timeout_list the timeout list. + */ +void +_dbus_timeout_list_free (DBusTimeoutList *timeout_list) +{ + /* free timeout_data and remove timeouts as a side effect */ + _dbus_timeout_list_set_functions (timeout_list, + NULL, NULL, NULL, NULL, NULL); + + _dbus_list_clear_full (&timeout_list->timeouts, + (DBusFreeFunction) _dbus_timeout_unref); + + dbus_free (timeout_list); +} + +/** + * Sets the timeout functions. This function is the "backend" + * for dbus_connection_set_timeout_functions(). + * + * @param timeout_list the timeout list + * @param add_function the add timeout function. + * @param remove_function the remove timeout function. + * @param toggled_function toggle notify function, or #NULL + * @param data the data for those functions. + * @param free_data_function the function to free the data. + * @returns #FALSE if no memory + * + */ +dbus_bool_t +_dbus_timeout_list_set_functions (DBusTimeoutList *timeout_list, + DBusAddTimeoutFunction add_function, + DBusRemoveTimeoutFunction remove_function, + DBusTimeoutToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function) +{ + /* Add timeouts with the new function, failing on OOM */ + if (add_function != NULL) + { + DBusList *link; + + link = _dbus_list_get_first_link (&timeout_list->timeouts); + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (&timeout_list->timeouts, + link); + + if (!(* add_function) (link->data, data)) + { + /* remove it all again and return FALSE */ + DBusList *link2; + + link2 = _dbus_list_get_first_link (&timeout_list->timeouts); + while (link2 != link) + { + DBusList *next2 = _dbus_list_get_next_link (&timeout_list->timeouts, + link2); + + (* remove_function) (link2->data, data); + + link2 = next2; + } + + return FALSE; + } + + link = next; + } + } + + /* Remove all current timeouts from previous timeout handlers */ + + if (timeout_list->remove_timeout_function != NULL) + { + _dbus_list_foreach (&timeout_list->timeouts, + (DBusForeachFunction) timeout_list->remove_timeout_function, + timeout_list->timeout_data); + } + + if (timeout_list->timeout_free_data_function != NULL) + (* timeout_list->timeout_free_data_function) (timeout_list->timeout_data); + + timeout_list->add_timeout_function = add_function; + timeout_list->remove_timeout_function = remove_function; + timeout_list->timeout_toggled_function = toggled_function; + timeout_list->timeout_data = data; + timeout_list->timeout_free_data_function = free_data_function; + + return TRUE; +} + +/** + * Adds a new timeout to the timeout list, invoking the + * application DBusAddTimeoutFunction if appropriate. + * + * @param timeout_list the timeout list. + * @param timeout the timeout to add. + * @returns #TRUE on success, #FALSE If no memory. + */ +dbus_bool_t +_dbus_timeout_list_add_timeout (DBusTimeoutList *timeout_list, + DBusTimeout *timeout) +{ + if (!_dbus_list_append (&timeout_list->timeouts, timeout)) + return FALSE; + + _dbus_timeout_ref (timeout); + + if (timeout_list->add_timeout_function != NULL) + { + if (!(* timeout_list->add_timeout_function) (timeout, + timeout_list->timeout_data)) + { + _dbus_list_remove_last (&timeout_list->timeouts, timeout); + _dbus_timeout_unref (timeout); + return FALSE; + } + } + + return TRUE; +} + +/** + * Removes a timeout from the timeout list, invoking the + * application's DBusRemoveTimeoutFunction if appropriate. + * + * @param timeout_list the timeout list. + * @param timeout the timeout to remove. + */ +void +_dbus_timeout_list_remove_timeout (DBusTimeoutList *timeout_list, + DBusTimeout *timeout) +{ + if (!_dbus_list_remove (&timeout_list->timeouts, timeout)) + _dbus_assert_not_reached ("Nonexistent timeout was removed"); + + if (timeout_list->remove_timeout_function != NULL) + (* timeout_list->remove_timeout_function) (timeout, + timeout_list->timeout_data); + + _dbus_timeout_unref (timeout); +} + +/** + * Sets a timeout to the given enabled state, invoking the + * application's DBusTimeoutToggledFunction if appropriate. + * + * @param timeout_list the timeout list. + * @param timeout the timeout to toggle. + * @param enabled #TRUE to enable + */ +void +_dbus_timeout_list_toggle_timeout (DBusTimeoutList *timeout_list, + DBusTimeout *timeout, + dbus_bool_t enabled) +{ + enabled = !!enabled; + + if (enabled == timeout->enabled) + return; + + timeout->enabled = enabled; + + if (timeout_list->timeout_toggled_function != NULL) + (* timeout_list->timeout_toggled_function) (timeout, + timeout_list->timeout_data); +} + +/** + * Returns whether a timeout needs restart time counting in the event loop. + * + * @param timeout the DBusTimeout object + * @returns #TRUE if restart is needed + */ +dbus_bool_t +_dbus_timeout_needs_restart (DBusTimeout *timeout) +{ + return timeout->needs_restart; +} + +/** + * Mark timeout as restarted (setting timestamps is responsibility of the event + * loop). + * + * @param timeout the DBusTimeout object + */ +void +_dbus_timeout_restarted (DBusTimeout *timeout) +{ + timeout->needs_restart = FALSE; +} + +/** @} */ + +/** + * @defgroup DBusTimeout DBusTimeout + * @ingroup DBus + * @brief Object representing a timeout + * + * Types and functions related to DBusTimeout. A timeout + * represents a timeout that the main loop needs to monitor, + * as in Qt's QTimer or GLib's g_timeout_add(). + * + * Use dbus_connection_set_timeout_functions() or dbus_server_set_timeout_functions() + * to be notified when libdbus needs to add or remove timeouts. + * + * @{ + */ + + +/** + * @typedef DBusTimeout + * + * Opaque object representing a timeout. + */ + +/** + * Gets the timeout interval. The dbus_timeout_handle() + * should be called each time this interval elapses, + * starting after it elapses once. + * + * The interval may change during the life of the + * timeout; if so, the timeout will be disabled and + * re-enabled (calling the "timeout toggled function") + * to notify you of the change. + * + * @param timeout the DBusTimeout object. + * @returns the interval in milliseconds. + */ +int +dbus_timeout_get_interval (DBusTimeout *timeout) +{ + return timeout->interval; +} + +/** + * Gets data previously set with dbus_timeout_set_data() + * or #NULL if none. + * + * @param timeout the DBusTimeout object. + * @returns previously-set data. + */ +void* +dbus_timeout_get_data (DBusTimeout *timeout) +{ + return timeout->data; +} + +/** + * Sets data which can be retrieved with dbus_timeout_get_data(). + * Intended for use by the DBusAddTimeoutFunction and + * DBusRemoveTimeoutFunction to store their own data. For example with + * Qt you might store the QTimer for this timeout and with GLib + * you might store a g_timeout_add result id. + * + * @param timeout the DBusTimeout object. + * @param data the data. + * @param free_data_function function to be called to free the data. + */ +void +dbus_timeout_set_data (DBusTimeout *timeout, + void *data, + DBusFreeFunction free_data_function) +{ + if (timeout->free_data_function != NULL) + (* timeout->free_data_function) (timeout->data); + + timeout->data = data; + timeout->free_data_function = free_data_function; +} + +/** + * Calls the timeout handler for this timeout. + * This function should be called when the timeout + * occurs. + * + * If this function returns #FALSE, then there wasn't + * enough memory to handle the timeout. Typically just + * letting the timeout fire again next time it naturally + * times out is an adequate response to that problem, + * but you could try to do more if you wanted. + * + * @param timeout the DBusTimeout object. + * @returns #FALSE if there wasn't enough memory + */ +dbus_bool_t +dbus_timeout_handle (DBusTimeout *timeout) +{ + return (* timeout->handler) (timeout->handler_data); +} + + +/** + * Returns whether a timeout is enabled or not. If not + * enabled, it should not be polled by the main loop. + * + * @param timeout the DBusTimeout object + * @returns #TRUE if the timeout is enabled + */ +dbus_bool_t +dbus_timeout_get_enabled (DBusTimeout *timeout) +{ + return timeout->enabled; +} + +/** @} end public API docs */ diff --git a/src/3rdparty/libdbus/dbus/dbus-timeout.h b/src/3rdparty/libdbus/dbus/dbus-timeout.h new file mode 100644 index 00000000..73fdb952 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-timeout.h @@ -0,0 +1,84 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-timeout.h DBusTimeout internal interfaces + * + * Copyright (C) 2003 CodeFactory AB + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_TIMEOUT_H +#define DBUS_TIMEOUT_H + +#include <dbus/dbus-connection.h> +#include <dbus/dbus-internals.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusTimeoutInternals + * @{ + */ + +/* Public methods on DBusTimeout are in dbus-connection.h */ + +typedef struct DBusTimeoutList DBusTimeoutList; + +/** function to run when the timeout is handled */ +typedef dbus_bool_t (* DBusTimeoutHandler) (void *data); + +DBUS_PRIVATE_EXPORT +DBusTimeout* _dbus_timeout_new (int interval, + DBusTimeoutHandler handler, + void *data, + DBusFreeFunction free_data_function); +DBusTimeout* _dbus_timeout_ref (DBusTimeout *timeout); +DBUS_PRIVATE_EXPORT +void _dbus_timeout_unref (DBusTimeout *timeout); +DBUS_PRIVATE_EXPORT +void _dbus_timeout_restart (DBusTimeout *timeout, + int interval); +DBUS_PRIVATE_EXPORT +void _dbus_timeout_disable (DBusTimeout *timeout); + +DBusTimeoutList *_dbus_timeout_list_new (void); +void _dbus_timeout_list_free (DBusTimeoutList *timeout_list); +dbus_bool_t _dbus_timeout_list_set_functions (DBusTimeoutList *timeout_list, + DBusAddTimeoutFunction add_function, + DBusRemoveTimeoutFunction remove_function, + DBusTimeoutToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function); +dbus_bool_t _dbus_timeout_list_add_timeout (DBusTimeoutList *timeout_list, + DBusTimeout *timeout); +void _dbus_timeout_list_remove_timeout (DBusTimeoutList *timeout_list, + DBusTimeout *timeout); +void _dbus_timeout_list_toggle_timeout (DBusTimeoutList *timeout_list, + DBusTimeout *timeout, + dbus_bool_t enabled); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_timeout_needs_restart (DBusTimeout *timeout); +DBUS_PRIVATE_EXPORT +void _dbus_timeout_restarted (DBusTimeout *timeout); + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_TIMEOUT_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-transport-protected.h b/src/3rdparty/libdbus/dbus/dbus-transport-protected.h new file mode 100644 index 00000000..136c4031 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-transport-protected.h @@ -0,0 +1,148 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-transport-protected.h Used by subclasses of DBusTransport object (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2004 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_TRANSPORT_PROTECTED_H +#define DBUS_TRANSPORT_PROTECTED_H + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-errors.h> +#include <dbus/dbus-transport.h> +#include <dbus/dbus-message-internal.h> +#include <dbus/dbus-auth.h> +#include <dbus/dbus-resources.h> + +DBUS_BEGIN_DECLS + +typedef struct DBusTransportVTable DBusTransportVTable; + +/** + * The virtual table that must be implemented to + * create a new kind of transport. + */ +struct DBusTransportVTable +{ + void (* finalize) (DBusTransport *transport); + /**< The finalize method must free the transport. */ + + dbus_bool_t (* handle_watch) (DBusTransport *transport, + DBusWatch *watch, + unsigned int flags); + /**< The handle_watch method handles reading/writing + * data as indicated by the flags. + */ + + void (* disconnect) (DBusTransport *transport); + /**< Disconnect this transport. */ + + dbus_bool_t (* connection_set) (DBusTransport *transport); + /**< Called when transport->connection has been filled in */ + + void (* do_iteration) (DBusTransport *transport, + unsigned int flags, + int timeout_milliseconds); + /**< Called to do a single "iteration" (block on select/poll + * followed by reading or writing data). + */ + + void (* live_messages_changed) (DBusTransport *transport); + /**< Outstanding messages counter changed */ + + dbus_bool_t (* get_socket_fd) (DBusTransport *transport, + DBusSocket *fd_p); + /**< Get socket file descriptor */ +}; + +/** + * Object representing a transport such as a socket. + * A transport can shuttle messages from point A to point B, + * and is the backend for a #DBusConnection. + * + */ +struct DBusTransport +{ + int refcount; /**< Reference count. */ + + const DBusTransportVTable *vtable; /**< Virtual methods for this instance. */ + + DBusConnection *connection; /**< Connection owning this transport. */ + + DBusMessageLoader *loader; /**< Message-loading buffer. */ + + DBusAuth *auth; /**< Authentication conversation */ + + DBusCredentials *credentials; /**< Credentials of other end read from the socket */ + + long max_live_messages_size; /**< Max total size of received messages. */ + long max_live_messages_unix_fds; /**< Max total unix fds of received messages. */ + + DBusCounter *live_messages; /**< Counter for size/unix fds of all live messages. */ + + char *address; /**< Address of the server we are connecting to (#NULL for the server side of a transport) */ + + char *expected_guid; /**< GUID we expect the server to have, #NULL on server side or if we don't have an expectation */ + + DBusAllowUnixUserFunction unix_user_function; /**< Function for checking whether a user is authorized. */ + void *unix_user_data; /**< Data for unix_user_function */ + + DBusFreeFunction free_unix_user_data; /**< Function to free unix_user_data */ + + DBusAllowWindowsUserFunction windows_user_function; /**< Function for checking whether a user is authorized. */ + void *windows_user_data; /**< Data for windows_user_function */ + + DBusFreeFunction free_windows_user_data; /**< Function to free windows_user_data */ + + unsigned int disconnected : 1; /**< #TRUE if we are disconnected. */ + unsigned int authenticated : 1; /**< Cache of auth state; use _dbus_transport_peek_is_authenticated() to query value */ + unsigned int send_credentials_pending : 1; /**< #TRUE if we need to send credentials */ + unsigned int receive_credentials_pending : 1; /**< #TRUE if we need to receive credentials */ + unsigned int is_server : 1; /**< #TRUE if on the server side */ + unsigned int unused_bytes_recovered : 1; /**< #TRUE if we've recovered unused bytes from auth */ + unsigned int allow_anonymous : 1; /**< #TRUE if an anonymous client can connect */ +}; + +dbus_bool_t _dbus_transport_init_base (DBusTransport *transport, + const DBusTransportVTable *vtable, + const DBusString *server_guid, + const DBusString *address); +void _dbus_transport_finalize_base (DBusTransport *transport); + + +typedef enum +{ + DBUS_TRANSPORT_OPEN_NOT_HANDLED, /**< we aren't in charge of this address type */ + DBUS_TRANSPORT_OPEN_OK, /**< we set up the listen */ + DBUS_TRANSPORT_OPEN_BAD_ADDRESS, /**< malformed address */ + DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT /**< well-formed address but failed to set it up */ +} DBusTransportOpenResult; + +DBusTransportOpenResult _dbus_transport_open_platform_specific (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error); + +#define DBUS_TRANSPORT_CAN_SEND_UNIX_FD(x) \ + _dbus_auth_get_unix_fd_negotiated((x)->auth) + +DBUS_END_DECLS + +#endif /* DBUS_TRANSPORT_PROTECTED_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-transport-socket.c b/src/3rdparty/libdbus/dbus/dbus-transport-socket.c new file mode 100644 index 00000000..d3c2b914 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-transport-socket.c @@ -0,0 +1,1645 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-transport-socket.c Socket subclasses of DBusTransport + * + * Copyright (C) 2002, 2003, 2004, 2006 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#include <stdio.h> + +#include "dbus-internals.h" +#include "dbus-connection-internal.h" +#include "dbus-nonce.h" +#include "dbus-transport-socket.h" +#include "dbus-transport-protected.h" +#include "dbus-watch.h" +#include "dbus-credentials.h" + +/** + * @defgroup DBusTransportSocket DBusTransport implementations for sockets + * @ingroup DBusInternals + * @brief Implementation details of DBusTransport on sockets + * + * @{ + */ + +/** + * Opaque object representing a socket file descriptor transport. + */ +typedef struct DBusTransportSocket DBusTransportSocket; + +/** + * Implementation details of DBusTransportSocket. All members are private. + */ +struct DBusTransportSocket +{ + DBusTransport base; /**< Parent instance */ + DBusSocket fd; /**< File descriptor. */ + DBusWatch *read_watch; /**< Watch for readability. */ + DBusWatch *write_watch; /**< Watch for writability. */ + + int max_bytes_read_per_iteration; /**< To avoid blocking too long. */ + int max_bytes_written_per_iteration; /**< To avoid blocking too long. */ + + int message_bytes_written; /**< Number of bytes of current + * outgoing message that have + * been written. + */ + DBusString encoded_outgoing; /**< Encoded version of current + * outgoing message. + */ + DBusString encoded_incoming; /**< Encoded version of current + * incoming data. + */ +}; + +static void +free_watches (DBusTransport *transport) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + + _dbus_verbose ("start\n"); + + if (socket_transport->read_watch) + { + if (transport->connection) + _dbus_connection_remove_watch_unlocked (transport->connection, + socket_transport->read_watch); + _dbus_watch_invalidate (socket_transport->read_watch); + _dbus_watch_unref (socket_transport->read_watch); + socket_transport->read_watch = NULL; + } + + if (socket_transport->write_watch) + { + if (transport->connection) + _dbus_connection_remove_watch_unlocked (transport->connection, + socket_transport->write_watch); + _dbus_watch_invalidate (socket_transport->write_watch); + _dbus_watch_unref (socket_transport->write_watch); + socket_transport->write_watch = NULL; + } + + _dbus_verbose ("end\n"); +} + +static void +socket_finalize (DBusTransport *transport) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + + _dbus_verbose ("\n"); + + free_watches (transport); + + _dbus_string_free (&socket_transport->encoded_outgoing); + _dbus_string_free (&socket_transport->encoded_incoming); + + _dbus_transport_finalize_base (transport); + + _dbus_assert (socket_transport->read_watch == NULL); + _dbus_assert (socket_transport->write_watch == NULL); + + dbus_free (transport); +} + +static void +check_write_watch (DBusTransport *transport) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + dbus_bool_t needed; + + if (transport->connection == NULL) + return; + + if (transport->disconnected) + { + _dbus_assert (socket_transport->write_watch == NULL); + return; + } + + _dbus_transport_ref (transport); + + if (_dbus_transport_try_to_authenticate (transport)) + needed = _dbus_connection_has_messages_to_send_unlocked (transport->connection); + else + { + if (transport->send_credentials_pending) + needed = TRUE; + else + { + DBusAuthState auth_state; + + auth_state = _dbus_auth_do_work (transport->auth); + + /* If we need memory we install the write watch just in case, + * if there's no need for it, it will get de-installed + * next time we try reading. + */ + if (auth_state == DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND || + auth_state == DBUS_AUTH_STATE_WAITING_FOR_MEMORY) + needed = TRUE; + else + needed = FALSE; + } + } + + _dbus_verbose ("check_write_watch(): needed = %d on connection %p watch %p fd = %" DBUS_SOCKET_FORMAT " outgoing messages exist %d\n", + needed, transport->connection, socket_transport->write_watch, + _dbus_socket_printable (socket_transport->fd), + _dbus_connection_has_messages_to_send_unlocked (transport->connection)); + + _dbus_connection_toggle_watch_unlocked (transport->connection, + socket_transport->write_watch, + needed); + + _dbus_transport_unref (transport); +} + +static void +check_read_watch (DBusTransport *transport) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + dbus_bool_t need_read_watch; + + _dbus_verbose ("fd = %" DBUS_SOCKET_FORMAT "\n", + _dbus_socket_printable (socket_transport->fd)); + + if (transport->connection == NULL) + return; + + if (transport->disconnected) + { + _dbus_assert (socket_transport->read_watch == NULL); + return; + } + + _dbus_transport_ref (transport); + + if (_dbus_transport_try_to_authenticate (transport)) + need_read_watch = + (_dbus_counter_get_size_value (transport->live_messages) < transport->max_live_messages_size) && + (_dbus_counter_get_unix_fd_value (transport->live_messages) < transport->max_live_messages_unix_fds); + else + { + if (transport->receive_credentials_pending) + need_read_watch = TRUE; + else + { + /* The reason to disable need_read_watch when not WAITING_FOR_INPUT + * is to avoid spinning on the file descriptor when we're waiting + * to write or for some other part of the auth process + */ + DBusAuthState auth_state; + + auth_state = _dbus_auth_do_work (transport->auth); + + /* If we need memory we install the read watch just in case, + * if there's no need for it, it will get de-installed + * next time we try reading. If we're authenticated we + * install it since we normally have it installed while + * authenticated. + */ + if (auth_state == DBUS_AUTH_STATE_WAITING_FOR_INPUT || + auth_state == DBUS_AUTH_STATE_WAITING_FOR_MEMORY || + auth_state == DBUS_AUTH_STATE_AUTHENTICATED) + need_read_watch = TRUE; + else + need_read_watch = FALSE; + } + } + + _dbus_verbose (" setting read watch enabled = %d\n", need_read_watch); + _dbus_connection_toggle_watch_unlocked (transport->connection, + socket_transport->read_watch, + need_read_watch); + + _dbus_transport_unref (transport); +} + +static void +do_io_error (DBusTransport *transport) +{ + _dbus_transport_ref (transport); + _dbus_transport_disconnect (transport); + _dbus_transport_unref (transport); +} + +/* return value is whether we successfully read any new data. */ +static dbus_bool_t +read_data_into_auth (DBusTransport *transport, + dbus_bool_t *oom) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + DBusString *buffer; + int bytes_read; + int saved_errno; + + *oom = FALSE; + + _dbus_auth_get_buffer (transport->auth, &buffer); + + bytes_read = _dbus_read_socket (socket_transport->fd, + buffer, socket_transport->max_bytes_read_per_iteration); + saved_errno = _dbus_save_socket_errno (); + + _dbus_auth_return_buffer (transport->auth, buffer); + + if (bytes_read > 0) + { + _dbus_verbose (" read %d bytes in auth phase\n", bytes_read); + + return TRUE; + } + else if (bytes_read < 0) + { + /* EINTR already handled for us */ + + if (_dbus_get_is_errno_enomem (saved_errno)) + { + *oom = TRUE; + } + else if (_dbus_get_is_errno_eagain_or_ewouldblock (saved_errno)) + ; /* do nothing, just return FALSE below */ + else + { + _dbus_verbose ("Error reading from remote app: %s\n", + _dbus_strerror (saved_errno)); + do_io_error (transport); + } + + return FALSE; + } + else + { + _dbus_assert (bytes_read == 0); + + _dbus_verbose ("Disconnected from remote app\n"); + do_io_error (transport); + + return FALSE; + } +} + +/* Return value is whether we successfully wrote any bytes */ +static dbus_bool_t +write_data_from_auth (DBusTransport *transport) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + int bytes_written; + int saved_errno; + const DBusString *buffer; + + if (!_dbus_auth_get_bytes_to_send (transport->auth, + &buffer)) + return FALSE; + + bytes_written = _dbus_write_socket (socket_transport->fd, + buffer, + 0, _dbus_string_get_length (buffer)); + saved_errno = _dbus_save_socket_errno (); + + if (bytes_written > 0) + { + _dbus_auth_bytes_sent (transport->auth, bytes_written); + return TRUE; + } + else if (bytes_written < 0) + { + /* EINTR already handled for us */ + + if (_dbus_get_is_errno_eagain_or_ewouldblock (saved_errno)) + ; + else + { + _dbus_verbose ("Error writing to remote app: %s\n", + _dbus_strerror (saved_errno)); + do_io_error (transport); + } + } + + return FALSE; +} + +/* FALSE on OOM */ +static dbus_bool_t +exchange_credentials (DBusTransport *transport, + dbus_bool_t do_reading, + dbus_bool_t do_writing) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + DBusError error = DBUS_ERROR_INIT; + + _dbus_verbose ("exchange_credentials: do_reading = %d, do_writing = %d\n", + do_reading, do_writing); + + if (do_writing && transport->send_credentials_pending) + { + if (_dbus_send_credentials_socket (socket_transport->fd, + &error)) + { + transport->send_credentials_pending = FALSE; + } + else + { + _dbus_verbose ("Failed to write credentials: %s\n", error.message); + dbus_error_free (&error); + do_io_error (transport); + } + } + + if (do_reading && transport->receive_credentials_pending) + { + /* FIXME this can fail due to IO error _or_ OOM, broken + * (somewhat tricky to fix since the OOM error can be set after + * we already read the credentials byte, so basically we need to + * separate reading the byte and storing it in the + * transport->credentials). Does not really matter for now + * because storing in credentials never actually fails on unix. + */ + if (_dbus_read_credentials_socket (socket_transport->fd, + transport->credentials, + &error)) + { + transport->receive_credentials_pending = FALSE; + } + else + { + _dbus_verbose ("Failed to read credentials %s\n", error.message); + dbus_error_free (&error); + do_io_error (transport); + } + } + + if (!(transport->send_credentials_pending || + transport->receive_credentials_pending)) + { + if (!_dbus_auth_set_credentials (transport->auth, + transport->credentials)) + return FALSE; + } + + return TRUE; +} + +static dbus_bool_t +do_authentication (DBusTransport *transport, + dbus_bool_t do_reading, + dbus_bool_t do_writing, + dbus_bool_t *auth_completed) +{ + dbus_bool_t oom; + dbus_bool_t orig_auth_state; + + oom = FALSE; + + orig_auth_state = _dbus_transport_try_to_authenticate (transport); + + /* This is essential to avoid the check_write_watch() at the end, + * we don't want to add a write watch in do_iteration before + * we try writing and get EAGAIN + */ + if (orig_auth_state) + { + if (auth_completed) + *auth_completed = FALSE; + return TRUE; + } + + _dbus_transport_ref (transport); + + while (!_dbus_transport_try_to_authenticate (transport) && + _dbus_transport_get_is_connected (transport)) + { + if (!exchange_credentials (transport, do_reading, do_writing)) + { + /* OOM */ + oom = TRUE; + goto out; + } + + if (transport->send_credentials_pending || + transport->receive_credentials_pending) + { + _dbus_verbose ("send_credentials_pending = %d receive_credentials_pending = %d\n", + transport->send_credentials_pending, + transport->receive_credentials_pending); + goto out; + } + +#define TRANSPORT_SIDE(t) ((t)->is_server ? "server" : "client") + switch (_dbus_auth_do_work (transport->auth)) + { + case DBUS_AUTH_STATE_WAITING_FOR_INPUT: + _dbus_verbose (" %s auth state: waiting for input\n", + TRANSPORT_SIDE (transport)); + if (!do_reading || !read_data_into_auth (transport, &oom)) + goto out; + break; + + case DBUS_AUTH_STATE_WAITING_FOR_MEMORY: + _dbus_verbose (" %s auth state: waiting for memory\n", + TRANSPORT_SIDE (transport)); + oom = TRUE; + goto out; + break; + + case DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND: + _dbus_verbose (" %s auth state: bytes to send\n", + TRANSPORT_SIDE (transport)); + if (!do_writing || !write_data_from_auth (transport)) + goto out; + break; + + case DBUS_AUTH_STATE_NEED_DISCONNECT: + _dbus_verbose (" %s auth state: need to disconnect\n", + TRANSPORT_SIDE (transport)); + do_io_error (transport); + break; + + case DBUS_AUTH_STATE_AUTHENTICATED: + _dbus_verbose (" %s auth state: authenticated\n", + TRANSPORT_SIDE (transport)); + break; + + case DBUS_AUTH_STATE_INVALID: + /* fall through */ + default: + _dbus_assert_not_reached ("invalid auth state"); + } + } + + out: + if (auth_completed) + *auth_completed = (orig_auth_state != _dbus_transport_try_to_authenticate (transport)); + + check_read_watch (transport); + check_write_watch (transport); + _dbus_transport_unref (transport); + + if (oom) + return FALSE; + else + return TRUE; +} + +/* returns false on oom */ +static dbus_bool_t +do_writing (DBusTransport *transport) +{ + int total; + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + dbus_bool_t oom; + + /* No messages without authentication! */ + if (!_dbus_transport_try_to_authenticate (transport)) + { + _dbus_verbose ("Not authenticated, not writing anything\n"); + return TRUE; + } + + if (transport->disconnected) + { + _dbus_verbose ("Not connected, not writing anything\n"); + return TRUE; + } + +#if 1 + _dbus_verbose ("do_writing(), have_messages = %d, fd = %" DBUS_SOCKET_FORMAT "\n", + _dbus_connection_has_messages_to_send_unlocked (transport->connection), + _dbus_socket_printable (socket_transport->fd)); +#endif + + oom = FALSE; + total = 0; + + while (!transport->disconnected && + _dbus_connection_has_messages_to_send_unlocked (transport->connection)) + { + int bytes_written; + DBusMessage *message; + const DBusString *header; + const DBusString *body; + int header_len, body_len; + int total_bytes_to_write; + int saved_errno; + + if (total > socket_transport->max_bytes_written_per_iteration) + { + _dbus_verbose ("%d bytes exceeds %d bytes written per iteration, returning\n", + total, socket_transport->max_bytes_written_per_iteration); + goto out; + } + + message = _dbus_connection_get_message_to_send (transport->connection); + _dbus_assert (message != NULL); + dbus_message_lock (message); + +#if 0 + _dbus_verbose ("writing message %p\n", message); +#endif + + _dbus_message_get_network_data (message, + &header, &body); + + header_len = _dbus_string_get_length (header); + body_len = _dbus_string_get_length (body); + + if (_dbus_auth_needs_encoding (transport->auth)) + { + /* Does fd passing even make sense with encoded data? */ + _dbus_assert(!DBUS_TRANSPORT_CAN_SEND_UNIX_FD(transport)); + + if (_dbus_string_get_length (&socket_transport->encoded_outgoing) == 0) + { + if (!_dbus_auth_encode_data (transport->auth, + header, &socket_transport->encoded_outgoing)) + { + oom = TRUE; + goto out; + } + + if (!_dbus_auth_encode_data (transport->auth, + body, &socket_transport->encoded_outgoing)) + { + _dbus_string_set_length (&socket_transport->encoded_outgoing, 0); + oom = TRUE; + goto out; + } + } + + total_bytes_to_write = _dbus_string_get_length (&socket_transport->encoded_outgoing); + +#if 0 + _dbus_verbose ("encoded message is %d bytes\n", + total_bytes_to_write); +#endif + + bytes_written = + _dbus_write_socket (socket_transport->fd, + &socket_transport->encoded_outgoing, + socket_transport->message_bytes_written, + total_bytes_to_write - socket_transport->message_bytes_written); + saved_errno = _dbus_save_socket_errno (); + } + else + { + total_bytes_to_write = header_len + body_len; + +#if 0 + _dbus_verbose ("message is %d bytes\n", + total_bytes_to_write); +#endif + +#ifdef HAVE_UNIX_FD_PASSING + if (socket_transport->message_bytes_written <= 0 && DBUS_TRANSPORT_CAN_SEND_UNIX_FD(transport)) + { + /* Send the fds along with the first byte of the message */ + const int *unix_fds; + unsigned n; + + _dbus_message_get_unix_fds(message, &unix_fds, &n); + + bytes_written = + _dbus_write_socket_with_unix_fds_two (socket_transport->fd, + header, + socket_transport->message_bytes_written, + header_len - socket_transport->message_bytes_written, + body, + 0, body_len, + unix_fds, + n); + saved_errno = _dbus_save_socket_errno (); + + if (bytes_written > 0 && n > 0) + _dbus_verbose("Wrote %i unix fds\n", n); + } + else +#endif + { + if (socket_transport->message_bytes_written < header_len) + { + bytes_written = + _dbus_write_socket_two (socket_transport->fd, + header, + socket_transport->message_bytes_written, + header_len - socket_transport->message_bytes_written, + body, + 0, body_len); + } + else + { + bytes_written = + _dbus_write_socket (socket_transport->fd, + body, + (socket_transport->message_bytes_written - header_len), + body_len - + (socket_transport->message_bytes_written - header_len)); + } + + saved_errno = _dbus_save_socket_errno (); + } + } + + if (bytes_written < 0) + { + /* EINTR already handled for us */ + + /* If the other end closed the socket with close() or shutdown(), we + * receive EPIPE here but we must not close the socket yet: there + * might still be some data to read. See: + * http://lists.freedesktop.org/archives/dbus/2008-March/009526.html + */ + + if (_dbus_get_is_errno_eagain_or_ewouldblock (saved_errno) || _dbus_get_is_errno_epipe (saved_errno)) + goto out; + + /* Since Linux commit 25888e (from 2.6.37-rc4, Nov 2010), sendmsg() + * on Unix sockets returns -1 errno=ETOOMANYREFS when the passfd + * mechanism (SCM_RIGHTS) is used recursively with a recursion level + * of maximum 4. The kernel does not have an API to check whether + * the passed fds can be forwarded and it can change asynchronously. + * See: + * https://bugs.freedesktop.org/show_bug.cgi?id=80163 + */ + + else if (_dbus_get_is_errno_etoomanyrefs (saved_errno)) + { + /* We only send fds in the first byte of the message. + * ETOOMANYREFS cannot happen after. + */ + _dbus_assert (socket_transport->message_bytes_written == 0); + + _dbus_verbose (" discard message of %d bytes due to ETOOMANYREFS\n", + total_bytes_to_write); + + socket_transport->message_bytes_written = 0; + _dbus_string_set_length (&socket_transport->encoded_outgoing, 0); + _dbus_string_compact (&socket_transport->encoded_outgoing, 2048); + + /* The message was not actually sent but it needs to be removed + * from the outgoing queue + */ + _dbus_connection_message_sent_unlocked (transport->connection, + message); + } + else + { + _dbus_verbose ("Error writing to remote app: %s\n", + _dbus_strerror (saved_errno)); + do_io_error (transport); + goto out; + } + } + else + { + _dbus_verbose (" wrote %d bytes of %d\n", bytes_written, + total_bytes_to_write); + + total += bytes_written; + socket_transport->message_bytes_written += bytes_written; + + _dbus_assert (socket_transport->message_bytes_written <= + total_bytes_to_write); + + if (socket_transport->message_bytes_written == total_bytes_to_write) + { + socket_transport->message_bytes_written = 0; + _dbus_string_set_length (&socket_transport->encoded_outgoing, 0); + _dbus_string_compact (&socket_transport->encoded_outgoing, 2048); + + _dbus_connection_message_sent_unlocked (transport->connection, + message); + } + } + } + + out: + if (oom) + return FALSE; + else + return TRUE; +} + +/* returns false on out-of-memory */ +static dbus_bool_t +do_reading (DBusTransport *transport) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + DBusString *buffer; + int bytes_read; + int total; + dbus_bool_t oom; + int saved_errno; + + _dbus_verbose ("fd = %" DBUS_SOCKET_FORMAT "\n", + _dbus_socket_printable (socket_transport->fd)); + + /* No messages without authentication! */ + if (!_dbus_transport_try_to_authenticate (transport)) + return TRUE; + + oom = FALSE; + + total = 0; + + again: + + /* See if we've exceeded max messages and need to disable reading */ + check_read_watch (transport); + + if (total > socket_transport->max_bytes_read_per_iteration) + { + _dbus_verbose ("%d bytes exceeds %d bytes read per iteration, returning\n", + total, socket_transport->max_bytes_read_per_iteration); + goto out; + } + + _dbus_assert (socket_transport->read_watch != NULL || + transport->disconnected); + + if (transport->disconnected) + goto out; + + if (!dbus_watch_get_enabled (socket_transport->read_watch)) + return TRUE; + + if (_dbus_auth_needs_decoding (transport->auth)) + { + /* Does fd passing even make sense with encoded data? */ + _dbus_assert(!DBUS_TRANSPORT_CAN_SEND_UNIX_FD(transport)); + + if (_dbus_string_get_length (&socket_transport->encoded_incoming) > 0) + bytes_read = _dbus_string_get_length (&socket_transport->encoded_incoming); + else + bytes_read = _dbus_read_socket (socket_transport->fd, + &socket_transport->encoded_incoming, + socket_transport->max_bytes_read_per_iteration); + + saved_errno = _dbus_save_socket_errno (); + + _dbus_assert (_dbus_string_get_length (&socket_transport->encoded_incoming) == + bytes_read); + + if (bytes_read > 0) + { + _dbus_message_loader_get_buffer (transport->loader, + &buffer, + NULL, + NULL); + + if (!_dbus_auth_decode_data (transport->auth, + &socket_transport->encoded_incoming, + buffer)) + { + _dbus_verbose ("Out of memory decoding incoming data\n"); + _dbus_message_loader_return_buffer (transport->loader, + buffer); + + oom = TRUE; + goto out; + } + + _dbus_message_loader_return_buffer (transport->loader, + buffer); + + _dbus_string_set_length (&socket_transport->encoded_incoming, 0); + _dbus_string_compact (&socket_transport->encoded_incoming, 2048); + } + } + else + { + int max_to_read = DBUS_MAXIMUM_MESSAGE_LENGTH; + dbus_bool_t may_read_unix_fds = TRUE; + + _dbus_message_loader_get_buffer (transport->loader, + &buffer, + &max_to_read, + &may_read_unix_fds); + + if (max_to_read > socket_transport->max_bytes_read_per_iteration) + max_to_read = socket_transport->max_bytes_read_per_iteration; + +#ifdef HAVE_UNIX_FD_PASSING + if (DBUS_TRANSPORT_CAN_SEND_UNIX_FD(transport) && may_read_unix_fds) + { + int *fds; + unsigned int n_fds; + + if (!_dbus_message_loader_get_unix_fds(transport->loader, &fds, &n_fds)) + { + _dbus_verbose ("Out of memory reading file descriptors\n"); + _dbus_message_loader_return_buffer (transport->loader, buffer); + oom = TRUE; + goto out; + } + + bytes_read = _dbus_read_socket_with_unix_fds(socket_transport->fd, + buffer, + max_to_read, + fds, &n_fds); + saved_errno = _dbus_save_socket_errno (); + + if (bytes_read >= 0 && n_fds > 0) + _dbus_verbose("Read %i unix fds\n", n_fds); + + _dbus_message_loader_return_unix_fds(transport->loader, fds, bytes_read < 0 ? 0 : n_fds); + } + else +#endif + { + bytes_read = _dbus_read_socket (socket_transport->fd, + buffer, max_to_read); + saved_errno = _dbus_save_socket_errno (); + } + + _dbus_message_loader_return_buffer (transport->loader, + buffer); + } + + if (bytes_read < 0) + { + /* EINTR already handled for us */ + + if (_dbus_get_is_errno_enomem (saved_errno)) + { + _dbus_verbose ("Out of memory in read()/do_reading()\n"); + oom = TRUE; + goto out; + } + else if (_dbus_get_is_errno_eagain_or_ewouldblock (saved_errno)) + goto out; + else + { + _dbus_verbose ("Error reading from remote app: %s\n", + _dbus_strerror (saved_errno)); + do_io_error (transport); + goto out; + } + } + else if (bytes_read == 0) + { + _dbus_verbose ("Disconnected from remote app\n"); + do_io_error (transport); + goto out; + } + else + { + _dbus_verbose (" read %d bytes\n", bytes_read); + + total += bytes_read; + + if (!_dbus_transport_queue_messages (transport)) + { + oom = TRUE; + _dbus_verbose (" out of memory when queueing messages we just read in the transport\n"); + goto out; + } + + /* Try reading more data until we get EAGAIN and return, or + * exceed max bytes per iteration. If in blocking mode of + * course we'll block instead of returning. + */ + goto again; + } + + out: + if (oom) + return FALSE; + else + return TRUE; +} + +static dbus_bool_t +unix_error_with_read_to_come (DBusTransport *itransport, + DBusWatch *watch, + unsigned int flags) +{ + DBusTransportSocket *transport = (DBusTransportSocket *) itransport; + + if (!(flags & DBUS_WATCH_HANGUP || flags & DBUS_WATCH_ERROR)) + return FALSE; + + /* If we have a read watch enabled ... + we -might have data incoming ... => handle the HANGUP there */ + if (watch != transport->read_watch && + _dbus_watch_get_enabled (transport->read_watch)) + return FALSE; + + return TRUE; +} + +static dbus_bool_t +socket_handle_watch (DBusTransport *transport, + DBusWatch *watch, + unsigned int flags) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + + _dbus_assert (watch == socket_transport->read_watch || + watch == socket_transport->write_watch); + _dbus_assert (watch != NULL); + + /* If we hit an error here on a write watch, don't disconnect the transport yet because data can + * still be in the buffer and do_reading may need several iteration to read + * it all (because of its max_bytes_read_per_iteration limit). + */ + if (!(flags & DBUS_WATCH_READABLE) && unix_error_with_read_to_come (transport, watch, flags)) + { + _dbus_verbose ("Hang up or error on watch\n"); + _dbus_transport_disconnect (transport); + return TRUE; + } + + if (watch == socket_transport->read_watch && + (flags & DBUS_WATCH_READABLE)) + { + dbus_bool_t auth_finished; +#if 1 + _dbus_verbose ("handling read watch %p flags = %x\n", + watch, flags); +#endif + if (!do_authentication (transport, TRUE, FALSE, &auth_finished)) + return FALSE; + + /* We don't want to do a read immediately following + * a successful authentication. This is so we + * have a chance to propagate the authentication + * state further up. Specifically, we need to + * process any pending data from the auth object. + */ + if (!auth_finished) + { + if (!do_reading (transport)) + { + _dbus_verbose ("no memory to read\n"); + return FALSE; + } + } + else + { + _dbus_verbose ("Not reading anything since we just completed the authentication\n"); + } + } + else if (watch == socket_transport->write_watch && + (flags & DBUS_WATCH_WRITABLE)) + { +#if 1 + _dbus_verbose ("handling write watch, have_outgoing_messages = %d\n", + _dbus_connection_has_messages_to_send_unlocked (transport->connection)); +#endif + if (!do_authentication (transport, FALSE, TRUE, NULL)) + return FALSE; + + if (!do_writing (transport)) + { + _dbus_verbose ("no memory to write\n"); + return FALSE; + } + + /* See if we still need the write watch */ + check_write_watch (transport); + } +#ifdef DBUS_ENABLE_VERBOSE_MODE + else + { + if (watch == socket_transport->read_watch) + _dbus_verbose ("asked to handle read watch with non-read condition 0x%x\n", + flags); + else if (watch == socket_transport->write_watch) + _dbus_verbose ("asked to handle write watch with non-write condition 0x%x\n", + flags); + else + _dbus_verbose ("asked to handle watch %p on fd %" DBUS_SOCKET_FORMAT " that we don't recognize\n", + watch, _dbus_socket_printable (_dbus_watch_get_socket (watch))); + } +#endif /* DBUS_ENABLE_VERBOSE_MODE */ + + return TRUE; +} + +static void +socket_disconnect (DBusTransport *transport) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + + _dbus_verbose ("\n"); + + free_watches (transport); + + _dbus_close_socket (&socket_transport->fd, NULL); +} + +static dbus_bool_t +socket_connection_set (DBusTransport *transport) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + + _dbus_watch_set_handler (socket_transport->write_watch, + _dbus_connection_handle_watch, + transport->connection, NULL); + + _dbus_watch_set_handler (socket_transport->read_watch, + _dbus_connection_handle_watch, + transport->connection, NULL); + + if (!_dbus_connection_add_watch_unlocked (transport->connection, + socket_transport->write_watch)) + return FALSE; + + if (!_dbus_connection_add_watch_unlocked (transport->connection, + socket_transport->read_watch)) + { + _dbus_connection_remove_watch_unlocked (transport->connection, + socket_transport->write_watch); + return FALSE; + } + + check_read_watch (transport); + check_write_watch (transport); + + return TRUE; +} + +/** + * @todo We need to have a way to wake up the select sleep if + * a new iteration request comes in with a flag (read/write) that + * we're not currently serving. Otherwise a call that just reads + * could block a write call forever (if there are no incoming + * messages). + */ +static void +socket_do_iteration (DBusTransport *transport, + unsigned int flags, + int timeout_milliseconds) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + DBusPollFD poll_fd; + int poll_res; + int poll_timeout; + + _dbus_verbose (" iteration flags = %s%s timeout = %d read_watch = %p write_watch = %p fd = %" DBUS_SOCKET_FORMAT "\n", + flags & DBUS_ITERATION_DO_READING ? "read" : "", + flags & DBUS_ITERATION_DO_WRITING ? "write" : "", + timeout_milliseconds, + socket_transport->read_watch, + socket_transport->write_watch, + _dbus_socket_printable (socket_transport->fd)); + + /* the passed in DO_READING/DO_WRITING flags indicate whether to + * read/write messages, but regardless of those we may need to block + * for reading/writing to do auth. But if we do reading for auth, + * we don't want to read any messages yet if not given DO_READING. + */ + + poll_fd.fd = _dbus_socket_get_pollable (socket_transport->fd); + poll_fd.events = 0; + + if (_dbus_transport_try_to_authenticate (transport)) + { + /* This is kind of a hack; if we have stuff to write, then try + * to avoid the poll. This is probably about a 5% speedup on an + * echo client/server. + * + * If both reading and writing were requested, we want to avoid this + * since it could have funky effects: + * - both ends spinning waiting for the other one to read + * data so they can finish writing + * - prioritizing all writing ahead of reading + */ + if ((flags & DBUS_ITERATION_DO_WRITING) && + !(flags & (DBUS_ITERATION_DO_READING | DBUS_ITERATION_BLOCK)) && + !transport->disconnected && + _dbus_connection_has_messages_to_send_unlocked (transport->connection)) + { + do_writing (transport); + + if (transport->disconnected || + !_dbus_connection_has_messages_to_send_unlocked (transport->connection)) + goto out; + } + + /* If we get here, we decided to do the poll() after all */ + _dbus_assert (socket_transport->read_watch); + if (flags & DBUS_ITERATION_DO_READING) + poll_fd.events |= _DBUS_POLLIN; + + _dbus_assert (socket_transport->write_watch); + if (flags & DBUS_ITERATION_DO_WRITING) + poll_fd.events |= _DBUS_POLLOUT; + } + else + { + DBusAuthState auth_state; + + auth_state = _dbus_auth_do_work (transport->auth); + + if (transport->receive_credentials_pending || + auth_state == DBUS_AUTH_STATE_WAITING_FOR_INPUT) + poll_fd.events |= _DBUS_POLLIN; + + if (transport->send_credentials_pending || + auth_state == DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND) + poll_fd.events |= _DBUS_POLLOUT; + } + + if (poll_fd.events) + { + int saved_errno; + + if (flags & DBUS_ITERATION_BLOCK) + poll_timeout = timeout_milliseconds; + else + poll_timeout = 0; + + /* For blocking selects we drop the connection lock here + * to avoid blocking out connection access during a potentially + * indefinite blocking call. The io path is still protected + * by the io_path_cond condvar, so we won't reenter this. + */ + if (flags & DBUS_ITERATION_BLOCK) + { + _dbus_verbose ("unlock pre poll\n"); + _dbus_connection_unlock (transport->connection); + } + + again: + poll_res = _dbus_poll (&poll_fd, 1, poll_timeout); + saved_errno = _dbus_save_socket_errno (); + + if (poll_res < 0 && _dbus_get_is_errno_eintr (saved_errno)) + goto again; + + if (flags & DBUS_ITERATION_BLOCK) + { + _dbus_verbose ("lock post poll\n"); + _dbus_connection_lock (transport->connection); + } + + if (poll_res >= 0) + { + if (poll_res == 0) + poll_fd.revents = 0; /* some concern that posix does not guarantee this; + * valgrind flags it as an error. though it probably + * is guaranteed on linux at least. + */ + + if (poll_fd.revents & _DBUS_POLLERR) + do_io_error (transport); + else + { + dbus_bool_t need_read = (poll_fd.revents & _DBUS_POLLIN) > 0; + dbus_bool_t need_write = (poll_fd.revents & _DBUS_POLLOUT) > 0; + dbus_bool_t authentication_completed; + + _dbus_verbose ("in iteration, need_read=%d need_write=%d\n", + need_read, need_write); + do_authentication (transport, need_read, need_write, + &authentication_completed); + + /* See comment in socket_handle_watch. */ + if (authentication_completed) + goto out; + + if (need_read && (flags & DBUS_ITERATION_DO_READING)) + do_reading (transport); + if (need_write && (flags & DBUS_ITERATION_DO_WRITING)) + do_writing (transport); + } + } + else + { + _dbus_verbose ("Error from _dbus_poll(): %s\n", + _dbus_strerror (saved_errno)); + } + } + + + out: + /* We need to install the write watch only if we did not + * successfully write everything. Note we need to be careful that we + * don't call check_write_watch *before* do_writing, since it's + * inefficient to add the write watch, and we can avoid it most of + * the time since we can write immediately. + * + * However, we MUST always call check_write_watch(); DBusConnection code + * relies on the fact that running an iteration will notice that + * messages are pending. + */ + check_write_watch (transport); + + _dbus_verbose (" ... leaving do_iteration()\n"); +} + +static void +socket_live_messages_changed (DBusTransport *transport) +{ + /* See if we should look for incoming messages again */ + check_read_watch (transport); +} + + +static dbus_bool_t +socket_get_socket_fd (DBusTransport *transport, + DBusSocket *fd_p) +{ + DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; + + *fd_p = socket_transport->fd; + + return TRUE; +} + +static const DBusTransportVTable socket_vtable = { + socket_finalize, + socket_handle_watch, + socket_disconnect, + socket_connection_set, + socket_do_iteration, + socket_live_messages_changed, + socket_get_socket_fd +}; + +/** + * Creates a new transport for the given socket file descriptor. The file + * descriptor must be nonblocking (use _dbus_set_fd_nonblocking() to + * make it so). This function is shared by various transports that + * boil down to a full duplex file descriptor. + * + * @param fd the file descriptor. + * @param server_guid non-#NULL if this transport is on the server side of a connection + * @param address the transport's address + * @returns the new transport, or #NULL if no memory. + */ +DBusTransport* +_dbus_transport_new_for_socket (DBusSocket fd, + const DBusString *server_guid, + const DBusString *address) +{ + DBusTransportSocket *socket_transport; + DBusString invalid = _DBUS_STRING_INIT_INVALID; + + socket_transport = dbus_new0 (DBusTransportSocket, 1); + if (socket_transport == NULL) + return NULL; + + /* So they can be "freed" without error */ + socket_transport->encoded_outgoing = invalid; + socket_transport->encoded_incoming = invalid; + + if (!_dbus_string_init (&socket_transport->encoded_outgoing)) + goto failed; + + if (!_dbus_string_init (&socket_transport->encoded_incoming)) + goto failed; + + socket_transport->write_watch = _dbus_watch_new (_dbus_socket_get_pollable (fd), + DBUS_WATCH_WRITABLE, + FALSE, + NULL, NULL, NULL); + if (socket_transport->write_watch == NULL) + goto failed; + + socket_transport->read_watch = _dbus_watch_new (_dbus_socket_get_pollable (fd), + DBUS_WATCH_READABLE, + FALSE, + NULL, NULL, NULL); + if (socket_transport->read_watch == NULL) + goto failed; + + if (!_dbus_transport_init_base (&socket_transport->base, + &socket_vtable, + server_guid, address)) + goto failed; + +#ifdef HAVE_UNIX_FD_PASSING + _dbus_auth_set_unix_fd_possible(socket_transport->base.auth, _dbus_socket_can_pass_unix_fd(fd)); +#endif + + socket_transport->fd = fd; + socket_transport->message_bytes_written = 0; + + /* These values should probably be tunable or something. */ + socket_transport->max_bytes_read_per_iteration = 2048; + socket_transport->max_bytes_written_per_iteration = 2048; + + return (DBusTransport*) socket_transport; + +failed: + if (socket_transport->read_watch != NULL) + { + _dbus_watch_invalidate (socket_transport->read_watch); + _dbus_watch_unref (socket_transport->read_watch); + } + + if (socket_transport->write_watch != NULL) + { + _dbus_watch_invalidate (socket_transport->write_watch); + _dbus_watch_unref (socket_transport->write_watch); + } + + _dbus_string_free (&socket_transport->encoded_incoming); + _dbus_string_free (&socket_transport->encoded_outgoing); + dbus_free (socket_transport); + return NULL; +} + +/** + * Creates a new transport for the given hostname and port. + * If host is NULL, it will default to localhost + * + * @param host the host to connect to + * @param port the port to connect to + * @param family the address family to connect to + * @param noncefile path to nonce file + * @param error location to store reason for failure. + * @returns a new transport, or #NULL on failure. + */ +DBusTransport* +_dbus_transport_new_for_tcp_socket (const char *host, + const char *port, + const char *family, + const char *noncefile, + DBusError *error) +{ + DBusSocket fd; + DBusTransport *transport; + DBusString address; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&address)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + if (host == NULL) + host = "localhost"; + + if (!_dbus_string_append (&address, noncefile ? "nonce-tcp:" : "tcp:")) + goto error; + + if (!_dbus_string_append (&address, "host=") || + !_dbus_string_append (&address, host)) + goto error; + + if (!_dbus_string_append (&address, ",port=") || + !_dbus_string_append (&address, port)) + goto error; + + if (family != NULL && + (!_dbus_string_append (&address, ",family=") || + !_dbus_string_append (&address, family))) + goto error; + + if (noncefile != NULL && + (!_dbus_string_append (&address, ",noncefile=") || + !_dbus_string_append (&address, noncefile))) + goto error; + + fd = _dbus_connect_tcp_socket_with_nonce (host, port, family, noncefile, error); + if (!_dbus_socket_is_valid (fd)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_string_free (&address); + return NULL; + } + + _dbus_verbose ("Successfully connected to tcp socket %s:%s\n", + host, port); + + transport = _dbus_transport_new_for_socket (fd, NULL, &address); + _dbus_string_free (&address); + if (transport == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_close_socket (&fd, NULL); + } + + return transport; + +error: + _dbus_string_free (&address); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; +} + +/** + * Opens a TCP socket transport. + * + * @param entry the address entry to try opening as a tcp transport. + * @param transport_p return location for the opened transport + * @param error error to be set + * @returns result of the attempt + */ +DBusTransportOpenResult +_dbus_transport_open_socket(DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error) +{ + const char *method; + dbus_bool_t isTcp; + dbus_bool_t isNonceTcp; + + method = dbus_address_entry_get_method (entry); + _dbus_assert (method != NULL); + + isTcp = strcmp (method, "tcp") == 0; + isNonceTcp = strcmp (method, "nonce-tcp") == 0; + + if (isTcp || isNonceTcp) + { + const char *host = dbus_address_entry_get_value (entry, "host"); + const char *port = dbus_address_entry_get_value (entry, "port"); + const char *family = dbus_address_entry_get_value (entry, "family"); + const char *noncefile = dbus_address_entry_get_value (entry, "noncefile"); + + if ((isNonceTcp == TRUE) != (noncefile != NULL)) { + _dbus_set_bad_address (error, method, "noncefile", NULL); + return DBUS_TRANSPORT_OPEN_BAD_ADDRESS; + } + + if (port == NULL) + { + _dbus_set_bad_address (error, method, "port", NULL); + return DBUS_TRANSPORT_OPEN_BAD_ADDRESS; + } + + *transport_p = _dbus_transport_new_for_tcp_socket (host, port, family, noncefile, error); + if (*transport_p == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_OK; + } + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_NOT_HANDLED; + } +} + +/** + * Creates a new transport for the given Unix domain socket + * path. This creates a client-side of a transport. + * + * @param path the path to the domain socket. + * @param abstract #TRUE to use abstract socket namespace + * @param error address where an error can be returned. + * @returns a new transport, or #NULL on failure. + */ +DBusTransport* +_dbus_transport_new_for_domain_socket (const char *path, + dbus_bool_t abstract, + DBusError *error) +{ + DBusSocket fd = DBUS_SOCKET_INIT; + DBusTransport *transport; + DBusString address; + DBusString unescaped_path; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&address)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + _dbus_string_init_const (&unescaped_path, path); + + if ((abstract && + !_dbus_string_append (&address, "unix:abstract=")) || + (!abstract && + !_dbus_string_append (&address, "unix:path=")) || + !_dbus_address_append_escaped (&address, &unescaped_path)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed_0; + } + + fd = _dbus_connect_unix_socket (path, abstract, error); + if (!_dbus_socket_is_valid (fd)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed_0; + } + + _dbus_verbose ("Successfully connected to unix socket %s\n", + path); + + transport = _dbus_transport_new_for_socket (fd, NULL, &address); + if (transport == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed_1; + } + + _dbus_string_free (&address); + + return transport; + + failed_1: + _dbus_close_socket (&fd, NULL); + failed_0: + _dbus_string_free (&address); + return NULL; +} + +/** + * Opens a UNIX socket transport. + * + * @param entry the address entry to try opening as a unix transport. + * @param transport_p return location for the opened transport + * @param error error to be set + * @returns result of the attempt + */ +DBusTransportOpenResult +_dbus_transport_open_unix_socket (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error) +{ + const char *method; + + method = dbus_address_entry_get_method (entry); + _dbus_assert (method != NULL); + + if (strcmp (method, "unix") == 0) + { + const char *path = dbus_address_entry_get_value (entry, "path"); + const char *tmpdir = dbus_address_entry_get_value (entry, "tmpdir"); + const char *abstract = dbus_address_entry_get_value (entry, "abstract"); + + if (tmpdir != NULL) + { + _dbus_set_bad_address (error, NULL, NULL, + "cannot use the \"tmpdir\" option for an address to connect to, only in an address to listen on"); + return DBUS_TRANSPORT_OPEN_BAD_ADDRESS; + } + + if (path == NULL && abstract == NULL) + { + _dbus_set_bad_address (error, "unix", + "path or abstract", + NULL); + return DBUS_TRANSPORT_OPEN_BAD_ADDRESS; + } + + if (path != NULL && abstract != NULL) + { + _dbus_set_bad_address (error, NULL, NULL, + "can't specify both \"path\" and \"abstract\" options in an address"); + return DBUS_TRANSPORT_OPEN_BAD_ADDRESS; + } + + if (path) + *transport_p = _dbus_transport_new_for_domain_socket (path, FALSE, + error); + else + *transport_p = _dbus_transport_new_for_domain_socket (abstract, TRUE, + error); + if (*transport_p == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_OK; + } + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_NOT_HANDLED; + } +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-transport-socket.h b/src/3rdparty/libdbus/dbus/dbus-transport-socket.h new file mode 100644 index 00000000..65de3c15 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-transport-socket.h @@ -0,0 +1,54 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-transport-socket.h Socket subclasses of DBusTransport + * + * Copyright (C) 2002, 2006 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_TRANSPORT_SOCKET_H +#define DBUS_TRANSPORT_SOCKET_H + +#include <dbus/dbus-transport-protected.h> + +DBUS_BEGIN_DECLS + +DBusTransport* _dbus_transport_new_for_socket (DBusSocket fd, + const DBusString *server_guid, + const DBusString *address); +DBusTransport* _dbus_transport_new_for_tcp_socket (const char *host, + const char *port, + const char *family, + const char *noncefile, + DBusError *error); +DBusTransportOpenResult _dbus_transport_open_socket (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error); + +DBusTransport* _dbus_transport_new_for_domain_socket (const char *path, + dbus_bool_t abstract, + DBusError *error); + +DBusTransportOpenResult _dbus_transport_open_unix_socket (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error); + +DBUS_END_DECLS + +#endif /* DBUS_TRANSPORT_SOCKET_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-transport-unix.c b/src/3rdparty/libdbus/dbus/dbus-transport-unix.c new file mode 100644 index 00000000..a8dfaa52 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-transport-unix.c @@ -0,0 +1,317 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-transport-unix.c UNIX socket subclasses of DBusTransport + * + * Copyright (C) 2002, 2003, 2004 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#include <stdio.h> + +#include "dbus-internals.h" +#include "dbus-connection-internal.h" +#include "dbus-transport-unix.h" +#include "dbus-transport-socket.h" +#include "dbus-transport-protected.h" +#include "dbus-watch.h" +#include "dbus-sysdeps-unix.h" +#include "dbus-test.h" + +/** + * @defgroup DBusTransportUnix DBusTransport implementations for UNIX + * @ingroup DBusInternals + * @brief Implementation details of DBusTransport on UNIX + * + * @{ + */ + +/** + * Creates a new transport for the given binary and arguments. This + * creates a client-side of a transport. The process will be forked + * off and executed with stdin/stdout connected to a local AF_UNIX + * socket. + * + * @param path the path to the domain socket. + * @param argv Parameters list + * @param error address where an error can be returned. + * @returns a new transport, or #NULL on failure. + */ +static DBusTransport* +_dbus_transport_new_for_exec (const char *path, + char *const argv[], + DBusError *error) +{ + DBusSocket fd = DBUS_SOCKET_INIT; + DBusTransport *transport; + DBusString address; + unsigned i; + char *escaped; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&address)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + escaped = dbus_address_escape_value (path); + if (!escaped) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + if (!_dbus_string_append (&address, "unixexec:path=") || + !_dbus_string_append (&address, escaped)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + dbus_free (escaped); + goto failed; + } + + dbus_free (escaped); + + if (argv) + { + for (i = 0; argv[i]; i++) + { + dbus_bool_t success; + + escaped = dbus_address_escape_value (argv[i]); + if (!escaped) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + success = _dbus_string_append_printf (&address, ",argv%u=%s", i, escaped); + dbus_free (escaped); + + if (!success) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + } + } + + fd = _dbus_connect_exec (path, argv, error); + if (!_dbus_socket_is_valid (fd)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + _dbus_verbose ("Successfully connected to process %s\n", + path); + + transport = _dbus_transport_new_for_socket (fd, NULL, &address); + if (transport == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + _dbus_string_free (&address); + + return transport; + + failed: + if (_dbus_socket_is_valid (fd)) + _dbus_close_socket (&fd, NULL); + + _dbus_string_free (&address); + return NULL; +} + + +DBusTransportOpenResult +_dbus_transport_open_unixexec (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error) +{ + const char *method; + + method = dbus_address_entry_get_method (entry); + _dbus_assert (method != NULL); + + if (strcmp (method, "unixexec") == 0) + { + const char *path; + unsigned i; + char **argv; + + path = dbus_address_entry_get_value (entry, "path"); + if (path == NULL) + { + _dbus_set_bad_address (error, NULL, NULL, + "No process path specified"); + return DBUS_TRANSPORT_OPEN_BAD_ADDRESS; + } + + /* First count argv arguments */ + for (i = 1; ; i++) + { + char t[4+20+1]; /* "argv" plus space for a formatted base 10 64bit integer, plus NUL */ + + snprintf (t, sizeof(t), "argv%u", i); + + if (!dbus_address_entry_get_value (entry, t)) + break; + } + + /* Allocate string array */ + argv = dbus_new0 (char*, i+1); + if (!argv) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + + /* Fill in string array */ + for (i = 0; ; i++) + { + char t[4+20+1]; + const char *p; + + snprintf (t, sizeof(t), "argv%u", i); + + p = dbus_address_entry_get_value (entry, t); + if (!p) + { + if (i == 0) + /* If argv0 isn't specified, fill in the path instead */ + p = path; + else + break; + } + + argv[i] = _dbus_strdup (p); + if (!argv[i]) + { + dbus_free_string_array (argv); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + } + + *transport_p = _dbus_transport_new_for_exec (path, argv, error); + dbus_free_string_array (argv); + + if (*transport_p == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_OK; + } + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_NOT_HANDLED; + } +} + +/** + * Opens platform specific transport types. + * + * @param entry the address entry to try opening + * @param transport_p return location for the opened transport + * @param error error to be set + * @returns result of the attempt + */ +DBusTransportOpenResult +_dbus_transport_open_platform_specific (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error) +{ +#ifdef DBUS_ENABLE_LAUNCHD + const char *method; + + method = dbus_address_entry_get_method (entry); + _dbus_assert (method != NULL); + + if (strcmp (method, "launchd") == 0) + { + DBusError tmp_error = DBUS_ERROR_INIT; + const char *launchd_env_var = dbus_address_entry_get_value (entry, "env"); + const char *launchd_socket; + DBusString socket_path; + dbus_bool_t valid_socket; + + if (!_dbus_string_init (&socket_path)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (launchd_env_var == NULL) + { + _dbus_set_bad_address (error, "launchd", "env", NULL); + return DBUS_TRANSPORT_OPEN_BAD_ADDRESS; + } + + valid_socket = _dbus_lookup_launchd_socket (&socket_path, launchd_env_var, error); + + if (dbus_error_is_set(error)) + { + _dbus_string_free(&socket_path); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + + if (!valid_socket) + { + dbus_set_error(&tmp_error, DBUS_ERROR_BAD_ADDRESS, + "launchd's env var %s does not exist", launchd_env_var); + dbus_error_free(error); + dbus_move_error(&tmp_error, error); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + + launchd_socket = _dbus_string_get_const_data(&socket_path); + *transport_p = _dbus_transport_new_for_domain_socket (launchd_socket, FALSE, error); + + if (*transport_p == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_OK; + } + } + else +#endif /* DBUS_ENABLE_LAUNCHD */ + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_NOT_HANDLED; + } +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-transport-unix.h b/src/3rdparty/libdbus/dbus/dbus-transport-unix.h new file mode 100644 index 00000000..df30a179 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-transport-unix.h @@ -0,0 +1,37 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-transport-unix.h UNIX socket subclasses of DBusTransport + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_TRANSPORT_UNIX_H +#define DBUS_TRANSPORT_UNIX_H + +#include <dbus/dbus-transport-protected.h> + +DBUS_BEGIN_DECLS + +DBusTransportOpenResult _dbus_transport_open_unixexec (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error); +DBUS_END_DECLS + +#endif /* DBUS_TRANSPORT_UNIX_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-transport-win.c b/src/3rdparty/libdbus/dbus/dbus-transport-win.c new file mode 100644 index 00000000..6a0dddce --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-transport-win.c @@ -0,0 +1,60 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-transport-win.c Windows socket subclasses of DBusTransport + * + * Copyright (C) 2002, 2003, 2004 Red Hat Inc. + * Copyright (C) 2007 Ralf Habacker <ralf.habacker@freenet.de> + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-connection-internal.h" +#include "dbus-transport-socket.h" +#include "dbus-transport-protected.h" +#include "dbus-watch.h" +#include "dbus-sysdeps-win.h" + +/** + * @defgroup DBusTransportUnix DBusTransport implementations for UNIX + * @ingroup DBusInternals + * @brief Implementation details of DBusTransport on UNIX + * + * @{ + */ + +/** + * Opens platform specific transport types. + * + * @param entry the address entry to try opening + * @param transport_p return location for the opened transport + * @param error error to be set + * @returns result of the attempt + */ +DBusTransportOpenResult +_dbus_transport_open_platform_specific (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error) +{ + /* currently no Windows-specific transports */ + return DBUS_TRANSPORT_OPEN_NOT_HANDLED; +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-transport-win.h b/src/3rdparty/libdbus/dbus/dbus-transport-win.h new file mode 100644 index 00000000..28ccfa85 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-transport-win.h @@ -0,0 +1,35 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-transport-win.h Windows socket subclasses of DBusTransport + * + * Copyright (C) 2002 Red Hat Inc. + * Copyright (C) 2007 Ralf Habacker <ralf.habacker@freenet.de> + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_TRANSPORT_WIN_H +#define DBUS_TRANSPORT_WIN_H + +#include <dbus/dbus-transport.h> + +DBUS_BEGIN_DECLS + +DBUS_END_DECLS + +#endif /* DBUS_TRANSPORT_WIN_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-transport.c b/src/3rdparty/libdbus/dbus/dbus-transport.c new file mode 100644 index 00000000..f6119754 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-transport.c @@ -0,0 +1,1633 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-transport.c DBusTransport object (internal to D-Bus implementation) + * + * Copyright (C) 2002, 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-transport-protected.h" +#include "dbus-transport-unix.h" +#include "dbus-transport-socket.h" +#include "dbus-connection-internal.h" +#include "dbus-watch.h" +#include "dbus-auth.h" +#include "dbus-address.h" +#include "dbus-credentials.h" +//#include "dbus-mainloop.h" +#include "dbus-message.h" +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +#include "dbus-server-debug-pipe.h" +#endif + +/** + * @defgroup DBusTransport DBusTransport object + * @ingroup DBusInternals + * @brief "Backend" for a DBusConnection. + * + * Types and functions related to DBusTransport. A transport is an + * abstraction that can send and receive data via various kinds of + * network connections or other IPC mechanisms. + * + * @{ + */ + +/** + * @typedef DBusTransport + * + * Opaque object representing a way message stream. + * DBusTransport abstracts various kinds of actual + * transport mechanism, such as different network protocols, + * or encryption schemes. + */ + +static void +live_messages_notify (DBusCounter *counter, + void *user_data) +{ + DBusTransport *transport = user_data; + + _dbus_connection_lock (transport->connection); + _dbus_transport_ref (transport); + +#if 0 + _dbus_verbose ("Size counter value is now %d\n", + (int) _dbus_counter_get_size_value (counter)); + _dbus_verbose ("Unix FD counter value is now %d\n", + (int) _dbus_counter_get_unix_fd_value (counter)); +#endif + + /* disable or re-enable the read watch for the transport if + * required. + */ + if (transport->vtable->live_messages_changed) + { + (* transport->vtable->live_messages_changed) (transport); + } + + _dbus_transport_unref (transport); + _dbus_connection_unlock (transport->connection); +} + +/** + * Initializes the base class members of DBusTransport. Chained up to + * by subclasses in their constructor. The server GUID is the + * globally unique ID for the server creating this connection + * and will be #NULL for the client side of a connection. The GUID + * is in hex format. + * + * @param transport the transport being created. + * @param vtable the subclass vtable. + * @param server_guid non-#NULL if this transport is on the server side of a connection + * @param address the address of the transport + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_transport_init_base (DBusTransport *transport, + const DBusTransportVTable *vtable, + const DBusString *server_guid, + const DBusString *address) +{ + DBusMessageLoader *loader; + DBusAuth *auth; + DBusCounter *counter; + char *address_copy; + DBusCredentials *creds; + + loader = _dbus_message_loader_new (); + if (loader == NULL) + return FALSE; + + if (server_guid) + auth = _dbus_auth_server_new (server_guid); + else + auth = _dbus_auth_client_new (); + if (auth == NULL) + { + _dbus_message_loader_unref (loader); + return FALSE; + } + + counter = _dbus_counter_new (); + if (counter == NULL) + { + _dbus_auth_unref (auth); + _dbus_message_loader_unref (loader); + return FALSE; + } + + creds = _dbus_credentials_new (); + if (creds == NULL) + { + _dbus_counter_unref (counter); + _dbus_auth_unref (auth); + _dbus_message_loader_unref (loader); + return FALSE; + } + + if (server_guid) + { + _dbus_assert (address == NULL); + address_copy = NULL; + } + else + { + _dbus_assert (address != NULL); + + if (!_dbus_string_copy_data (address, &address_copy)) + { + _dbus_credentials_unref (creds); + _dbus_counter_unref (counter); + _dbus_auth_unref (auth); + _dbus_message_loader_unref (loader); + return FALSE; + } + } + + transport->refcount = 1; + transport->vtable = vtable; + transport->loader = loader; + transport->auth = auth; + transport->live_messages = counter; + transport->authenticated = FALSE; + transport->disconnected = FALSE; + transport->is_server = (server_guid != NULL); + transport->send_credentials_pending = !transport->is_server; + transport->receive_credentials_pending = transport->is_server; + transport->address = address_copy; + + transport->unix_user_function = NULL; + transport->unix_user_data = NULL; + transport->free_unix_user_data = NULL; + + transport->windows_user_function = NULL; + transport->windows_user_data = NULL; + transport->free_windows_user_data = NULL; + + transport->expected_guid = NULL; + + /* Try to default to something that won't totally hose the system, + * but doesn't impose too much of a limitation. + */ + transport->max_live_messages_size = _DBUS_ONE_MEGABYTE * 63; + + /* On Linux RLIMIT_NOFILE defaults to 1024, so allowing 4096 fds live + should be more than enough */ + transport->max_live_messages_unix_fds = 4096; + + /* credentials read from socket if any */ + transport->credentials = creds; + + _dbus_counter_set_notify (transport->live_messages, + transport->max_live_messages_size, + transport->max_live_messages_unix_fds, + live_messages_notify, + transport); + + if (transport->address) + _dbus_verbose ("Initialized transport on address %s\n", transport->address); + + return TRUE; +} + +/** + * Finalizes base class members of DBusTransport. + * Chained up to from subclass finalizers. + * + * @param transport the transport. + */ +void +_dbus_transport_finalize_base (DBusTransport *transport) +{ + if (!transport->disconnected) + _dbus_transport_disconnect (transport); + + if (transport->free_unix_user_data != NULL) + (* transport->free_unix_user_data) (transport->unix_user_data); + + if (transport->free_windows_user_data != NULL) + (* transport->free_windows_user_data) (transport->windows_user_data); + + _dbus_message_loader_unref (transport->loader); + _dbus_auth_unref (transport->auth); + _dbus_counter_set_notify (transport->live_messages, + 0, 0, NULL, NULL); + _dbus_counter_unref (transport->live_messages); + dbus_free (transport->address); + dbus_free (transport->expected_guid); + if (transport->credentials) + _dbus_credentials_unref (transport->credentials); +} + + +/** + * Verifies if a given D-Bus address is a valid address + * by attempting to connect to it. If it is, returns the + * opened DBusTransport object. If it isn't, returns #NULL + * and sets @p error. + * + * @param address the address to be checked. + * @param error address where an error can be returned. + * @returns a new transport, or #NULL on failure. + */ +static DBusTransport* +check_address (const char *address, DBusError *error) +{ + DBusAddressEntry **entries; + DBusTransport *transport = NULL; + int len, i; + + _dbus_assert (address != NULL); + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!dbus_parse_address (address, &entries, &len, error)) + return NULL; /* not a valid address */ + + for (i = 0; i < len; i++) + { + dbus_error_free (error); + transport = _dbus_transport_open (entries[i], error); + + if (transport != NULL) + break; + } + + dbus_address_entries_free (entries); + return transport; +} + +/** + * Creates a new transport for the "autostart" method. + * This creates a client-side of a transport. + * + * @param scope scope of autolaunch (Windows only) + * @param error address where an error can be returned. + * @returns a new transport, or #NULL on failure. + */ +static DBusTransport* +_dbus_transport_new_for_autolaunch (const char *scope, DBusError *error) +{ + DBusString address; + DBusTransport *result = NULL; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&address)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + if (!_dbus_get_autolaunch_address (scope, &address, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto out; + } + + result = check_address (_dbus_string_get_const_data (&address), error); + _DBUS_ASSERT_ERROR_XOR_BOOL (error, result != NULL); + + out: + _dbus_string_free (&address); + return result; +} + +static DBusTransportOpenResult +_dbus_transport_open_autolaunch (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error) +{ + const char *method; + + method = dbus_address_entry_get_method (entry); + _dbus_assert (method != NULL); + + if (strcmp (method, "autolaunch") == 0) + { + const char *scope = dbus_address_entry_get_value (entry, "scope"); + + *transport_p = _dbus_transport_new_for_autolaunch (scope, error); + + if (*transport_p == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_OK; + } + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_NOT_HANDLED; + } +} + +static const struct { + DBusTransportOpenResult (* func) (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error); +} open_funcs[] = { + { _dbus_transport_open_socket }, + { _dbus_transport_open_unix_socket }, +#ifndef _WIN32 + { _dbus_transport_open_unixexec }, +#endif + { _dbus_transport_open_platform_specific }, + { _dbus_transport_open_autolaunch } +#ifdef DBUS_ENABLE_EMBEDDED_TESTS + , { _dbus_transport_open_debug_pipe } +#endif +}; + +/** + * Try to open a new transport for the given address entry. (This + * opens a client-side-of-the-connection transport.) + * + * @param entry the address entry + * @param error location to store reason for failure. + * @returns new transport of #NULL on failure. + */ +DBusTransport* +_dbus_transport_open (DBusAddressEntry *entry, + DBusError *error) +{ + DBusTransport *transport; + const char *expected_guid_orig; + char *expected_guid; + int i; + DBusError tmp_error = DBUS_ERROR_INIT; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + transport = NULL; + expected_guid_orig = dbus_address_entry_get_value (entry, "guid"); + expected_guid = _dbus_strdup (expected_guid_orig); + + if (expected_guid_orig != NULL && expected_guid == NULL) + { + _DBUS_SET_OOM (error); + return NULL; + } + + for (i = 0; i < (int) _DBUS_N_ELEMENTS (open_funcs); ++i) + { + DBusTransportOpenResult result; + + _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error); + result = (* open_funcs[i].func) (entry, &transport, &tmp_error); + + switch (result) + { + case DBUS_TRANSPORT_OPEN_OK: + _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error); + goto out; + break; + case DBUS_TRANSPORT_OPEN_NOT_HANDLED: + _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error); + /* keep going through the loop of open funcs */ + break; + case DBUS_TRANSPORT_OPEN_BAD_ADDRESS: + _DBUS_ASSERT_ERROR_IS_SET (&tmp_error); + goto out; + break; + case DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT: + _DBUS_ASSERT_ERROR_IS_SET (&tmp_error); + goto out; + break; + default: + _dbus_assert_not_reached ("invalid transport open result"); + break; + } + } + + out: + + if (transport == NULL) + { + if (!dbus_error_is_set (&tmp_error)) + _dbus_set_bad_address (&tmp_error, + NULL, NULL, + "Unknown address type (examples of valid types are \"tcp\" and on UNIX \"unix\")"); + + _DBUS_ASSERT_ERROR_IS_SET (&tmp_error); + dbus_move_error(&tmp_error, error); + dbus_free (expected_guid); + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error); + + /* In the case of autostart the initial guid is NULL + * and the autostart transport recursively calls + * _dbus_open_transport wich returns a transport + * with a guid. That guid is the definitive one. + * + * FIXME: if more transports are added they may have + * an effect on the expected_guid semantics (i.e. + * expected_guid and transport->expected_guid may + * both have values). This is very unlikely though + * we should either throw asserts here for those + * corner cases or refactor the code so it is + * clearer on what is expected and what is not + */ + if(expected_guid) + transport->expected_guid = expected_guid; + } + + return transport; +} + +/** + * Increments the reference count for the transport. + * + * @param transport the transport. + * @returns the transport. + */ +DBusTransport * +_dbus_transport_ref (DBusTransport *transport) +{ + _dbus_assert (transport->refcount > 0); + + transport->refcount += 1; + + return transport; +} + +/** + * Decrements the reference count for the transport. + * Disconnects and finalizes the transport if + * the reference count reaches zero. + * + * @param transport the transport. + */ +void +_dbus_transport_unref (DBusTransport *transport) +{ + _dbus_assert (transport != NULL); + _dbus_assert (transport->refcount > 0); + + transport->refcount -= 1; + if (transport->refcount == 0) + { + _dbus_verbose ("finalizing\n"); + + _dbus_assert (transport->vtable->finalize != NULL); + + (* transport->vtable->finalize) (transport); + } +} + +/** + * Closes our end of the connection to a remote application. Further + * attempts to use this transport will fail. Only the first call to + * _dbus_transport_disconnect() will have an effect. + * + * @param transport the transport. + * + */ +void +_dbus_transport_disconnect (DBusTransport *transport) +{ + _dbus_verbose ("start\n"); + + _dbus_assert (transport->vtable->disconnect != NULL); + + if (transport->disconnected) + return; + + (* transport->vtable->disconnect) (transport); + + transport->disconnected = TRUE; + + _dbus_verbose ("end\n"); +} + +/** + * Returns #TRUE if the transport has not been disconnected. + * Disconnection can result from _dbus_transport_disconnect() + * or because the server drops its end of the connection. + * + * @param transport the transport. + * @returns whether we're connected + */ +dbus_bool_t +_dbus_transport_get_is_connected (DBusTransport *transport) +{ + return !transport->disconnected; +} + +static dbus_bool_t +auth_via_unix_user_function (DBusTransport *transport) +{ + DBusCredentials *auth_identity; + dbus_bool_t allow; + DBusConnection *connection; + DBusAllowUnixUserFunction unix_user_function; + void *unix_user_data; + dbus_uid_t uid; + + /* Dropping the lock here probably isn't that safe. */ + + auth_identity = _dbus_auth_get_identity (transport->auth); + _dbus_assert (auth_identity != NULL); + + connection = transport->connection; + unix_user_function = transport->unix_user_function; + unix_user_data = transport->unix_user_data; + uid = _dbus_credentials_get_unix_uid (auth_identity); + + _dbus_verbose ("unlock\n"); + _dbus_connection_unlock (connection); + + allow = (* unix_user_function) (connection, + uid, + unix_user_data); + + _dbus_verbose ("lock post unix user function\n"); + _dbus_connection_lock (connection); + + if (allow) + { + _dbus_verbose ("Client UID "DBUS_UID_FORMAT" authorized\n", uid); + } + else + { + _dbus_verbose ("Client UID "DBUS_UID_FORMAT + " was rejected, disconnecting\n", + _dbus_credentials_get_unix_uid (auth_identity)); + _dbus_transport_disconnect (transport); + } + + return allow; +} + +static dbus_bool_t +auth_via_windows_user_function (DBusTransport *transport) +{ + DBusCredentials *auth_identity; + dbus_bool_t allow; + DBusConnection *connection; + DBusAllowWindowsUserFunction windows_user_function; + void *windows_user_data; + char *windows_sid; + + /* Dropping the lock here probably isn't that safe. */ + + auth_identity = _dbus_auth_get_identity (transport->auth); + _dbus_assert (auth_identity != NULL); + + connection = transport->connection; + windows_user_function = transport->windows_user_function; + windows_user_data = transport->unix_user_data; + windows_sid = _dbus_strdup (_dbus_credentials_get_windows_sid (auth_identity)); + + if (windows_sid == NULL) + { + /* OOM */ + return FALSE; + } + + _dbus_verbose ("unlock\n"); + _dbus_connection_unlock (connection); + + allow = (* windows_user_function) (connection, + windows_sid, + windows_user_data); + + _dbus_verbose ("lock post windows user function\n"); + _dbus_connection_lock (connection); + + if (allow) + { + _dbus_verbose ("Client SID '%s' authorized\n", windows_sid); + } + else + { + _dbus_verbose ("Client SID '%s' was rejected, disconnecting\n", + _dbus_credentials_get_windows_sid (auth_identity)); + _dbus_transport_disconnect (transport); + } + + return allow; +} + +static dbus_bool_t +auth_via_default_rules (DBusTransport *transport) +{ + DBusCredentials *auth_identity; + DBusCredentials *our_identity; + dbus_bool_t allow; + + auth_identity = _dbus_auth_get_identity (transport->auth); + _dbus_assert (auth_identity != NULL); + + /* By default, connection is allowed if the client is 1) root or 2) + * has the same UID as us or 3) anonymous is allowed. + */ + + our_identity = _dbus_credentials_new_from_current_process (); + if (our_identity == NULL) + { + /* OOM */ + return FALSE; + } + + if (transport->allow_anonymous || + _dbus_credentials_get_unix_uid (auth_identity) == 0 || + _dbus_credentials_same_user (our_identity, + auth_identity)) + { + if (_dbus_credentials_include(our_identity,DBUS_CREDENTIAL_WINDOWS_SID)) + _dbus_verbose ("Client authorized as SID '%s'" + "matching our SID '%s'\n", + _dbus_credentials_get_windows_sid(auth_identity), + _dbus_credentials_get_windows_sid(our_identity)); + else + _dbus_verbose ("Client authorized as UID "DBUS_UID_FORMAT + " matching our UID "DBUS_UID_FORMAT"\n", + _dbus_credentials_get_unix_uid(auth_identity), + _dbus_credentials_get_unix_uid(our_identity)); + /* We have authenticated! */ + allow = TRUE; + } + else + { + if (_dbus_credentials_include(our_identity,DBUS_CREDENTIAL_WINDOWS_SID)) + _dbus_verbose ("Client authorized as SID '%s'" + " but our SID is '%s', disconnecting\n", + (_dbus_credentials_get_windows_sid(auth_identity) ? + _dbus_credentials_get_windows_sid(auth_identity) : "<null>"), + (_dbus_credentials_get_windows_sid(our_identity) ? + _dbus_credentials_get_windows_sid(our_identity) : "<null>")); + else + _dbus_verbose ("Client authorized as UID "DBUS_UID_FORMAT + " but our UID is "DBUS_UID_FORMAT", disconnecting\n", + _dbus_credentials_get_unix_uid(auth_identity), + _dbus_credentials_get_unix_uid(our_identity)); + _dbus_transport_disconnect (transport); + allow = FALSE; + } + + _dbus_credentials_unref (our_identity); + + return allow; +} + +/** + * Returns #TRUE if we have been authenticated. It will return #TRUE even if + * the transport is now disconnected, but was ever authenticated before + * disconnecting. + * + * This replaces the older _dbus_transport_get_is_authenticated() which + * had side-effects. + * + * @param transport the transport + * @returns whether we're authenticated + */ +dbus_bool_t +_dbus_transport_peek_is_authenticated (DBusTransport *transport) +{ + return transport->authenticated; +} + +/** + * Returns #TRUE if we have been authenticated. It will return #TRUE even if + * the transport is now disconnected, but was ever authenticated before + * disconnecting. + * + * If we have not finished authenticating, but we have enough buffered input + * to finish the job, then this function will do so before it returns. + * + * This used to be called _dbus_transport_get_is_authenticated(), but that + * name seems inappropriate for a function with side-effects. + * + * @todo we drop connection->mutex when calling the unix_user_function, + * and windows_user_function, which may not be safe really. + * + * @param transport the transport + * @returns whether we're authenticated + */ +dbus_bool_t +_dbus_transport_try_to_authenticate (DBusTransport *transport) +{ + if (transport->authenticated) + return TRUE; + else + { + dbus_bool_t maybe_authenticated; + + if (transport->disconnected) + return FALSE; + + /* paranoia ref since we call user callbacks sometimes */ + _dbus_connection_ref_unlocked (transport->connection); + + maybe_authenticated = + (!(transport->send_credentials_pending || + transport->receive_credentials_pending)); + + if (maybe_authenticated) + { + switch (_dbus_auth_do_work (transport->auth)) + { + case DBUS_AUTH_STATE_AUTHENTICATED: + /* leave as maybe_authenticated */ + break; + + case DBUS_AUTH_STATE_WAITING_FOR_INPUT: + case DBUS_AUTH_STATE_WAITING_FOR_MEMORY: + case DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND: + case DBUS_AUTH_STATE_NEED_DISCONNECT: + maybe_authenticated = FALSE; + break; + + case DBUS_AUTH_STATE_INVALID: + default: + _dbus_assert_not_reached ("invalid authentication state"); + } + } + + /* If we're the client, verify the GUID + */ + if (maybe_authenticated && !transport->is_server) + { + const char *server_guid; + + server_guid = _dbus_auth_get_guid_from_server (transport->auth); + _dbus_assert (server_guid != NULL); + + if (transport->expected_guid && + strcmp (transport->expected_guid, server_guid) != 0) + { + _dbus_verbose ("Client expected GUID '%s' and we got '%s' from the server\n", + transport->expected_guid, server_guid); + _dbus_transport_disconnect (transport); + _dbus_connection_unref_unlocked (transport->connection); + return FALSE; + } + } + + /* If we're the server, see if we want to allow this identity to proceed. + */ + if (maybe_authenticated && transport->is_server) + { + dbus_bool_t allow; + DBusCredentials *auth_identity; + + auth_identity = _dbus_auth_get_identity (transport->auth); + _dbus_assert (auth_identity != NULL); + + /* If we have an auth'd user and a user function, delegate + * deciding whether auth credentials are good enough to the + * app; otherwise, use our default decision process. + */ + if (transport->unix_user_function != NULL && + _dbus_credentials_include (auth_identity, DBUS_CREDENTIAL_UNIX_USER_ID)) + { + allow = auth_via_unix_user_function (transport); + } + else if (transport->windows_user_function != NULL && + _dbus_credentials_include (auth_identity, DBUS_CREDENTIAL_WINDOWS_SID)) + { + allow = auth_via_windows_user_function (transport); + } + else + { + allow = auth_via_default_rules (transport); + } + + if (!allow) + maybe_authenticated = FALSE; + } + + transport->authenticated = maybe_authenticated; + + _dbus_connection_unref_unlocked (transport->connection); + return maybe_authenticated; + } +} + +/** + * See dbus_connection_get_is_anonymous(). + * + * @param transport the transport + * @returns #TRUE if not authenticated or authenticated as anonymous + */ +dbus_bool_t +_dbus_transport_get_is_anonymous (DBusTransport *transport) +{ + DBusCredentials *auth_identity; + + if (!transport->authenticated) + return TRUE; + + auth_identity = _dbus_auth_get_identity (transport->auth); + + if (_dbus_credentials_are_anonymous (auth_identity)) + return TRUE; + else + return FALSE; +} + +/** + * Returns TRUE if the transport supports sending unix fds. + * + * @param transport the transport + * @returns #TRUE if TRUE it is possible to send unix fds across the transport. + */ +dbus_bool_t +_dbus_transport_can_pass_unix_fd(DBusTransport *transport) +{ + return DBUS_TRANSPORT_CAN_SEND_UNIX_FD(transport); +} + +/** + * Gets the address of a transport. It will be + * #NULL for a server-side transport. + * + * @param transport the transport + * @returns transport's address + */ +const char* +_dbus_transport_get_address (DBusTransport *transport) +{ + return transport->address; +} + +/** + * Gets the id of the server we are connected to (see + * dbus_server_get_id()). Only works on client side. + * + * @param transport the transport + * @returns transport's server's id or #NULL if we are the server side + */ +const char* +_dbus_transport_get_server_id (DBusTransport *transport) +{ + if (transport->is_server) + return NULL; + else if (transport->authenticated) + return _dbus_auth_get_guid_from_server (transport->auth); + else + return transport->expected_guid; +} + +/** + * Handles a watch by reading data, writing data, or disconnecting + * the transport, as appropriate for the given condition. + * + * @param transport the transport. + * @param watch the watch. + * @param condition the current state of the watched file descriptor. + * @returns #FALSE if not enough memory to fully handle the watch + */ +dbus_bool_t +_dbus_transport_handle_watch (DBusTransport *transport, + DBusWatch *watch, + unsigned int condition) +{ + dbus_bool_t retval; + + _dbus_assert (transport->vtable->handle_watch != NULL); + + if (transport->disconnected) + return TRUE; + + if (dbus_watch_get_socket (watch) < 0) + { + _dbus_warn_check_failed ("Tried to handle an invalidated watch; this watch should have been removed"); + return TRUE; + } + + _dbus_watch_sanitize_condition (watch, &condition); + + _dbus_transport_ref (transport); + _dbus_watch_ref (watch); + retval = (* transport->vtable->handle_watch) (transport, watch, condition); + _dbus_watch_unref (watch); + _dbus_transport_unref (transport); + + return retval; +} + +/** + * Sets the connection using this transport. Allows the transport + * to add watches to the connection, queue incoming messages, + * and pull outgoing messages. + * + * @param transport the transport. + * @param connection the connection. + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_transport_set_connection (DBusTransport *transport, + DBusConnection *connection) +{ + _dbus_assert (transport->vtable->connection_set != NULL); + _dbus_assert (transport->connection == NULL); + + transport->connection = connection; + + _dbus_transport_ref (transport); + if (!(* transport->vtable->connection_set) (transport)) + transport->connection = NULL; + _dbus_transport_unref (transport); + + return transport->connection != NULL; +} + +/** + * Get the socket file descriptor, if any. + * + * @param transport the transport + * @param fd_p pointer to fill in with the descriptor + * @returns #TRUE if a descriptor was available + */ +dbus_bool_t +_dbus_transport_get_socket_fd (DBusTransport *transport, + DBusSocket *fd_p) +{ + dbus_bool_t retval; + + if (transport->vtable->get_socket_fd == NULL) + return FALSE; + + if (transport->disconnected) + return FALSE; + + _dbus_transport_ref (transport); + + retval = (* transport->vtable->get_socket_fd) (transport, + fd_p); + + _dbus_transport_unref (transport); + + return retval; +} + +/** + * Performs a single poll()/select() on the transport's file + * descriptors and then reads/writes data as appropriate, + * queueing incoming messages and sending outgoing messages. + * This is the backend for _dbus_connection_do_iteration(). + * See _dbus_connection_do_iteration() for full details. + * + * @param transport the transport. + * @param flags indicates whether to read or write, and whether to block. + * @param timeout_milliseconds if blocking, timeout or -1 for no timeout. + */ +void +_dbus_transport_do_iteration (DBusTransport *transport, + unsigned int flags, + int timeout_milliseconds) +{ + _dbus_assert (transport->vtable->do_iteration != NULL); + + _dbus_verbose ("Transport iteration flags 0x%x timeout %d connected = %d\n", + flags, timeout_milliseconds, !transport->disconnected); + + if ((flags & (DBUS_ITERATION_DO_WRITING | + DBUS_ITERATION_DO_READING)) == 0) + return; /* Nothing to do */ + + if (transport->disconnected) + return; + + _dbus_transport_ref (transport); + (* transport->vtable->do_iteration) (transport, flags, + timeout_milliseconds); + _dbus_transport_unref (transport); + + _dbus_verbose ("end\n"); +} + +static dbus_bool_t +recover_unused_bytes (DBusTransport *transport) +{ + if (_dbus_auth_needs_decoding (transport->auth)) + { + DBusString plaintext; + const DBusString *encoded; + DBusString *buffer; + int orig_len; + + if (!_dbus_string_init (&plaintext)) + goto nomem; + + _dbus_auth_get_unused_bytes (transport->auth, + &encoded); + + if (!_dbus_auth_decode_data (transport->auth, + encoded, &plaintext)) + { + _dbus_string_free (&plaintext); + goto nomem; + } + + _dbus_message_loader_get_buffer (transport->loader, + &buffer, + NULL, + NULL); + + orig_len = _dbus_string_get_length (buffer); + + if (!_dbus_string_move (&plaintext, 0, buffer, + orig_len)) + { + _dbus_string_free (&plaintext); + goto nomem; + } + + _dbus_verbose (" %d unused bytes sent to message loader\n", + _dbus_string_get_length (buffer) - + orig_len); + + _dbus_message_loader_return_buffer (transport->loader, + buffer); + + _dbus_auth_delete_unused_bytes (transport->auth); + + _dbus_string_free (&plaintext); + } + else + { + const DBusString *bytes; + DBusString *buffer; +#ifdef DBUS_ENABLE_VERBOSE_MODE + int orig_len; +#endif + dbus_bool_t succeeded; + + _dbus_message_loader_get_buffer (transport->loader, + &buffer, + NULL, + NULL); + +#ifdef DBUS_ENABLE_VERBOSE_MODE + orig_len = _dbus_string_get_length (buffer); +#endif + + _dbus_auth_get_unused_bytes (transport->auth, + &bytes); + + succeeded = TRUE; + if (!_dbus_string_copy (bytes, 0, buffer, _dbus_string_get_length (buffer))) + succeeded = FALSE; + + _dbus_verbose (" %d unused bytes sent to message loader\n", + _dbus_string_get_length (buffer) - + orig_len); + + _dbus_message_loader_return_buffer (transport->loader, + buffer); + + if (succeeded) + _dbus_auth_delete_unused_bytes (transport->auth); + else + goto nomem; + } + + return TRUE; + + nomem: + _dbus_verbose ("Not enough memory to transfer unused bytes from auth conversation\n"); + return FALSE; +} + +/** + * Reports our current dispatch status (whether there's buffered + * data to be queued as messages, or not, or we need memory). + * + * @param transport the transport + * @returns current status + */ +DBusDispatchStatus +_dbus_transport_get_dispatch_status (DBusTransport *transport) +{ + if (_dbus_counter_get_size_value (transport->live_messages) >= transport->max_live_messages_size || + _dbus_counter_get_unix_fd_value (transport->live_messages) >= transport->max_live_messages_unix_fds) + return DBUS_DISPATCH_COMPLETE; /* complete for now */ + + if (!_dbus_transport_try_to_authenticate (transport)) + { + if (_dbus_auth_do_work (transport->auth) == + DBUS_AUTH_STATE_WAITING_FOR_MEMORY) + return DBUS_DISPATCH_NEED_MEMORY; + else if (!_dbus_transport_try_to_authenticate (transport)) + return DBUS_DISPATCH_COMPLETE; + } + + if (!transport->unused_bytes_recovered && + !recover_unused_bytes (transport)) + return DBUS_DISPATCH_NEED_MEMORY; + + transport->unused_bytes_recovered = TRUE; + + if (!_dbus_message_loader_queue_messages (transport->loader)) + return DBUS_DISPATCH_NEED_MEMORY; + + if (_dbus_message_loader_peek_message (transport->loader) != NULL) + return DBUS_DISPATCH_DATA_REMAINS; + else + return DBUS_DISPATCH_COMPLETE; +} + +/** + * Processes data we've read while handling a watch, potentially + * converting some of it to messages and queueing those messages on + * the connection. + * + * @param transport the transport + * @returns #TRUE if we had enough memory to queue all messages + */ +dbus_bool_t +_dbus_transport_queue_messages (DBusTransport *transport) +{ + DBusDispatchStatus status; + +#if 0 + _dbus_verbose ("enter\n"); +#endif + + /* Queue any messages */ + while ((status = _dbus_transport_get_dispatch_status (transport)) == DBUS_DISPATCH_DATA_REMAINS) + { + DBusMessage *message; + DBusList *link; + + link = _dbus_message_loader_pop_message_link (transport->loader); + _dbus_assert (link != NULL); + + message = link->data; + + _dbus_verbose ("queueing received message %p\n", message); + + if (!_dbus_message_add_counter (message, transport->live_messages)) + { + _dbus_message_loader_putback_message_link (transport->loader, + link); + status = DBUS_DISPATCH_NEED_MEMORY; + break; + } + else + { + /* We didn't call the notify function when we added the counter, so + * catch up now. Since we have the connection's lock, it's desirable + * that we bypass the notify function and call this virtual method + * directly. */ + if (transport->vtable->live_messages_changed) + (* transport->vtable->live_messages_changed) (transport); + + /* pass ownership of link and message ref to connection */ + _dbus_connection_queue_received_message_link (transport->connection, + link); + } + } + + if (_dbus_message_loader_get_is_corrupted (transport->loader)) + { + _dbus_verbose ("Corrupted message stream, disconnecting\n"); + _dbus_transport_disconnect (transport); + } + + return status != DBUS_DISPATCH_NEED_MEMORY; +} + +/** + * See dbus_connection_set_max_message_size(). + * + * @param transport the transport + * @param size the max size of a single message + */ +void +_dbus_transport_set_max_message_size (DBusTransport *transport, + long size) +{ + _dbus_message_loader_set_max_message_size (transport->loader, size); +} + +/** + * See dbus_connection_set_max_message_unix_fds(). + * + * @param transport the transport + * @param n the max number of unix fds of a single message + */ +void +_dbus_transport_set_max_message_unix_fds (DBusTransport *transport, + long n) +{ + _dbus_message_loader_set_max_message_unix_fds (transport->loader, n); +} + +/** + * See dbus_connection_get_max_message_size(). + * + * @param transport the transport + * @returns max message size + */ +long +_dbus_transport_get_max_message_size (DBusTransport *transport) +{ + return _dbus_message_loader_get_max_message_size (transport->loader); +} + +/** + * See dbus_connection_get_max_message_unix_fds(). + * + * @param transport the transport + * @returns max message unix fds + */ +long +_dbus_transport_get_max_message_unix_fds (DBusTransport *transport) +{ + return _dbus_message_loader_get_max_message_unix_fds (transport->loader); +} + +/** + * See dbus_connection_set_max_received_size(). + * + * @param transport the transport + * @param size the max size of all incoming messages + */ +void +_dbus_transport_set_max_received_size (DBusTransport *transport, + long size) +{ + transport->max_live_messages_size = size; + _dbus_counter_set_notify (transport->live_messages, + transport->max_live_messages_size, + transport->max_live_messages_unix_fds, + live_messages_notify, + transport); +} + +/** + * See dbus_connection_set_max_received_unix_fds(). + * + * @param transport the transport + * @param n the max unix fds of all incoming messages + */ +void +_dbus_transport_set_max_received_unix_fds (DBusTransport *transport, + long n) +{ + transport->max_live_messages_unix_fds = n; + _dbus_counter_set_notify (transport->live_messages, + transport->max_live_messages_size, + transport->max_live_messages_unix_fds, + live_messages_notify, + transport); +} + +/** + * See dbus_connection_get_max_received_size(). + * + * @param transport the transport + * @returns max bytes for all live messages + */ +long +_dbus_transport_get_max_received_size (DBusTransport *transport) +{ + return transport->max_live_messages_size; +} + +/** + * See dbus_connection_set_max_received_unix_fds(). + * + * @param transport the transport + * @returns max unix fds for all live messages + */ +long +_dbus_transport_get_max_received_unix_fds (DBusTransport *transport) +{ + return transport->max_live_messages_unix_fds; +} + +/** + * See dbus_connection_get_unix_user(). + * + * @param transport the transport + * @param uid return location for the user ID + * @returns #TRUE if uid is filled in with a valid user ID + */ +dbus_bool_t +_dbus_transport_get_unix_user (DBusTransport *transport, + unsigned long *uid) +{ + DBusCredentials *auth_identity; + + *uid = _DBUS_INT32_MAX; /* better than some root or system user in + * case of bugs in the caller. Caller should + * never use this value on purpose, however. + */ + + if (!transport->authenticated) + return FALSE; + + auth_identity = _dbus_auth_get_identity (transport->auth); + + if (_dbus_credentials_include (auth_identity, + DBUS_CREDENTIAL_UNIX_USER_ID)) + { + *uid = _dbus_credentials_get_unix_uid (auth_identity); + return TRUE; + } + else + return FALSE; +} + +/** + * See dbus_connection_get_unix_process_id(). + * + * @param transport the transport + * @param pid return location for the process ID + * @returns #TRUE if uid is filled in with a valid process ID + */ +dbus_bool_t +_dbus_transport_get_unix_process_id (DBusTransport *transport, + unsigned long *pid) +{ + DBusCredentials *auth_identity; + + *pid = DBUS_PID_UNSET; /* Caller should never use this value on purpose, + * but we set it to a safe number, INT_MAX, + * just to root out possible bugs in bad callers. + */ + + if (!transport->authenticated) + return FALSE; + + auth_identity = _dbus_auth_get_identity (transport->auth); + + if (_dbus_credentials_include (auth_identity, + DBUS_CREDENTIAL_UNIX_PROCESS_ID)) + { + *pid = _dbus_credentials_get_pid (auth_identity); + return TRUE; + } + else + return FALSE; +} + +/** + * See dbus_connection_get_adt_audit_session_data(). + * + * @param transport the transport + * @param data return location for the ADT audit data + * @param data_size return length of audit data + * @returns #TRUE if audit data is filled in with a valid ucred + */ +dbus_bool_t +_dbus_transport_get_adt_audit_session_data (DBusTransport *transport, + void **data, + int *data_size) +{ + DBusCredentials *auth_identity; + + *data = NULL; + *data_size = 0; + + if (!transport->authenticated) + return FALSE; + + auth_identity = _dbus_auth_get_identity (transport->auth); + + if (_dbus_credentials_include (auth_identity, + DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID)) + { + *data = (void *) _dbus_credentials_get_adt_audit_data (auth_identity); + *data_size = _dbus_credentials_get_adt_audit_data_size (auth_identity); + return TRUE; + } + else + return FALSE; +} + +/** + * See dbus_connection_set_unix_user_function(). + * + * @param transport the transport + * @param function the predicate + * @param data data to pass to the predicate + * @param free_data_function function to free the data + * @param old_data the old user data to be freed + * @param old_free_data_function old free data function to free it with + */ +void +_dbus_transport_set_unix_user_function (DBusTransport *transport, + DBusAllowUnixUserFunction function, + void *data, + DBusFreeFunction free_data_function, + void **old_data, + DBusFreeFunction *old_free_data_function) +{ + *old_data = transport->unix_user_data; + *old_free_data_function = transport->free_unix_user_data; + + transport->unix_user_function = function; + transport->unix_user_data = data; + transport->free_unix_user_data = free_data_function; +} + +dbus_bool_t +_dbus_transport_get_linux_security_label (DBusTransport *transport, + char **label_p) +{ + DBusCredentials *auth_identity; + + *label_p = NULL; + + if (!transport->authenticated) + return FALSE; + + auth_identity = _dbus_auth_get_identity (transport->auth); + + if (_dbus_credentials_include (auth_identity, + DBUS_CREDENTIAL_LINUX_SECURITY_LABEL)) + { + /* If no memory, we are supposed to return TRUE and set NULL */ + *label_p = _dbus_strdup (_dbus_credentials_get_linux_security_label (auth_identity)); + + return TRUE; + } + else + { + return FALSE; + } +} + +/** + * If the transport has already been authenticated, return its + * credentials. If not, return #NULL. + * + * The caller must ref the returned credentials object if it wants to + * keep it. + */ +DBusCredentials * +_dbus_transport_get_credentials (DBusTransport *transport) +{ + if (!transport->authenticated) + return FALSE; + + return _dbus_auth_get_identity (transport->auth); +} + +/** + * See dbus_connection_get_windows_user(). + * + * @param transport the transport + * @param windows_sid_p return location for the user ID + * @returns #TRUE if user is available; the returned value may still be #NULL if no memory to copy it + */ +dbus_bool_t +_dbus_transport_get_windows_user (DBusTransport *transport, + char **windows_sid_p) +{ + DBusCredentials *auth_identity; + + *windows_sid_p = NULL; + + if (!transport->authenticated) + return FALSE; + + auth_identity = _dbus_auth_get_identity (transport->auth); + + if (_dbus_credentials_include (auth_identity, + DBUS_CREDENTIAL_WINDOWS_SID)) + { + /* If no memory, we are supposed to return TRUE and set NULL */ + *windows_sid_p = _dbus_strdup (_dbus_credentials_get_windows_sid (auth_identity)); + + return TRUE; + } + else + return FALSE; +} + +/** + * See dbus_connection_set_windows_user_function(). + * + * @param transport the transport + * @param function the predicate + * @param data data to pass to the predicate + * @param free_data_function function to free the data + * @param old_data the old user data to be freed + * @param old_free_data_function old free data function to free it with + */ + +void +_dbus_transport_set_windows_user_function (DBusTransport *transport, + DBusAllowWindowsUserFunction function, + void *data, + DBusFreeFunction free_data_function, + void **old_data, + DBusFreeFunction *old_free_data_function) +{ + *old_data = transport->windows_user_data; + *old_free_data_function = transport->free_windows_user_data; + + transport->windows_user_function = function; + transport->windows_user_data = data; + transport->free_windows_user_data = free_data_function; +} + +/** + * Sets the SASL authentication mechanisms supported by this transport. + * + * @param transport the transport + * @param mechanisms the #NULL-terminated array of mechanisms + * + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_transport_set_auth_mechanisms (DBusTransport *transport, + const char **mechanisms) +{ + return _dbus_auth_set_mechanisms (transport->auth, mechanisms); +} + +/** + * See dbus_connection_set_allow_anonymous() + * + * @param transport the transport + * @param value #TRUE to allow anonymous connection + */ +void +_dbus_transport_set_allow_anonymous (DBusTransport *transport, + dbus_bool_t value) +{ + transport->allow_anonymous = value != FALSE; +} + +/** + * Return how many file descriptors are pending in the loader + * + * @param transport the transport + */ +int +_dbus_transport_get_pending_fds_count (DBusTransport *transport) +{ + return _dbus_message_loader_get_pending_fds_count (transport->loader); +} + +/** + * Register a function to be called whenever the number of pending file + * descriptors in the loader change. + * + * @param transport the transport + * @param callback the callback + */ +void +_dbus_transport_set_pending_fds_function (DBusTransport *transport, + void (* callback) (void *), + void *data) +{ + _dbus_message_loader_set_pending_fds_function (transport->loader, + callback, data); +} + +#ifdef DBUS_ENABLE_STATS +void +_dbus_transport_get_stats (DBusTransport *transport, + dbus_uint32_t *queue_bytes, + dbus_uint32_t *queue_fds, + dbus_uint32_t *peak_queue_bytes, + dbus_uint32_t *peak_queue_fds) +{ + if (queue_bytes != NULL) + *queue_bytes = _dbus_counter_get_size_value (transport->live_messages); + + if (queue_fds != NULL) + *queue_fds = _dbus_counter_get_unix_fd_value (transport->live_messages); + + if (peak_queue_bytes != NULL) + *peak_queue_bytes = _dbus_counter_get_peak_size_value (transport->live_messages); + + if (peak_queue_fds != NULL) + *peak_queue_fds = _dbus_counter_get_peak_unix_fd_value (transport->live_messages); +} +#endif /* DBUS_ENABLE_STATS */ + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-transport.h b/src/3rdparty/libdbus/dbus/dbus-transport.h new file mode 100644 index 00000000..24fd1ac8 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-transport.h @@ -0,0 +1,121 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-transport.h DBusTransport object (internal to D-BUS implementation) + * + * Copyright (C) 2002, 2004 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_TRANSPORT_H +#define DBUS_TRANSPORT_H + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-connection.h> +#include <dbus/dbus-credentials.h> +#include <dbus/dbus-protocol.h> +#include <dbus/dbus-address.h> + +DBUS_BEGIN_DECLS + +typedef struct DBusTransport DBusTransport; + +DBusTransport* _dbus_transport_open (DBusAddressEntry *entry, + DBusError *error); +DBusTransport* _dbus_transport_ref (DBusTransport *transport); +void _dbus_transport_unref (DBusTransport *transport); +void _dbus_transport_disconnect (DBusTransport *transport); +dbus_bool_t _dbus_transport_get_is_connected (DBusTransport *transport); +dbus_bool_t _dbus_transport_peek_is_authenticated (DBusTransport *transport); +dbus_bool_t _dbus_transport_try_to_authenticate (DBusTransport *transport); +dbus_bool_t _dbus_transport_get_is_anonymous (DBusTransport *transport); +dbus_bool_t _dbus_transport_can_pass_unix_fd (DBusTransport *transport); + +const char* _dbus_transport_get_address (DBusTransport *transport); +const char* _dbus_transport_get_server_id (DBusTransport *transport); +dbus_bool_t _dbus_transport_handle_watch (DBusTransport *transport, + DBusWatch *watch, + unsigned int condition); +dbus_bool_t _dbus_transport_set_connection (DBusTransport *transport, + DBusConnection *connection); +void _dbus_transport_do_iteration (DBusTransport *transport, + unsigned int flags, + int timeout_milliseconds); +DBusDispatchStatus _dbus_transport_get_dispatch_status (DBusTransport *transport); +dbus_bool_t _dbus_transport_queue_messages (DBusTransport *transport); + +void _dbus_transport_set_max_message_size (DBusTransport *transport, + long size); +long _dbus_transport_get_max_message_size (DBusTransport *transport); +void _dbus_transport_set_max_received_size (DBusTransport *transport, + long size); +long _dbus_transport_get_max_received_size (DBusTransport *transport); + +void _dbus_transport_set_max_message_unix_fds (DBusTransport *transport, + long n); +long _dbus_transport_get_max_message_unix_fds (DBusTransport *transport); +void _dbus_transport_set_max_received_unix_fds(DBusTransport *transport, + long n); +long _dbus_transport_get_max_received_unix_fds(DBusTransport *transport); + +dbus_bool_t _dbus_transport_get_socket_fd (DBusTransport *transport, + DBusSocket *fd_p); +dbus_bool_t _dbus_transport_get_unix_user (DBusTransport *transport, + unsigned long *uid); +dbus_bool_t _dbus_transport_get_unix_process_id (DBusTransport *transport, + unsigned long *pid); +dbus_bool_t _dbus_transport_get_adt_audit_session_data (DBusTransport *transport, + void **data, + int *data_size); +void _dbus_transport_set_unix_user_function (DBusTransport *transport, + DBusAllowUnixUserFunction function, + void *data, + DBusFreeFunction free_data_function, + void **old_data, + DBusFreeFunction *old_free_data_function); +dbus_bool_t _dbus_transport_get_windows_user (DBusTransport *transport, + char **windows_sid_p); +dbus_bool_t _dbus_transport_get_linux_security_label (DBusTransport *transport, + char **label_p); +DBusCredentials *_dbus_transport_get_credentials (DBusTransport *transport); + +void _dbus_transport_set_windows_user_function (DBusTransport *transport, + DBusAllowWindowsUserFunction function, + void *data, + DBusFreeFunction free_data_function, + void **old_data, + DBusFreeFunction *old_free_data_function); +dbus_bool_t _dbus_transport_set_auth_mechanisms (DBusTransport *transport, + const char **mechanisms); +void _dbus_transport_set_allow_anonymous (DBusTransport *transport, + dbus_bool_t value); +int _dbus_transport_get_pending_fds_count (DBusTransport *transport); +void _dbus_transport_set_pending_fds_function (DBusTransport *transport, + void (* callback) (void *), + void *data); + +/* if DBUS_ENABLE_STATS */ +void _dbus_transport_get_stats (DBusTransport *transport, + dbus_uint32_t *queue_bytes, + dbus_uint32_t *queue_fds, + dbus_uint32_t *peak_queue_bytes, + dbus_uint32_t *peak_queue_fds); + +DBUS_END_DECLS + +#endif /* DBUS_TRANSPORT_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-types.h b/src/3rdparty/libdbus/dbus/dbus-types.h new file mode 100644 index 00000000..5ace2733 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-types.h @@ -0,0 +1,179 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-types.h types such as dbus_bool_t + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_TYPES_H +#define DBUS_TYPES_H + +#include <stddef.h> +#include <dbus/dbus-arch-deps.h> + +typedef dbus_uint32_t dbus_unichar_t; +/* boolean size must be fixed at 4 bytes due to wire protocol! */ +typedef dbus_uint32_t dbus_bool_t; + +/* Normally docs are in .c files, but there isn't a .c file for this. */ +/** + * @defgroup DBusTypes Basic types + * @ingroup DBus + * @brief dbus_bool_t, dbus_int32_t, etc. + * + * Typedefs for common primitive types. + * + * @{ + */ + +/** + * @typedef dbus_bool_t + * + * A boolean, valid values are #TRUE and #FALSE. + */ + +/** + * @typedef dbus_uint32_t + * + * A 32-bit unsigned integer on all platforms. + */ + +/** + * @typedef dbus_int32_t + * + * A 32-bit signed integer on all platforms. + */ + +/** + * @typedef dbus_uint16_t + * + * A 16-bit unsigned integer on all platforms. + */ + +/** + * @typedef dbus_int16_t + * + * A 16-bit signed integer on all platforms. + */ + + +/** + * @typedef dbus_uint64_t + * + * A 64-bit unsigned integer. + */ + +/** + * @typedef dbus_int64_t + * + * A 64-bit signed integer. + */ + +/** + * @def DBUS_HAVE_INT64 + * + * Always defined. + * + * In older libdbus versions, this would be undefined if there was no + * 64-bit integer type on that platform. libdbus no longer supports + * such platforms. + */ + +/** + * @def DBUS_INT64_CONSTANT + * + * Declare a 64-bit signed integer constant. The macro + * adds the necessary "LL" or whatever after the integer, + * giving a literal such as "325145246765LL" + */ + +/** + * @def DBUS_UINT64_CONSTANT + * + * Declare a 64-bit unsigned integer constant. The macro + * adds the necessary "ULL" or whatever after the integer, + * giving a literal such as "325145246765ULL" + */ + +/** + * @def DBUS_INT64_MODIFIER + * + * A string literal for a length modifier that is appropriate to print + * the #dbus_int64_t and #dbus_uint64_t types. + * For example, it might be an empty string, "l", "ll", or "I64". + * + * This modifier needs to be concatenated with a literal "%" and a + * conversion specifier that can print signed or unsigned integers, + * for example: + * + * @code + * dbus_int64_t i = -123; + * dbus_uint64_t u = 456; + * + * printf ("signed: %" DBUS_INT64_MODIFIER "d\n", i); + * printf ("unsigned decimal: %" DBUS_INT64_MODIFIER "u\n", u); + * printf ("unsigned hex: 0x%" DBUS_INT64_MODIFIER "x\n", x); + * @endcode + */ + +/** + * An 8-byte struct you could use to access int64 without having + * int64 support. Use #dbus_int64_t or #dbus_uint64_t instead. + */ +typedef struct +{ + dbus_uint32_t first32; /**< first 32 bits in the 8 bytes (beware endian issues) */ + dbus_uint32_t second32; /**< second 32 bits in the 8 bytes (beware endian issues) */ +} DBus8ByteStruct; + +/** + * A simple value union that lets you access bytes as if they + * were various types; useful when dealing with basic types via + * void pointers and varargs. + * + * This union also contains a pointer member (which can be used + * to retrieve a string from dbus_message_iter_get_basic(), for + * instance), so on future platforms it could conceivably be larger + * than 8 bytes. + */ +typedef union +{ + unsigned char bytes[8]; /**< as 8 individual bytes */ + dbus_int16_t i16; /**< as int16 */ + dbus_uint16_t u16; /**< as int16 */ + dbus_int32_t i32; /**< as int32 */ + dbus_uint32_t u32; /**< as int32 */ + dbus_bool_t bool_val; /**< as boolean */ + dbus_int64_t i64; /**< as int64 */ + dbus_uint64_t u64; /**< as int64 */ + DBus8ByteStruct eight; /**< as 8-byte struct */ + double dbl; /**< as double */ + unsigned char byt; /**< as byte */ + char *str; /**< as char* (string, object path or signature) */ + int fd; /**< as Unix file descriptor */ +} DBusBasicValue; + +/** @} */ + +#endif /* DBUS_TYPES_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-userdb.c b/src/3rdparty/libdbus/dbus/dbus-userdb.c new file mode 100644 index 00000000..452ff9d7 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-userdb.c @@ -0,0 +1,735 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-userdb.c User database abstraction + * + * Copyright (C) 2003, 2004 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <config.h> +#define DBUS_USERDB_INCLUDES_PRIVATE 1 +#include "dbus-userdb.h" +#include "dbus-hash.h" +#include "dbus-test.h" +#include "dbus-internals.h" +#include "dbus-protocol.h" +#include "dbus-credentials.h" +#include <string.h> + +/* It isn't obvious from its name, but this file is part of the Unix + * system-dependent part of libdbus. Windows has a parallel + * implementation of some of it in dbus-sysdeps-win.c. */ +#if defined(DBUS_WIN) || !defined(DBUS_UNIX) +#error "This file only makes sense on Unix OSs" +#endif + +/** + * @addtogroup DBusInternalsUtils + * @{ + */ + +static DBusUserInfo * +_dbus_user_info_ref (DBusUserInfo *info) +{ + _dbus_assert (info->refcount > 0); + _dbus_assert (info->refcount < SIZE_MAX); + info->refcount++; + return info; +} + +/** + * Decrements the reference count. If it reaches 0, + * frees the given #DBusUserInfo's members with _dbus_user_info_free() + * and also calls dbus_free() on the block itself + * + * @param info the info + */ +void +_dbus_user_info_unref (DBusUserInfo *info) +{ + if (info == NULL) /* hash table will pass NULL */ + return; + + _dbus_assert (info->refcount > 0); + _dbus_assert (info->refcount < SIZE_MAX); + + if (--info->refcount > 0) + return; + + _dbus_user_info_free (info); + dbus_free (info); +} + +/** + * Decrements the reference count. If it reaches 0, + * frees the given #DBusGroupInfo's members with _dbus_group_info_free() + * and also calls dbus_free() on the block itself + * + * @param info the info + */ +void +_dbus_group_info_unref (DBusGroupInfo *info) +{ + if (info == NULL) /* hash table will pass NULL */ + return; + + _dbus_assert (info->refcount > 0); + _dbus_assert (info->refcount < SIZE_MAX); + + if (--info->refcount > 0) + return; + + _dbus_group_info_free (info); + dbus_free (info); +} + +/** + * Frees the members of info + * (but not info itself) + * @param info the user info struct + */ +void +_dbus_user_info_free (DBusUserInfo *info) +{ + dbus_free (info->group_ids); + dbus_free (info->username); + dbus_free (info->homedir); +} + +/** + * Frees the members of info (but not info itself). + * + * @param info the group info + */ +void +_dbus_group_info_free (DBusGroupInfo *info) +{ + dbus_free (info->groupname); +} + +/** + * Checks if a given string is actually a number + * and converts it if it is + * + * @param str the string to check + * @param num the memory location of the unsigned long to fill in + * @returns TRUE if str is a number and num is filled in + */ +dbus_bool_t +_dbus_is_a_number (const DBusString *str, + unsigned long *num) +{ + int end; + + if (_dbus_string_parse_uint (str, 0, num, &end) && + end == _dbus_string_get_length (str)) + return TRUE; + else + return FALSE; +} + +/** + * Looks up a uid or username in the user database. Only one of name + * or UID can be provided. There are wrapper functions for this that + * are better to use, this one does no locking or anything on the + * database and otherwise sort of sucks. + * + * @param db the database + * @param uid the user ID or #DBUS_UID_UNSET + * @param username username or #NULL + * @param error error to fill in + * @returns the entry in the database (borrowed, do not free) + */ +const DBusUserInfo * +_dbus_user_database_lookup (DBusUserDatabase *db, + dbus_uid_t uid, + const DBusString *username, + DBusError *error) +{ + DBusUserInfo *info; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + _dbus_assert (uid != DBUS_UID_UNSET || username != NULL); + + /* See if the username is really a number */ + if (uid == DBUS_UID_UNSET) + { + unsigned long n; + + if (_dbus_is_a_number (username, &n)) + uid = n; + } + + if (uid != DBUS_UID_UNSET) + info = _dbus_hash_table_lookup_uintptr (db->users, uid); + else + info = _dbus_hash_table_lookup_string (db->users_by_name, _dbus_string_get_const_data (username)); + + if (info) + { + _dbus_verbose ("Using cache for UID "DBUS_UID_FORMAT" information\n", + info->uid); + return info; + } + else + { + if (uid != DBUS_UID_UNSET) + _dbus_verbose ("No cache for UID "DBUS_UID_FORMAT"\n", + uid); + else + _dbus_verbose ("No cache for user \"%s\"\n", + _dbus_string_get_const_data (username)); + + info = dbus_new0 (DBusUserInfo, 1); + if (info == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + info->refcount = 1; + + if (uid != DBUS_UID_UNSET) + { + if (!_dbus_user_info_fill_uid (info, uid, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_user_info_unref (info); + return NULL; + } + } + else + { + if (!_dbus_user_info_fill (info, username, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_user_info_unref (info); + return NULL; + } + } + + /* be sure we don't use these after here */ + uid = DBUS_UID_UNSET; + username = NULL; + + /* insert into hash */ + if (_dbus_hash_table_insert_uintptr (db->users, info->uid, info)) + { + _dbus_user_info_ref (info); + } + else + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_user_info_unref (info); + return NULL; + } + + if (_dbus_hash_table_insert_string (db->users_by_name, + info->username, + info)) + { + _dbus_user_info_ref (info); + } + else + { + _dbus_hash_table_remove_uintptr (db->users, info->uid); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_user_info_unref (info); + return NULL; + } + + _dbus_user_info_unref (info); + + /* Return a borrowed pointer to the DBusUserInfo owned by the + * hash tables */ + return info; + } +} + +/* Protected by _DBUS_LOCK_system_users */ +static dbus_bool_t database_locked = FALSE; +static DBusUserDatabase *system_db = NULL; +static DBusString process_username; +static DBusString process_homedir; + +static void +shutdown_system_db (void *data) +{ + if (system_db != NULL) + _dbus_user_database_unref (system_db); + system_db = NULL; + _dbus_string_free (&process_username); + _dbus_string_free (&process_homedir); +} + +static dbus_bool_t +init_system_db (void) +{ + _dbus_assert (database_locked); + + if (system_db == NULL) + { + DBusError error = DBUS_ERROR_INIT; + const DBusUserInfo *info; + + system_db = _dbus_user_database_new (); + if (system_db == NULL) + return FALSE; + + if (!_dbus_user_database_get_uid (system_db, + _dbus_getuid (), + &info, + &error)) + { + _dbus_user_database_unref (system_db); + system_db = NULL; + + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + return FALSE; + } + else + { + /* This really should not happen. */ + _dbus_warn ("Could not get password database information for UID of current process: %s", + error.message); + dbus_error_free (&error); + return FALSE; + } + } + + if (!_dbus_string_init (&process_username)) + { + _dbus_user_database_unref (system_db); + system_db = NULL; + return FALSE; + } + + if (!_dbus_string_init (&process_homedir)) + { + _dbus_string_free (&process_username); + _dbus_user_database_unref (system_db); + system_db = NULL; + return FALSE; + } + + if (!_dbus_string_append (&process_username, + info->username) || + !_dbus_string_append (&process_homedir, + info->homedir) || + !_dbus_register_shutdown_func (shutdown_system_db, NULL)) + { + _dbus_string_free (&process_username); + _dbus_string_free (&process_homedir); + _dbus_user_database_unref (system_db); + system_db = NULL; + return FALSE; + } + } + + return TRUE; +} + +/** + * Locks global system user database. + */ +dbus_bool_t +_dbus_user_database_lock_system (void) +{ + if (_DBUS_LOCK (system_users)) + { + database_locked = TRUE; + return TRUE; + } + else + { + return FALSE; + } +} + +/** + * Unlocks global system user database. + */ +void +_dbus_user_database_unlock_system (void) +{ + database_locked = FALSE; + _DBUS_UNLOCK (system_users); +} + +/** + * Gets the system global user database; + * must be called with lock held (_dbus_user_database_lock_system()). + * + * @returns the database or #NULL if no memory + */ +DBusUserDatabase* +_dbus_user_database_get_system (void) +{ + _dbus_assert (database_locked); + + init_system_db (); + + return system_db; +} + +/** + * Flushes the system global user database; + */ +void +_dbus_user_database_flush_system (void) +{ + if (!_dbus_user_database_lock_system ()) + { + /* nothing to flush */ + return; + } + + if (system_db != NULL) + _dbus_user_database_flush (system_db); + + _dbus_user_database_unlock_system (); +} + +/** + * Gets username of user owning current process. The returned string + * is valid until dbus_shutdown() is called. + * + * @param username place to store pointer to username + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_username_from_current_process (const DBusString **username) +{ + if (!_dbus_user_database_lock_system ()) + return FALSE; + + if (!init_system_db ()) + { + _dbus_user_database_unlock_system (); + return FALSE; + } + *username = &process_username; + _dbus_user_database_unlock_system (); + + return TRUE; +} + +/** + * Gets homedir of user owning current process. The returned string + * is valid until dbus_shutdown() is called. + * + * @param homedir place to store pointer to homedir + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_homedir_from_current_process (const DBusString **homedir) +{ + if (!_dbus_user_database_lock_system ()) + return FALSE; + + if (!init_system_db ()) + { + _dbus_user_database_unlock_system (); + return FALSE; + } + *homedir = &process_homedir; + _dbus_user_database_unlock_system (); + + return TRUE; +} + +/** + * Gets the home directory for the given user. + * + * @param uid the uid + * @param homedir string to append home directory to + * @returns #TRUE if user existed and we appended their homedir + */ +dbus_bool_t +_dbus_homedir_from_uid (dbus_uid_t uid, + DBusString *homedir) +{ + DBusUserDatabase *db; + const DBusUserInfo *info; + + if (uid == _dbus_getuid () && uid == _dbus_geteuid ()) + { + const char *from_environment; + + from_environment = _dbus_getenv ("HOME"); + + if (from_environment != NULL) + return _dbus_string_append (homedir, from_environment); + } + + /* FIXME: this can't distinguish ENOMEM from other errors */ + if (!_dbus_user_database_lock_system ()) + return FALSE; + + db = _dbus_user_database_get_system (); + if (db == NULL) + { + _dbus_user_database_unlock_system (); + return FALSE; + } + + if (!_dbus_user_database_get_uid (db, uid, + &info, NULL)) + { + _dbus_user_database_unlock_system (); + return FALSE; + } + + if (!_dbus_string_append (homedir, info->homedir)) + { + _dbus_user_database_unlock_system (); + return FALSE; + } + + _dbus_user_database_unlock_system (); + return TRUE; +} + +/** + * Adds the credentials corresponding to the given username. + * + * Used among other purposes to parses a desired identity provided + * from a client in the auth protocol. On UNIX this means parsing a + * UID, on Windows probably parsing an SID string. + * + * @todo this is broken because it treats OOM and parse error + * the same way. Needs a #DBusError. + * + * @param credentials credentials to fill in + * @param username the username + * @returns #TRUE if the username existed and we got some credentials + */ +dbus_bool_t +_dbus_credentials_add_from_user (DBusCredentials *credentials, + const DBusString *username, + DBusCredentialsAddFlags flags, + DBusError *error) +{ + DBusUserDatabase *db; + const DBusUserInfo *info; + unsigned long uid = DBUS_UID_UNSET; + + /* Fast-path for the common case: if the "username" is all-numeric, + * then it's a Unix uid. This is true regardless of whether that uid + * exists in NSS or /etc/passwd or equivalent. */ + if (_dbus_is_a_number (username, &uid)) + { + _DBUS_STATIC_ASSERT (sizeof (uid) == sizeof (dbus_uid_t)); + + if (_dbus_credentials_add_unix_uid (credentials, uid)) + { + return TRUE; + } + else + { + _DBUS_SET_OOM (error); + return FALSE; + } + } + + /* If we aren't allowed to look in NSS or /etc/passwd, fail now. */ + if (!(flags & DBUS_CREDENTIALS_ADD_FLAGS_USER_DATABASE)) + { + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Expected a numeric Unix uid"); + return FALSE; + } + + if (!_dbus_user_database_lock_system ()) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + db = _dbus_user_database_get_system (); + if (db == NULL) + { + _dbus_user_database_unlock_system (); + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_user_database_get_username (db, username, + &info, error)) + { + _dbus_user_database_unlock_system (); + return FALSE; + } + + if (!_dbus_credentials_add_unix_uid(credentials, info->uid)) + { + _dbus_user_database_unlock_system (); + _DBUS_SET_OOM (error); + return FALSE; + } + + _dbus_user_database_unlock_system (); + return TRUE; +} + +/** + * Creates a new user database object used to look up and + * cache user information. + * @returns new database, or #NULL on out of memory + */ +DBusUserDatabase* +_dbus_user_database_new (void) +{ + DBusUserDatabase *db; + + db = dbus_new0 (DBusUserDatabase, 1); + if (db == NULL) + return NULL; + + db->refcount = 1; + + db->users = _dbus_hash_table_new (DBUS_HASH_UINTPTR, + NULL, (DBusFreeFunction) _dbus_user_info_unref); + + if (db->users == NULL) + goto failed; + + db->groups = _dbus_hash_table_new (DBUS_HASH_UINTPTR, + NULL, (DBusFreeFunction) _dbus_group_info_unref); + + if (db->groups == NULL) + goto failed; + + db->users_by_name = _dbus_hash_table_new (DBUS_HASH_STRING, + NULL, (DBusFreeFunction) _dbus_user_info_unref); + if (db->users_by_name == NULL) + goto failed; + + db->groups_by_name = _dbus_hash_table_new (DBUS_HASH_STRING, + NULL, (DBusFreeFunction) _dbus_group_info_unref); + if (db->groups_by_name == NULL) + goto failed; + + return db; + + failed: + _dbus_user_database_unref (db); + return NULL; +} + +/** + * Flush all information out of the user database. + */ +void +_dbus_user_database_flush (DBusUserDatabase *db) +{ + _dbus_hash_table_remove_all(db->users_by_name); + _dbus_hash_table_remove_all(db->groups_by_name); + _dbus_hash_table_remove_all(db->users); + _dbus_hash_table_remove_all(db->groups); +} + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +/** + * Increments refcount of user database. + * @param db the database + * @returns the database + */ +DBusUserDatabase * +_dbus_user_database_ref (DBusUserDatabase *db) +{ + _dbus_assert (db->refcount > 0); + + db->refcount += 1; + + return db; +} +#endif /* DBUS_ENABLE_EMBEDDED_TESTS */ + +/** + * Decrements refcount of user database. + * @param db the database + */ +void +_dbus_user_database_unref (DBusUserDatabase *db) +{ + _dbus_assert (db->refcount > 0); + + db->refcount -= 1; + if (db->refcount == 0) + { + if (db->users) + _dbus_hash_table_unref (db->users); + + if (db->groups) + _dbus_hash_table_unref (db->groups); + + if (db->users_by_name) + _dbus_hash_table_unref (db->users_by_name); + + if (db->groups_by_name) + _dbus_hash_table_unref (db->groups_by_name); + + dbus_free (db); + } +} + +/** + * Gets the user information for the given UID, + * returned user info should not be freed. + * + * @param db user database + * @param uid the user ID + * @param info return location for const ref to user info + * @param error error location + * @returns #FALSE if error is set + */ +dbus_bool_t +_dbus_user_database_get_uid (DBusUserDatabase *db, + dbus_uid_t uid, + const DBusUserInfo **info, + DBusError *error) +{ + *info = _dbus_user_database_lookup (db, uid, NULL, error); + return *info != NULL; +} + +/** + * Gets the user information for the given username. + * + * @param db user database + * @param username the user name + * @param info return location for const ref to user info + * @param error error location + * @returns #FALSE if error is set + */ +dbus_bool_t +_dbus_user_database_get_username (DBusUserDatabase *db, + const DBusString *username, + const DBusUserInfo **info, + DBusError *error) +{ + *info = _dbus_user_database_lookup (db, DBUS_UID_UNSET, username, error); + return *info != NULL; +} + +/** @} */ + +/* Tests in dbus-userdb-util.c */ diff --git a/src/3rdparty/libdbus/dbus/dbus-userdb.h b/src/3rdparty/libdbus/dbus/dbus-userdb.h new file mode 100644 index 00000000..d37d2433 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-userdb.h @@ -0,0 +1,124 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-userdb.h User database abstraction + * + * Copyright (C) 2003 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_USERDB_H +#define DBUS_USERDB_H + +#include <dbus/dbus-sysdeps-unix.h> + +#ifdef DBUS_WIN +#error "Don't include this on Windows" +#endif + +DBUS_BEGIN_DECLS + +typedef struct DBusUserDatabase DBusUserDatabase; + +#ifdef DBUS_USERDB_INCLUDES_PRIVATE +#include <dbus/dbus-hash.h> + +/** + * Internals of DBusUserDatabase + */ +struct DBusUserDatabase +{ + int refcount; /**< Reference count */ + + DBusHashTable *users; /**< Users in the database by UID */ + DBusHashTable *groups; /**< Groups in the database by GID */ + DBusHashTable *users_by_name; /**< Users in the database by name */ + DBusHashTable *groups_by_name; /**< Groups in the database by name */ + +}; + + +DBusUserDatabase* _dbus_user_database_new (void); +DBusUserDatabase* _dbus_user_database_ref (DBusUserDatabase *db); +void _dbus_user_database_flush (DBusUserDatabase *db); +void _dbus_user_database_unref (DBusUserDatabase *db); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_user_database_get_uid (DBusUserDatabase *db, + dbus_uid_t uid, + const DBusUserInfo **info, + DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_user_database_get_username (DBusUserDatabase *db, + const DBusString *username, + const DBusUserInfo **info, + DBusError *error); +DBUS_PRIVATE_EXPORT +const DBusUserInfo *_dbus_user_database_lookup (DBusUserDatabase *db, + dbus_uid_t uid, + const DBusString *username, + DBusError *error); +DBUS_PRIVATE_EXPORT +const DBusGroupInfo* _dbus_user_database_lookup_group (DBusUserDatabase *db, + dbus_gid_t gid, + const DBusString *groupname, + DBusError *error); + +void _dbus_user_info_unref (DBusUserInfo *info); +DBUS_PRIVATE_EXPORT +void _dbus_group_info_unref (DBusGroupInfo *info); +#endif /* DBUS_USERDB_INCLUDES_PRIVATE */ + +DBUS_PRIVATE_EXPORT +DBusUserDatabase* _dbus_user_database_get_system (void); +DBUS_PRIVATE_EXPORT _DBUS_WARN_UNUSED_RESULT +dbus_bool_t _dbus_user_database_lock_system (void); +DBUS_PRIVATE_EXPORT +void _dbus_user_database_unlock_system (void); +void _dbus_user_database_flush_system (void); + +dbus_bool_t _dbus_get_user_id (const DBusString *username, + dbus_uid_t *uid); +dbus_bool_t _dbus_get_group_id (const DBusString *group_name, + dbus_gid_t *gid); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_get_user_id_and_primary_group (const DBusString *username, + dbus_uid_t *uid_p, + dbus_gid_t *gid_p); +dbus_bool_t _dbus_groups_from_uid (dbus_uid_t uid, + dbus_gid_t **group_ids, + int *n_group_ids, + DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_is_console_user (dbus_uid_t uid, + DBusError *error); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_is_a_number (const DBusString *str, + unsigned long *num); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_username_from_current_process (const DBusString **username); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_homedir_from_current_process (const DBusString **homedir); +dbus_bool_t _dbus_homedir_from_uid (dbus_uid_t uid, + DBusString *homedir); + +DBUS_END_DECLS + +#endif /* DBUS_USERDB_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-uuidgen.c b/src/3rdparty/libdbus/dbus/dbus-uuidgen.c new file mode 100644 index 00000000..4a5aa13f --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-uuidgen.c @@ -0,0 +1,132 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-uuidgen.c The guts of the dbus-uuidgen binary live in libdbus, in this file. + * + * Copyright (C) 2006 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <config.h> +#include "dbus-uuidgen.h" +#include "dbus-internals.h" +#include "dbus-string.h" +#include "dbus-protocol.h" + +#ifdef DBUS_WIN +#error "dbus-uuidgen should not be needed on Windows" +#endif + +/** + * @defgroup DBusInternalsUuidgen dbus-uuidgen implementation + * @ingroup DBusInternals + * @brief Functions for dbus-uuidgen binary + * + * These are not considered part of the ABI, and if you call them + * you will get screwed by future changes. + * + * @{ + */ + +static dbus_bool_t +return_uuid (DBusGUID *uuid, + char **uuid_p, + DBusError *error) +{ + if (uuid_p) + { + DBusString encoded; + + if (!_dbus_string_init (&encoded)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_uuid_encode (uuid, &encoded) || + !_dbus_string_steal_data (&encoded, uuid_p)) + { + _DBUS_SET_OOM (error); + _dbus_string_free (&encoded); + return FALSE; + } + _dbus_string_free (&encoded); + } + return TRUE; +} + +/** + * For use by the dbus-uuidgen binary ONLY, do not call this. + * We can and will change this function without modifying + * the libdbus soname. + * + * @param filename the file or #NULL for the machine ID file + * @param uuid_p out param to return the uuid + * @param create_if_not_found whether to create it if not already there + * @param error error return + * @returns #FALSE if error is set + */ +dbus_bool_t +_dbus_get_uuid (const char *filename, + char **uuid_p, + dbus_bool_t create_if_not_found, + DBusError *error) +{ + DBusGUID uuid; + + if (filename) + { + DBusString filename_str; + _dbus_string_init_const (&filename_str, filename); + if (!_dbus_read_uuid_file (&filename_str, &uuid, create_if_not_found, error)) + goto error; + } + else + { + if (!_dbus_read_local_machine_uuid (&uuid, create_if_not_found, error)) + goto error; + } + + if (!return_uuid(&uuid, uuid_p, error)) + goto error; + + return TRUE; + + error: + _DBUS_ASSERT_ERROR_IS_SET (error); + return FALSE; +} + +/** + * @param uuid_p out param to return the uuid + * @param error location to store reason for failure + * @returns #TRUE on success + */ +dbus_bool_t +_dbus_create_uuid (char **uuid_p, + DBusError *error) +{ + DBusGUID uuid; + + if (!_dbus_generate_uuid (&uuid, error)) + return FALSE; + + return return_uuid (&uuid, uuid_p, error); +} + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-uuidgen.h b/src/3rdparty/libdbus/dbus/dbus-uuidgen.h new file mode 100644 index 00000000..b74d9e0e --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-uuidgen.h @@ -0,0 +1,49 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-uuidgen.h The guts of the dbus-uuidgen binary live in libdbus, in this file. + * + * Copyright (C) 2006 Red Hat, Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifdef DBUS_INSIDE_DBUS_H +#error "You can't include dbus-uuidgen.h in the public header dbus.h" +#endif + +#ifndef DBUS_UUIDGEN_H +#define DBUS_UUIDGEN_H + +#include <dbus/dbus-macros-internal.h> +#include <dbus/dbus-types.h> +#include <dbus/dbus-errors.h> + +DBUS_BEGIN_DECLS + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_get_uuid (const char *filename, + char **uuid_p, + dbus_bool_t create_if_not_found, + DBusError *error); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_create_uuid (char **uuid_p, + DBusError *error); + +DBUS_END_DECLS + +#endif /* DBUS_UUIDGEN_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus-valgrind-internal.h b/src/3rdparty/libdbus/dbus/dbus-valgrind-internal.h new file mode 100644 index 00000000..4c3f3ff7 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-valgrind-internal.h @@ -0,0 +1,69 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-valgrind-internal.h - valgrind glue + * + * Copyright © 2011 Nokia Corporation + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef DBUS_VALGRIND_INTERNAL_H +#define DBUS_VALGRIND_INTERNAL_H + +#include "config.h" +#include "dbus-internals.h" + +#ifdef WITH_VALGRIND +# include <memcheck.h> +# include <valgrind.h> +#else +# define VALGRIND_CREATE_MEMPOOL(_1, _2, _3) do { } while (0) +# define VALGRIND_DESTROY_MEMPOOL(_1) do { } while (0) +# define VALGRIND_MEMPOOL_ALLOC(_1, _2, _3) do { } while (0) +# define VALGRIND_MEMPOOL_FREE(_1, _2) do { } while (0) + +/* Recent gcc will warn if you have a statement that's just a macro + * expanding to (0), but not if you have an inline stub function that + * always returns 0, so let's do the latter. */ +static inline int +VALGRIND_MAKE_MEM_UNDEFINED (void *addr, + size_t len) +{ + return 0; +} + +static inline int +VALGRIND_PRINTF (const char *format, + ...) +{ + return 0; +} + +static inline int +VALGRIND_PRINTF_BACKTRACE (const char *format, + ...) +{ + return 0; +} + +# define RUNNING_ON_VALGRIND 0 +#endif /* WITH_VALGRIND */ + +#endif /* header guard */ diff --git a/src/3rdparty/libdbus/dbus/dbus-watch.c b/src/3rdparty/libdbus/dbus/dbus-watch.c new file mode 100644 index 00000000..76298ba1 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-watch.c @@ -0,0 +1,764 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-watch.c DBusWatch implementation + * + * Copyright (C) 2002, 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "dbus-internals.h" +#include "dbus-watch.h" +#include "dbus-list.h" + +/** + * @defgroup DBusWatchInternals DBusWatch implementation details + * @ingroup DBusInternals + * @brief implementation details for DBusWatch + * + * @{ + */ + +/** + * Implementation of DBusWatch + */ +struct DBusWatch +{ + int refcount; /**< Reference count */ + DBusPollable fd; /**< File descriptor. */ + unsigned int flags; /**< Conditions to watch. */ + + DBusWatchHandler handler; /**< Watch handler. */ + void *handler_data; /**< Watch handler data. */ + DBusFreeFunction free_handler_data_function; /**< Free the watch handler data. */ + + void *data; /**< Application data. */ + DBusFreeFunction free_data_function; /**< Free the application data. */ + unsigned int enabled : 1; /**< Whether it's enabled. */ + unsigned int oom_last_time : 1; /**< Whether it was OOM last time. */ +}; + +dbus_bool_t +_dbus_watch_get_enabled (DBusWatch *watch) +{ + return watch->enabled; +} + +dbus_bool_t +_dbus_watch_get_oom_last_time (DBusWatch *watch) +{ + return watch->oom_last_time; +} + +void +_dbus_watch_set_oom_last_time (DBusWatch *watch, + dbus_bool_t oom) +{ + watch->oom_last_time = oom; +} + +/** + * Creates a new DBusWatch. Used to add a file descriptor to be polled + * by a main loop. + * + * @param fd the file descriptor to be watched. + * @param flags the conditions to watch for on the descriptor. + * @param enabled the initial enabled state + * @param handler the handler function + * @param data data for handler function + * @param free_data_function function to free the data + * @returns the new DBusWatch object. + */ +DBusWatch* +_dbus_watch_new (DBusPollable fd, + unsigned int flags, + dbus_bool_t enabled, + DBusWatchHandler handler, + void *data, + DBusFreeFunction free_data_function) +{ + DBusWatch *watch; + +#define VALID_WATCH_FLAGS (DBUS_WATCH_WRITABLE | DBUS_WATCH_READABLE) + + _dbus_assert ((flags & VALID_WATCH_FLAGS) == flags); + + watch = dbus_new0 (DBusWatch, 1); + if (watch == NULL) + return NULL; + + watch->refcount = 1; + watch->fd = fd; + watch->flags = flags; + watch->enabled = enabled; + + watch->handler = handler; + watch->handler_data = data; + watch->free_handler_data_function = free_data_function; + + return watch; +} + +/** + * Increments the reference count of a DBusWatch object. + * + * @param watch the watch object. + * @returns the watch object. + */ +DBusWatch * +_dbus_watch_ref (DBusWatch *watch) +{ + watch->refcount += 1; + + return watch; +} + +/** + * Decrements the reference count of a DBusWatch object + * and finalizes the object if the count reaches zero. + * + * @param watch the watch object. + */ +void +_dbus_watch_unref (DBusWatch *watch) +{ + _dbus_assert (watch != NULL); + _dbus_assert (watch->refcount > 0); + + watch->refcount -= 1; + if (watch->refcount == 0) + { + if (_dbus_pollable_is_valid (watch->fd)) + _dbus_warn ("this watch should have been invalidated"); + + dbus_watch_set_data (watch, NULL, NULL); /* call free_data_function */ + + if (watch->free_handler_data_function) + (* watch->free_handler_data_function) (watch->handler_data); + + dbus_free (watch); + } +} + +/** + * Clears the file descriptor from a now-invalid watch object so that + * no one tries to use it. This is because a watch may stay alive due + * to reference counts after the file descriptor is closed. + * Invalidation makes it easier to catch bugs. It also + * keeps people from doing dorky things like assuming file descriptors + * are unique (never recycled). + * + * @param watch the watch object. + */ +void +_dbus_watch_invalidate (DBusWatch *watch) +{ + _dbus_pollable_invalidate (&watch->fd); + watch->flags = 0; +} + +/** + * Sanitizes the given condition so that it only contains + * flags that the DBusWatch requested. e.g. if the + * watch is a DBUS_WATCH_READABLE watch then + * DBUS_WATCH_WRITABLE will be stripped from the condition. + * + * @param watch the watch object. + * @param condition address of the condition to sanitize. + */ +void +_dbus_watch_sanitize_condition (DBusWatch *watch, + unsigned int *condition) +{ + if (!(watch->flags & DBUS_WATCH_READABLE)) + *condition &= ~DBUS_WATCH_READABLE; + if (!(watch->flags & DBUS_WATCH_WRITABLE)) + *condition &= ~DBUS_WATCH_WRITABLE; +} + + +/** + * @typedef DBusWatchList + * + * Opaque data type representing a list of watches + * and a set of DBusAddWatchFunction/DBusRemoveWatchFunction. + * Automatically handles removing/re-adding watches + * when the DBusAddWatchFunction is updated or changed. + * Holds a reference count to each watch. + * + * Used in the implementation of both DBusServer and + * DBusClient. + * + */ + +/** + * DBusWatchList implementation details. All fields + * are private. + * + */ +struct DBusWatchList +{ + DBusList *watches; /**< Watch objects. */ + + DBusAddWatchFunction add_watch_function; /**< Callback for adding a watch. */ + DBusRemoveWatchFunction remove_watch_function; /**< Callback for removing a watch. */ + DBusWatchToggledFunction watch_toggled_function; /**< Callback on toggling enablement */ + void *watch_data; /**< Data for watch callbacks */ + DBusFreeFunction watch_free_data_function; /**< Free function for watch callback data */ +}; + +/** + * Creates a new watch list. Returns #NULL if insufficient + * memory exists. + * + * @returns the new watch list, or #NULL on failure. + */ +DBusWatchList* +_dbus_watch_list_new (void) +{ + DBusWatchList *watch_list; + + watch_list = dbus_new0 (DBusWatchList, 1); + if (watch_list == NULL) + return NULL; + + return watch_list; +} + +/** + * Frees a DBusWatchList. + * + * @param watch_list the watch list. + */ +void +_dbus_watch_list_free (DBusWatchList *watch_list) +{ + /* free watch_data and removes watches as a side effect */ + _dbus_watch_list_set_functions (watch_list, + NULL, NULL, NULL, NULL, NULL); + + _dbus_list_clear_full (&watch_list->watches, + (DBusFreeFunction) _dbus_watch_unref); + + dbus_free (watch_list); +} + +#ifdef DBUS_ENABLE_VERBOSE_MODE +static const char* +watch_flags_to_string (int flags) +{ + const char *watch_type; + + if ((flags & DBUS_WATCH_READABLE) && + (flags & DBUS_WATCH_WRITABLE)) + watch_type = "readwrite"; + else if (flags & DBUS_WATCH_READABLE) + watch_type = "read"; + else if (flags & DBUS_WATCH_WRITABLE) + watch_type = "write"; + else + watch_type = "not read or write"; + return watch_type; +} +#endif /* DBUS_ENABLE_VERBOSE_MODE */ + +/** + * Sets the watch functions. This function is the "backend" + * for dbus_connection_set_watch_functions() and + * dbus_server_set_watch_functions(). + * + * @param watch_list the watch list. + * @param add_function the add watch function. + * @param remove_function the remove watch function. + * @param toggled_function function on toggling enabled flag, or #NULL + * @param data the data for those functions. + * @param free_data_function the function to free the data. + * @returns #FALSE if not enough memory + * + */ +dbus_bool_t +_dbus_watch_list_set_functions (DBusWatchList *watch_list, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + DBusWatchToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function) +{ + /* Add watches with the new watch function, failing on OOM */ + if (add_function != NULL) + { + DBusList *link; + + link = _dbus_list_get_first_link (&watch_list->watches); + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (&watch_list->watches, + link); +#ifdef DBUS_ENABLE_VERBOSE_MODE + DBusWatch *watch = link->data; + + _dbus_verbose ("Adding a %s watch on fd %" DBUS_POLLABLE_FORMAT " using newly-set add watch function\n", + watch_flags_to_string (dbus_watch_get_flags (link->data)), + _dbus_pollable_printable (watch->fd)); +#endif + + if (!(* add_function) (link->data, data)) + { + /* remove it all again and return FALSE */ + DBusList *link2; + + link2 = _dbus_list_get_first_link (&watch_list->watches); + while (link2 != link) + { + DBusList *next2 = _dbus_list_get_next_link (&watch_list->watches, + link2); +#ifdef DBUS_ENABLE_VERBOSE_MODE + DBusWatch *watch2 = link2->data; + + _dbus_verbose ("Removing watch on fd %" DBUS_POLLABLE_FORMAT " using newly-set remove function because initial add failed\n", + _dbus_pollable_printable (watch2->fd)); +#endif + + (* remove_function) (link2->data, data); + + link2 = next2; + } + + return FALSE; + } + + link = next; + } + } + + /* Remove all current watches from previous watch handlers */ + + if (watch_list->remove_watch_function != NULL) + { + _dbus_verbose ("Removing all pre-existing watches\n"); + + _dbus_list_foreach (&watch_list->watches, + (DBusForeachFunction) watch_list->remove_watch_function, + watch_list->watch_data); + } + + if (watch_list->watch_free_data_function != NULL) + (* watch_list->watch_free_data_function) (watch_list->watch_data); + + watch_list->add_watch_function = add_function; + watch_list->remove_watch_function = remove_function; + watch_list->watch_toggled_function = toggled_function; + watch_list->watch_data = data; + watch_list->watch_free_data_function = free_data_function; + + return TRUE; +} + +/** + * Adds a new watch to the watch list, invoking the + * application DBusAddWatchFunction if appropriate. + * + * @param watch_list the watch list. + * @param watch the watch to add. + * @returns #TRUE on success, #FALSE if no memory. + */ +dbus_bool_t +_dbus_watch_list_add_watch (DBusWatchList *watch_list, + DBusWatch *watch) +{ + if (!_dbus_list_append (&watch_list->watches, watch)) + return FALSE; + + _dbus_watch_ref (watch); + + if (watch_list->add_watch_function != NULL) + { + _dbus_verbose ("Adding watch on fd %" DBUS_POLLABLE_FORMAT "\n", + _dbus_pollable_printable (watch->fd)); + + if (!(* watch_list->add_watch_function) (watch, + watch_list->watch_data)) + { + _dbus_list_remove_last (&watch_list->watches, watch); + _dbus_watch_unref (watch); + return FALSE; + } + } + + return TRUE; +} + +/** + * Removes a watch from the watch list, invoking the + * application's DBusRemoveWatchFunction if appropriate. + * + * @param watch_list the watch list. + * @param watch the watch to remove. + */ +void +_dbus_watch_list_remove_watch (DBusWatchList *watch_list, + DBusWatch *watch) +{ + if (!_dbus_list_remove (&watch_list->watches, watch)) + _dbus_assert_not_reached ("Nonexistent watch was removed"); + + if (watch_list->remove_watch_function != NULL) + { + _dbus_verbose ("Removing watch on fd %" DBUS_POLLABLE_FORMAT "\n", + _dbus_pollable_printable (watch->fd)); + + (* watch_list->remove_watch_function) (watch, + watch_list->watch_data); + } + + _dbus_watch_unref (watch); +} + +/** + * Sets a watch to the given enabled state, invoking the + * application's DBusWatchToggledFunction if appropriate. + * + * @param watch_list the watch list. + * @param watch the watch to toggle. + * @param enabled #TRUE to enable + */ +void +_dbus_watch_list_toggle_watch (DBusWatchList *watch_list, + DBusWatch *watch, + dbus_bool_t enabled) +{ + enabled = !!enabled; + + if (enabled == watch->enabled) + return; + + watch->enabled = enabled; + + if (watch_list->watch_toggled_function != NULL) + { + _dbus_verbose ("Toggling watch %p on fd %" DBUS_POLLABLE_FORMAT " to %d\n", + watch, + _dbus_pollable_printable (watch->fd), + watch->enabled); + + (* watch_list->watch_toggled_function) (watch, + watch_list->watch_data); + } +} + +/** + * Sets all watches to the given enabled state, invoking the + * application's DBusWatchToggledFunction if appropriate. + * + * @param watch_list the watch list. + * @param enabled #TRUE to enable + */ +void +_dbus_watch_list_toggle_all_watches (DBusWatchList *watch_list, + dbus_bool_t enabled) +{ + DBusList *link; + + for (link = _dbus_list_get_first_link (&watch_list->watches); + link != NULL; + link = _dbus_list_get_next_link (&watch_list->watches, link)) + { + _dbus_watch_list_toggle_watch (watch_list, link->data, enabled); + } +} + +/** + * Sets the handler for the watch. + * + * @todo this function only exists because of the weird + * way connection watches are done, see the note + * in docs for _dbus_connection_handle_watch(). + * + * @param watch the watch + * @param handler the new handler + * @param data the data + * @param free_data_function free data with this + */ +void +_dbus_watch_set_handler (DBusWatch *watch, + DBusWatchHandler handler, + void *data, + DBusFreeFunction free_data_function) +{ + if (watch->free_handler_data_function) + (* watch->free_handler_data_function) (watch->handler_data); + + watch->handler = handler; + watch->handler_data = data; + watch->free_handler_data_function = free_data_function; +} + +/** @} */ + +/** + * @defgroup DBusWatch DBusWatch + * @ingroup DBus + * @brief Object representing a file descriptor to be watched. + * + * Types and functions related to DBusWatch. A watch represents + * a file descriptor that the main loop needs to monitor, + * as in Qt's QSocketNotifier or GLib's g_io_add_watch(). + * + * Use dbus_connection_set_watch_functions() or dbus_server_set_watch_functions() + * to be notified when libdbus needs to add or remove watches. + * + * @{ + */ + +/** + * @typedef DBusWatch + * + * Opaque object representing a file descriptor + * to be watched for changes in readability, + * writability, or hangup. + */ + +/** + * Deprecated former name of dbus_watch_get_unix_fd(). + * + * @param watch the DBusWatch object. + * @returns the file descriptor to watch. + */ +int +dbus_watch_get_fd (DBusWatch *watch) +{ + _dbus_return_val_if_fail (watch != NULL, -1); + + return dbus_watch_get_unix_fd(watch); +} + +/** + * Returns a UNIX file descriptor to be watched, + * which may be a pipe, socket, or other type of + * descriptor. On UNIX this is preferred to + * dbus_watch_get_socket() since it works with + * more kinds of #DBusWatch. + * + * Always returns -1 on Windows. On Windows you use + * dbus_watch_get_socket() to get a Winsock socket to watch. + * + * @param watch the DBusWatch object. + * @returns the file descriptor to watch. + */ +int +dbus_watch_get_unix_fd (DBusWatch *watch) +{ + _dbus_return_val_if_fail (watch != NULL, -1); + + /* FIXME remove #ifdef and do this on a lower level + * (watch should have set_socket and set_unix_fd and track + * which it has, and the transport should provide the + * appropriate watch type) + */ +#ifdef DBUS_UNIX + return watch->fd; +#else + return dbus_watch_get_socket( watch ); +#endif +} + +/** + * Returns a socket to be watched, on UNIX this will return -1 if our + * transport is not socket-based so dbus_watch_get_unix_fd() is + * preferred. + * + * On Windows, dbus_watch_get_unix_fd() returns -1 but this function + * returns a Winsock socket (assuming the transport is socket-based, + * as it always is for now). + * + * @param watch the DBusWatch object. + * @returns the socket to watch. + */ +int +dbus_watch_get_socket (DBusWatch *watch) +{ + _dbus_return_val_if_fail (watch != NULL, -1); + +#ifdef DBUS_UNIX + return watch->fd; +#else + return _dbus_socket_get_int (watch->fd); +#endif +} + +DBusSocket +_dbus_watch_get_socket (DBusWatch *watch) +{ + DBusSocket s; + + _dbus_assert (watch != NULL); + +#ifdef DBUS_UNIX + s.fd = watch->fd; +#else + s = watch->fd; +#endif + + return s; +} + +DBusPollable +_dbus_watch_get_pollable (DBusWatch *watch) +{ + _dbus_assert (watch != NULL); + + return watch->fd; +} + +/** + * Gets flags from DBusWatchFlags indicating + * what conditions should be monitored on the + * file descriptor. + * + * The flags returned will only contain DBUS_WATCH_READABLE + * and DBUS_WATCH_WRITABLE, never DBUS_WATCH_HANGUP or + * DBUS_WATCH_ERROR; all watches implicitly include a watch + * for hangups, errors, and other exceptional conditions. + * + * @param watch the DBusWatch object. + * @returns the conditions to watch. + */ +unsigned int +dbus_watch_get_flags (DBusWatch *watch) +{ + _dbus_return_val_if_fail (watch != NULL, 0); + _dbus_assert ((watch->flags & VALID_WATCH_FLAGS) == watch->flags); + + return watch->flags; +} + +/** + * Gets data previously set with dbus_watch_set_data() + * or #NULL if none. + * + * @param watch the DBusWatch object. + * @returns previously-set data. + */ +void* +dbus_watch_get_data (DBusWatch *watch) +{ + _dbus_return_val_if_fail (watch != NULL, NULL); + + return watch->data; +} + +/** + * Sets data which can be retrieved with dbus_watch_get_data(). + * Intended for use by the DBusAddWatchFunction and + * DBusRemoveWatchFunction to store their own data. For example with + * Qt you might store the QSocketNotifier for this watch and with GLib + * you might store a GSource. + * + * @param watch the DBusWatch object. + * @param data the data. + * @param free_data_function function to be called to free the data. + */ +void +dbus_watch_set_data (DBusWatch *watch, + void *data, + DBusFreeFunction free_data_function) +{ + _dbus_return_if_fail (watch != NULL); + + _dbus_verbose ("Setting watch fd %" DBUS_POLLABLE_FORMAT " data to data = %p function = %p from data = %p function = %p\n", + _dbus_pollable_printable (watch->fd), + data, free_data_function, watch->data, watch->free_data_function); + + if (watch->free_data_function != NULL) + (* watch->free_data_function) (watch->data); + + watch->data = data; + watch->free_data_function = free_data_function; +} + +/** + * Returns whether a watch is enabled or not. If not + * enabled, it should not be polled by the main loop. + * + * @param watch the DBusWatch object + * @returns #TRUE if the watch is enabled + */ +dbus_bool_t +dbus_watch_get_enabled (DBusWatch *watch) +{ + _dbus_return_val_if_fail (watch != NULL, FALSE); + + return watch->enabled; +} + + +/** + * Called to notify the D-Bus library when a previously-added watch is + * ready for reading or writing, or has an exception such as a hangup. + * + * If this function returns #FALSE, then the file descriptor may still + * be ready for reading or writing, but more memory is needed in order + * to do the reading or writing. If you ignore the #FALSE return, your + * application may spin in a busy loop on the file descriptor until + * memory becomes available, but nothing more catastrophic should + * happen. + * + * dbus_watch_handle() cannot be called during the + * DBusAddWatchFunction, as the connection will not be ready to handle + * that watch yet. + * + * It is not allowed to reference a DBusWatch after it has been passed + * to remove_function. + * + * @param watch the DBusWatch object. + * @param flags the poll condition using #DBusWatchFlags values + * @returns #FALSE if there wasn't enough memory + */ +dbus_bool_t +dbus_watch_handle (DBusWatch *watch, + unsigned int flags) +{ + _dbus_return_val_if_fail (watch != NULL, FALSE); + +#ifndef DBUS_DISABLE_CHECKS + if (!_dbus_pollable_is_valid (watch->fd) || watch->flags == 0) + { + _dbus_warn_check_failed ("Watch is invalid, it should have been removed"); + return TRUE; + } +#endif + + _dbus_return_val_if_fail (_dbus_pollable_is_valid (watch->fd) /* fails if watch was removed */, TRUE); + + _dbus_watch_sanitize_condition (watch, &flags); + + if (flags == 0) + { + _dbus_verbose ("After sanitization, watch flags on fd %" DBUS_POLLABLE_FORMAT " were 0\n", + _dbus_pollable_printable (watch->fd)); + return TRUE; + } + else + return (* watch->handler) (watch, flags, + watch->handler_data); +} + + +/** @} */ diff --git a/src/3rdparty/libdbus/dbus/dbus-watch.h b/src/3rdparty/libdbus/dbus/dbus-watch.h new file mode 100644 index 00000000..2b278461 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus-watch.h @@ -0,0 +1,115 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-watch.h DBusWatch internal interfaces + * + * Copyright (C) 2002 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef DBUS_WATCH_H +#define DBUS_WATCH_H + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-connection.h> + +DBUS_BEGIN_DECLS + +/** + * @addtogroup DBusWatchInternals + * @{ + */ + +/* Public methods on DBusWatch are in dbus-connection.h */ + +typedef struct DBusWatchList DBusWatchList; + +#define _DBUS_WATCH_NVAL (1<<4) + +/** function to run when the watch is handled */ +typedef dbus_bool_t (* DBusWatchHandler) (DBusWatch *watch, + unsigned int flags, + void *data); + +DBUS_PRIVATE_EXPORT +DBusWatch* _dbus_watch_new (DBusPollable fd, + unsigned int flags, + dbus_bool_t enabled, + DBusWatchHandler handler, + void *data, + DBusFreeFunction free_data_function); +DBUS_PRIVATE_EXPORT +DBusWatch* _dbus_watch_ref (DBusWatch *watch); +DBUS_PRIVATE_EXPORT +void _dbus_watch_unref (DBusWatch *watch); +DBUS_PRIVATE_EXPORT +void _dbus_watch_invalidate (DBusWatch *watch); +void _dbus_watch_sanitize_condition (DBusWatch *watch, + unsigned int *condition); +void _dbus_watch_set_handler (DBusWatch *watch, + DBusWatchHandler handler, + void *data, + DBusFreeFunction free_data_function); + + +DBUS_PRIVATE_EXPORT +DBusWatchList* _dbus_watch_list_new (void); +DBUS_PRIVATE_EXPORT +void _dbus_watch_list_free (DBusWatchList *watch_list); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_watch_list_set_functions (DBusWatchList *watch_list, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + DBusWatchToggledFunction toggled_function, + void *data, + DBusFreeFunction free_data_function); +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_watch_list_add_watch (DBusWatchList *watch_list, + DBusWatch *watch); +DBUS_PRIVATE_EXPORT +void _dbus_watch_list_remove_watch (DBusWatchList *watch_list, + DBusWatch *watch); +void _dbus_watch_list_toggle_watch (DBusWatchList *watch_list, + DBusWatch *watch, + dbus_bool_t enabled); +void _dbus_watch_list_toggle_all_watches (DBusWatchList *watch_list, + dbus_bool_t enabled); +dbus_bool_t _dbus_watch_get_enabled (DBusWatch *watch); + +DBUS_PRIVATE_EXPORT +dbus_bool_t _dbus_watch_get_oom_last_time (DBusWatch *watch); +DBUS_PRIVATE_EXPORT +void _dbus_watch_set_oom_last_time (DBusWatch *watch, + dbus_bool_t oom); + +DBusSocket _dbus_watch_get_socket (DBusWatch *watch); +DBUS_PRIVATE_EXPORT +DBusPollable _dbus_watch_get_pollable (DBusWatch *watch); + +static inline void +_dbus_clear_watch (DBusWatch **pointer_to_watch) +{ + _dbus_clear_pointer_impl (DBusWatch, pointer_to_watch, + _dbus_watch_unref); +} + +/** @} */ + +DBUS_END_DECLS + +#endif /* DBUS_WATCH_H */ diff --git a/src/3rdparty/libdbus/dbus/dbus.h b/src/3rdparty/libdbus/dbus/dbus.h new file mode 100644 index 00000000..53998c54 --- /dev/null +++ b/src/3rdparty/libdbus/dbus/dbus.h @@ -0,0 +1,106 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus.h Convenience header including all other headers + * + * Copyright (C) 2002, 2003 Red Hat Inc. + * + * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef DBUS_H +#define DBUS_H + +#define DBUS_INSIDE_DBUS_H 1 + +#include <dbus/dbus-arch-deps.h> +#include <dbus/dbus-address.h> +#include <dbus/dbus-bus.h> +#include <dbus/dbus-connection.h> +#include <dbus/dbus-errors.h> +#include <dbus/dbus-macros.h> +#include <dbus/dbus-message.h> +#include <dbus/dbus-misc.h> +#include <dbus/dbus-pending-call.h> +#include <dbus/dbus-protocol.h> +#include <dbus/dbus-server.h> +#include <dbus/dbus-shared.h> +#include <dbus/dbus-signature.h> +#include <dbus/dbus-syntax.h> +#include <dbus/dbus-threads.h> +#include <dbus/dbus-types.h> + +#undef DBUS_INSIDE_DBUS_H + +/** + * @defgroup DBus D-Bus low-level public API + * @brief The low-level public API of the D-Bus library + * + * libdbus provides a low-level C API intended primarily for use by + * bindings to specific object systems and languages. D-Bus is most + * convenient when used with the GLib bindings, Python bindings, Qt + * bindings, Mono bindings, and so forth. This low-level API has a + * lot of complexity useful only for bindings. + * + * @{ + */ + +/** @} */ + +/** + * @mainpage + * + * This manual documents the <em>low-level</em> D-Bus C API. <b>If you use + * this low-level API directly, you're signing up for some pain.</b> + * + * Caveats aside, you might get started learning the low-level API by reading + * about @ref DBusConnection and @ref DBusMessage. + * + * There are several other places to look for D-Bus information, such + * as the tutorial and the specification; those can be found at <a + * href="http://www.freedesktop.org/wiki/Software/dbus">the D-Bus + * website</a>. If you're interested in a sysadmin or package + * maintainer's perspective on the dbus-daemon itself and its + * configuration, be sure to check out the man pages as well. + * + * The low-level API documented in this manual deliberately lacks + * most convenience functions - those are left up to higher-level libraries + * based on frameworks such as GLib, Qt, Python, Mono, Java, + * etc. These higher-level libraries (often called "D-Bus bindings") + * have features such as object systems and main loops that allow a + * <em>much</em> more convenient API. + * + * The low-level API also contains plenty of clutter to support + * integration with arbitrary object systems, languages, main loops, + * and so forth. These features add a lot of noise to the API that you + * probably don't care about unless you're coding a binding. + * + * This manual also contains docs for @ref DBusInternals "D-Bus internals", + * so you can use it to get oriented to the D-Bus source code if you're + * interested in patching the code. You should also read the + * file CONTRIBUTING.md which comes with the source code if you plan to + * contribute to D-Bus. + * + * As you read the code, you can identify internal D-Bus functions + * because they start with an underscore ('_') character. Also, any + * identifier or macro that lacks a DBus, dbus_, or DBUS_ namepace + * prefix is internal, with a couple of exceptions such as #NULL, + * #TRUE, and #FALSE. + */ + +#endif /* DBUS_H */ |