+2003-04-03 Havoc Pennington <hp@pobox.com>
+
+ * bus/config-parser.c (bus_config_parser_unref): free
+ list of mechanisms, bug discovered by test suite enhancements
+ (putting system.conf and session.conf into suite)
+
+ * test/Makefile.am, test/test-service.c: add placeholder for a
+ test service that we'll activate as part of test suite. Doesn't
+ do anything yet.
+
+ * dbus/dbus-sysdeps.c (_dbus_setenv): support unsetenv by
+ setting NULL value, and use system malloc not dbus_malloc()
+ when we have unavoidable memleakage.
+
+ * dbus/dbus-bus.c (dbus_bus_get): fix bug where bus type of 0
+ didn't work, and support DBUS_BUS_ACTIVATION.
+
+ * bus/activation.c (child_setup): pass our well-known bus type to
+ the child
+
+ * bus/config-parser.c: support <type> to specify well-known type
+
+ * doc/dbus-specification.sgml: document the env variables to
+ locate well-known buses and find service activator
+
2003-04-02 Havoc Pennington <hp@redhat.com>
* test/Makefile.am (all-local): add a rule to copy tests to
child_setup (void *data)
{
BusActivation *activation = data;
+ const char *type;
/* If no memory, we simply have the child exit, so it won't try
* to connect to the wrong thing.
*/
- if (!_dbus_setenv ("DBUS_ADDRESS", activation->server_address))
+ if (!_dbus_setenv ("DBUS_ACTIVATION_ADDRESS", activation->server_address))
_dbus_exit (1);
+
+ type = bus_context_get_type (activation->context);
+ if (type != NULL)
+ {
+ if (!_dbus_setenv ("DBUS_BUS_TYPE", type))
+ _dbus_exit (1);
+ }
}
dbus_bool_t
struct BusContext
{
int refcount;
+ char *type;
char *address;
DBusList *servers;
BusConnections *connections;
if (!_dbus_change_identity (creds.uid, creds.gid, error))
goto failed;
}
+
+ /* note that type may be NULL */
+ context->type = _dbus_strdup (bus_config_parser_get_type (parser));
/* We have to build the address backward, so that
* <listen> later in the config file have priority
_dbus_hash_table_unref (context->rules_by_gid);
context->rules_by_gid = NULL;
}
-
+
+ dbus_free (context->type);
dbus_free (context->address);
dbus_free (context);
}
}
+/* type may be NULL */
+const char*
+bus_context_get_type (BusContext *context)
+{
+ return context->type;
+}
+
BusRegistry*
bus_context_get_registry (BusContext *context)
{
void bus_context_shutdown (BusContext *context);
void bus_context_ref (BusContext *context);
void bus_context_unref (BusContext *context);
+const char* bus_context_get_type (BusContext *context);
BusRegistry* bus_context_get_registry (BusContext *context);
BusConnections* bus_context_get_connections (BusContext *context);
BusActivation* bus_context_get_activation (BusContext *context);
ELEMENT_DENY,
ELEMENT_FORK,
ELEMENT_SERVICEDIR,
- ELEMENT_INCLUDEDIR
+ ELEMENT_INCLUDEDIR,
+ ELEMENT_TYPE
} ElementType;
typedef struct
struct
{
- char *mechanism;
- } auth;
-
- struct
- {
char *context;
char *user;
char *group;
char *user; /**< user to run as */
+ char *bus_type; /**< Message bus type */
+
DBusList *listen_on; /**< List of addresses to listen to */
DBusList *mechanisms; /**< Auth mechanisms */
return "servicedir";
case ELEMENT_INCLUDEDIR:
return "includedir";
+ case ELEMENT_TYPE:
+ return "type";
}
_dbus_assert_not_reached ("bad element type");
included->user = NULL;
}
+ if (included->bus_type != NULL)
+ {
+ dbus_free (parser->bus_type);
+ parser->bus_type = included->bus_type;
+ included->bus_type = NULL;
+ }
+
if (included->fork)
parser->fork = TRUE;
pop_element (parser);
dbus_free (parser->user);
-
+ dbus_free (parser->bus_type);
+
_dbus_list_foreach (&parser->listen_on,
(DBusForeachFunction) dbus_free,
NULL);
_dbus_list_clear (&parser->service_dirs);
+ _dbus_list_foreach (&parser->mechanisms,
+ (DBusForeachFunction) dbus_free,
+ NULL);
+
+ _dbus_list_clear (&parser->mechanisms);
+
_dbus_string_free (&parser->basedir);
dbus_free (parser);
if (push_element (parser, ELEMENT_USER) == NULL)
{
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ BUS_SET_OOM (error);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else if (strcmp (element_name, "type") == 0)
+ {
+ if (!check_no_attributes (parser, "type", attribute_names, attribute_values, error))
+ return FALSE;
+
+ if (push_element (parser, ELEMENT_TYPE) == NULL)
+ {
+ BUS_SET_OOM (error);
return FALSE;
}
if (push_element (parser, ELEMENT_FORK) == NULL)
{
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ BUS_SET_OOM (error);
return FALSE;
}
if (push_element (parser, ELEMENT_LISTEN) == NULL)
{
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ BUS_SET_OOM (error);
return FALSE;
}
if (push_element (parser, ELEMENT_AUTH) == NULL)
{
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ BUS_SET_OOM (error);
return FALSE;
}
if (push_element (parser, ELEMENT_INCLUDEDIR) == NULL)
{
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ BUS_SET_OOM (error);
return FALSE;
}
if (push_element (parser, ELEMENT_SERVICEDIR) == NULL)
{
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ BUS_SET_OOM (error);
return FALSE;
}
if ((e = push_element (parser, ELEMENT_INCLUDE)) == NULL)
{
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ BUS_SET_OOM (error);
return FALSE;
}
if ((e = push_element (parser, ELEMENT_POLICY)) == NULL)
{
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ BUS_SET_OOM (error);
return FALSE;
}
{
if (push_element (parser, ELEMENT_ALLOW) == NULL)
{
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ BUS_SET_OOM (error);
return FALSE;
}
{
if (push_element (parser, ELEMENT_DENY) == NULL)
{
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ BUS_SET_OOM (error);
return FALSE;
}
if (push_element (parser, ELEMENT_BUSCONFIG) == NULL)
{
- dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ BUS_SET_OOM (error);
return FALSE;
}
* being paranoid about XML parsers
*/
dbus_set_error (error, DBUS_ERROR_FAILED,
- "XML element ended which was not the topmost element on the stack");
+ "XML element <%s> ended but topmost element on the stack was <%s>",
+ element_name, n);
return FALSE;
}
case ELEMENT_INCLUDE:
case ELEMENT_USER:
+ case ELEMENT_TYPE:
case ELEMENT_LISTEN:
case ELEMENT_AUTH:
case ELEMENT_SERVICEDIR:
}
break;
+ case ELEMENT_TYPE:
+ {
+ char *s;
+
+ e->had_content = TRUE;
+
+ if (!_dbus_string_copy_data (content, &s))
+ goto nomem;
+
+ dbus_free (parser->bus_type);
+ parser->bus_type = s;
+ }
+ break;
+
case ELEMENT_LISTEN:
{
char *s;
return parser->user;
}
+const char*
+bus_config_parser_get_type (BusConfigParser *parser)
+{
+ return parser->bus_type;
+}
+
DBusList**
bus_config_parser_get_addresses (BusConfigParser *parser)
{
/* Functions for extracting the parse results */
const char* bus_config_parser_get_user (BusConfigParser *parser);
+const char* bus_config_parser_get_type (BusConfigParser *parser);
DBusList** bus_config_parser_get_addresses (BusConfigParser *parser);
DBusList** bus_config_parser_get_mechanisms (BusConfigParser *parser);
dbus_bool_t bus_config_parser_get_fork (BusConfigParser *parser);
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
+ <!-- Our well-known bus type, don't change this -->
+ <type>session</type>
+
<!-- FIXME - this is fairly complicated to fix.
Propose the following:
- add "unix:tmpdir=/tmp" which means unix domain transport
reads the address from there and sets the env variable
-->
<listen>unix:path=/tmp/foobar</listen>
+
<policy context="default">
<!-- Allow everything -->
<allow send="*"/>
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
+ <!-- Our well-known bus type, do not change this -->
+ <type>system</type>
+
<!-- Run as special user -->
<user>messagebus</user>
AC_CHECK_LIB(socket,socket)
AC_CHECK_LIB(nsl,gethostbyname)
-AC_CHECK_FUNCS(vsnprintf vasprintf nanosleep usleep poll setenv socketpair getgrouplist)
+AC_CHECK_FUNCS(vsnprintf vasprintf nanosleep usleep poll setenv unsetenv socketpair getgrouplist)
AC_CHECK_HEADERS(execinfo.h, [AC_CHECK_FUNCS(backtrace)])
#include "dbus-bus.h"
#include "dbus-protocol.h"
#include "dbus-internals.h"
+#include <string.h>
/**
* @defgroup DBusBus Message bus APIs
*/
static int bus_data_slot_refcount = 0;
+/** Number of bus types */
+#define N_BUS_TYPES 3
+
+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_ACTIVATION;
+
+static dbus_bool_t initialized = FALSE;
+
/**
- * Lock for bus_data_slot and bus_data_slot_refcount
+ * Lock for globals in this file
*/
_DBUS_DEFINE_GLOBAL_LOCK (bus);
+static void
+addresses_shutdown_func (void *data)
+{
+ int i;
+
+ i = 0;
+ while (i < N_BUS_TYPES)
+ {
+ if (bus_connections[i] != NULL)
+ _dbus_warn ("dbus_shutdown() called but connections were still live!");
+
+ dbus_free (bus_connection_addresses[i]);
+ bus_connection_addresses[i] = NULL;
+ ++i;
+ }
+
+ activation_bus_type = DBUS_BUS_ACTIVATION;
+}
+
+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_connections_unlocked (void)
+{
+ if (!initialized)
+ {
+ const char *s;
+
+ bus_connections[0] = NULL;
+ bus_connections[1] = NULL;
+ bus_connections[2] = NULL;
+
+ /* Don't init these twice, we may run this code twice if
+ * init_connections_unlocked() fails midway through.
+ */
+
+ if (bus_connection_addresses[DBUS_BUS_SYSTEM] == NULL)
+ {
+ 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 ("unix:path=" DBUS_SYSTEM_BUS_PATH);
+ if (bus_connection_addresses[DBUS_BUS_SYSTEM] == NULL)
+ return FALSE;
+ }
+ }
+
+ if (bus_connection_addresses[DBUS_BUS_SESSION] == NULL)
+ {
+ if (!get_from_env (&bus_connection_addresses[DBUS_BUS_SESSION],
+ "DBUS_SESSION_BUS_ADDRESS"))
+ return FALSE;
+ }
+
+ if (bus_connection_addresses[DBUS_BUS_ACTIVATION] == NULL)
+ {
+ if (!get_from_env (&bus_connection_addresses[DBUS_BUS_ACTIVATION],
+ "DBUS_ACTIVATION_ADDRESS"))
+ return FALSE;
+ }
+
+ s = _dbus_getenv ("DBUS_ACTIVATION_BUS_TYPE");
+
+ if (s != NULL)
+ {
+ if (strcmp (s, "system") == 0)
+ activation_bus_type = DBUS_BUS_SYSTEM;
+ else if (strcmp (s, "session") == 0)
+ activation_bus_type = DBUS_BUS_SESSION;
+ }
+
+ /* If we return FALSE we have to be sure that restarting
+ * the above code will work right
+ */
+
+ if (!_dbus_setenv ("DBUS_ACTIVATION_ADDRESS", NULL))
+ return FALSE;
+
+ if (!_dbus_setenv ("DBUS_ACTIVATION_BUS_TYPE", NULL))
+ return FALSE;
+
+ if (!_dbus_register_shutdown_func (addresses_shutdown_func,
+ NULL))
+ return FALSE;
+
+ initialized = TRUE;
+ }
+
+ return initialized;
+}
static dbus_bool_t
data_slot_ref (void)
* @{
*/
-/** Number of bus types */
-#define BUS_TYPES 2
-
-static DBusConnection *bus_connections[BUS_TYPES];
-
/**
* Connects to a bus daemon and registers the client with it.
* If a connection to the bus already exists, then that connection is returned.
dbus_bus_get (DBusBusType type,
DBusError *error)
{
- const char *name, *value;
+ const char *address;
DBusConnection *connection;
BusData *bd;
+ DBusBusType address_type;
- if (type <= 0 || type >= BUS_TYPES)
+ _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+ if (type < 0 || type >= N_BUS_TYPES)
{
_dbus_assert_not_reached ("Invalid bus type specified.");
}
_DBUS_LOCK (bus);
+
+ if (!init_connections_unlocked ())
+ {
+ _DBUS_UNLOCK (bus);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+ return NULL;
+ }
+
+ /* 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. (If the activating bus isn't a well-known
+ * bus then activation_bus_type == DBUS_BUS_ACTIVATION)
+ */
+ if (type == DBUS_BUS_ACTIVATION)
+ type = activation_bus_type;
if (bus_connections[type] != NULL)
{
return connection;
}
- switch (type)
- {
- case DBUS_BUS_SESSION:
- name = "DBUS_SESSION_BUS_ADDRESS";
- break;
- case DBUS_BUS_SYSTEM:
- name = "DBUS_SYSTEM_BUS_ADDRESS";
- break;
- }
-
- value = _dbus_getenv (name);
-
- if (type == DBUS_BUS_SYSTEM &&
- (value == NULL || *value == '\0'))
- {
- /* Use default system bus address if none set */
- value = "unix:path=" DBUS_SYSTEM_BUS_PATH;
- }
-
- if (value == NULL || *value == '\0')
+ address = bus_connection_addresses[address_type];
+ if (address == NULL)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
- "Environment variable %s not set, address of message bus unknown",
- name);
+ "Unable to determine the address of the message bus");
_DBUS_UNLOCK (bus);
-
return NULL;
}
- connection = dbus_connection_open (value, error);
+ connection = dbus_connection_open (address, error);
if (!connection)
{
+ _DBUS_ASSERT_ERROR_IS_SET (error);
_DBUS_UNLOCK (bus);
return NULL;
}
if (!dbus_bus_register (connection, error))
{
+ _DBUS_ASSERT_ERROR_IS_SET (error);
dbus_connection_disconnect (connection);
dbus_connection_unref (connection);
bd->connection = &bus_connections[type];
- _DBUS_UNLOCK (bus);
+ _DBUS_UNLOCK (bus);
return connection;
}
typedef enum
{
- DBUS_BUS_SESSION, /**< The login session bus */
- DBUS_BUS_SYSTEM /**< The system bus */
+ DBUS_BUS_SESSION, /**< The login session bus */
+ DBUS_BUS_SYSTEM, /**< The systemwide bus */
+ DBUS_BUS_ACTIVATION /**< The bus that activated us, if any */
} DBusBusType;
DBusConnection *dbus_bus_get (DBusBusType type,
}
/**
- * Wrapper for setenv().
+ * Wrapper for setenv(). If the value is #NULL, unsets
+ * the environment variable.
+ *
+ * @todo if someone can verify it's safe, we could avoid the
+ * memleak when doing an unset.
*
* @param varname name of environment variable
* @param value value of environment variable
* @returns #TRUE on success.
*/
dbus_bool_t
-_dbus_setenv (const char *varname, const char *value)
+_dbus_setenv (const char *varname,
+ const char *value)
{
-#ifdef HAVE_SETENV
- return (setenv (varname, value, TRUE) == 0);
+ _dbus_assert (varname != NULL);
+
+ if (value == NULL)
+ {
+#ifdef HAVE_UNSETENV
+ unsetenv (varname);
+ return TRUE;
#else
- DBusString str;
- char *putenv_value;
+ char *putenv_value;
+ size_t len;
- if (!_dbus_string_init (&str))
- return FALSE;
+ len = strlen (varname);
- if (!_dbus_string_append (&str, varname) ||
- !_dbus_string_append (&str, "=") ||
- !_dbus_string_append (&str, value) ||
- !_dbus_string_steal_data (&str, &putenv_value))
- {
- _dbus_string_free (&str);
- return FALSE;
+ /* 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);
+
+ 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 /* '=' */ ;
- _dbus_string_free (&str);
+ /* Use system malloc to avoid memleaks that dbus_malloc
+ * will get upset about.
+ */
+
+ putenv_value = malloc (len + 1);
+ if (putenv_value == NULL)
+ return FALSE;
- return (putenv (putenv_value) == 0);
+ strcpy (putenv_value, varname);
+ strcpy (putenv_value + varname_len, "=");
+ strcpy (putenv_value + varname_len + 1, value);
+
+ return (putenv (putenv_value) == 0);
#endif
+ }
}
/**
Root element.
+ <type>
+
+ The well-known type of the message bus. Currently known values
+ are "system" and "session"; if other values are set, they should
+ be either added to the D-BUS specification, or namespaced.
+ The last <type> element "wins"
+
+ Example: <type>session</type>
+
<include>
ignore_missing="(yes|no)" optional attribute, defaults to no
</para>
<para>
The executable launched will have the environment variable
- <literal>DBUS_BUS_ADDRESS</literal> set to the address of the
+ <literal>DBUS_ACTIVATION_ADDRESS</literal> set to the address of the
message bus so it can connect and register the appropriate services.
</para>
<para>
+ The executable being launched may want to know whether the message bus
+ activating it is one of the well-known message buses (see <xref
+ linkend="message-bus-types">). To facilitate this, the bus MUST also set
+ the <literal>DBUS_ACTIVATION_BUS_TYPE</literal> environment variable if it is one
+ of the well-known buses. The currently-defined values for this variable
+ are <literal>system</literal> for the systemwide message bus,
+ and <literal>session</literal> for the per-login-session message
+ bus. The activated executable must still connect to the address given
+ in <literal>DBUS_ACTIVATION_ADDRESS</literal>, but may assume that the
+ resulting connection is to the well-known bus.
+ </para>
+ <para>
[FIXME there should be a timeout somewhere, either specified
in the .service file, by the client, or just a global value
and if the client being activated fails to connect within that
</sect2>
<sect2 id="message-bus-types">
- <title>Standard Message Bus Instances</title>
+ <title>Well-known Message Bus Instances</title>
<para>
Two standard message bus instances are defined here, along with how
to locate them and where their service files live.
<para>
Each time a user logs in, a <firstterm>login session message
bus</firstterm> may be started. All applications in the user's login
- session may interact with one another using this message bus. [specify
- how to find the address of the login session message bus via
- environment variable and/or X property]
+ session may interact with one another using this message bus.
+ </para>
+ <para>
+ The address of the login session message bus is given
+ in the <literal>DBUS_SESSION_BUS_ADDRESS</literal> environment
+ variable. If that variable is not set, applications may
+ also try to read the address from the X Window System root
+ window property <literal>_DBUS_SESSION_BUS_ADDRESS</literal>.
+ The root window property must have type <literal>STRING</literal>.
+ The environment variable should have precedence over the
+ root window property.
</para>
<para>
[FIXME specify location of .service files, probably using
<para>
A computer may have a <firstterm>system message bus</firstterm>,
accessible to all applications on the system. This message bus may be
- used to broadcast system events, such as adding new hardware devices.
- [specify how to find the address of the system message bus]
+ used to broadcast system events, such as adding new hardware devices,
+ changes in the printer queue, and so forth.
+ </para>
+ <para>
+ The address of the login session message bus is given
+ in the <literal>DBUS_SYSTEM_BUS_ADDRESS</literal> environment
+ variable. If that variable is not set, applications should try
+ to connect to the well-known address
+ <literal>unix:path=/var/run/dbus/system_bus_socket</literal>.
+ <footnote>
+ <para>
+ The D-BUS reference implementation actually honors the
+ <literal>$(localstatedir)</literal> configure option
+ for this address, on both client and server side.
+ </para>
+ </footnote>
</para>
<para>
[FIXME specify location of system bus .service files]
INCLUDES=-I$(top_srcdir) $(DBUS_TEST_CFLAGS)
if DBUS_BUILD_TESTS
-TEST_BINARIES=echo-client echo-server unbase64 break-loader spawn-test
+TEST_BINARIES=test-service echo-client echo-server unbase64 break-loader spawn-test
else
TEST_BINARIES=
endif
watch.c \
watch.h
+test_service_SOURCES= \
+ test-service.c \
+ watch.c \
+ watch.h
+
unbase64_SOURCES= \
unbase64.c
echo_client_LDADD=$(TEST_LIBS)
echo_server_LDADD=$(TEST_LIBS)
+test_service_LDADD=$(TEST_LIBS)
unbase64_LDADD=$(TEST_LIBS)
break_loader_LDADD= $(TEST_LIBS)
#bus_test_LDADD=$(TEST_LIBS) $(top_builddir)/bus/libdbus-daemon.la
--- /dev/null
+#include <dbus/dbus.h>
+#include <stdio.h>
+#include "watch.h"
+
+int
+main (int argc,
+ char **argv)
+{
+ DBusConnection *connection;
+ DBusError error;
+ DBusMessage *message;
+
+ dbus_error_init (&error);
+ connection = dbus_bus_get (DBUS_BUS_ACTIVATION, &error);
+ if (connection == NULL)
+ {
+ fprintf (stderr, "Failed to open connection to activating message bus: %s\n",
+ error.message);
+ dbus_error_free (&error);
+ return 1;
+ }
+
+ setup_connection (connection);
+
+ do_mainloop ();
+
+ dbus_connection_unref (connection);
+
+ return 0;
+}