diff --git a/configure.in b/configure.in index 62a21391a..91d6f258b 100644 --- a/configure.in +++ b/configure.in @@ -3496,6 +3496,16 @@ if test x$glib_win32_static_compilation = xyes; then fi ]) +# Check for libdbus1 - Optional - is only used in the GDBus test cases +# +PKG_CHECK_MODULES(DBUS1, + dbus-1, + [AC_DEFINE(HAVE_DBUS1, 1, [Define if dbus-1 is available]) have_dbus1=yes], + have_dbus1=no) +AC_SUBST(DBUS1_CFLAGS) +AC_SUBST(DBUS1_LIBS) +AM_CONDITIONAL(HAVE_DBUS1, [test "x$have_dbus1" = "xyes"]) + AC_CONFIG_FILES([ glib-2.0.pc glib-2.0-uninstalled.pc diff --git a/docs/reference/gio/Makefile.am b/docs/reference/gio/Makefile.am index 16a1341bc..158e42d69 100644 --- a/docs/reference/gio/Makefile.am +++ b/docs/reference/gio/Makefile.am @@ -114,15 +114,23 @@ HTML_IMAGES = \ content_files = \ version.xml \ overview.xml \ - migrating.xml \ + migrating-posix.xml \ + migrating-gnome-vfs.xml \ + migrating-gconf.xml \ + migrating-gdbus.xml \ gio-querymodules.xml \ glib-compile-schemas.xml\ gsettings.xml \ - gsettings-schema-convert.xml + gsettings-schema-convert.xml \ + gdbus.xml \ + $(NULL) expand_content_files = \ overview.xml \ - migrating.xml + migrating-posix.xml \ + migrating-gnome-vfs.xml \ + migrating-gconf.xml \ + migrating-gdbus.xml extra_files = \ version.xml.in \ @@ -137,7 +145,8 @@ man_MANS = \ gio-querymodules.1 \ glib-compile-schemas.1 \ gsettings.1 \ - gsettings-schema-convert.1 + gsettings-schema-convert.1 \ + gdbus.1 if ENABLE_MAN diff --git a/docs/reference/gio/gdbus.xml b/docs/reference/gio/gdbus.xml new file mode 100644 index 000000000..e2f33546d --- /dev/null +++ b/docs/reference/gio/gdbus.xml @@ -0,0 +1,266 @@ + + + + gdbus + 1 + User Commands + + + + gdbus + Introspect and call remote objects + + + + + gdbus + introspect + + --system + --session + --address address + + --dest bus_name + --object-path /path/to/object + + + gdbus + monitor + + --system + --session + --address address + + --dest bus_name + + --object-path /path/to/object + + + + gdbus + call + + --system + --session + --address address + + --dest bus_name + --object-path /path/to/object + --method org.project.InterfaceName.MethodName + ARG1 + ARG2 + + + gdbus + help + + + + + Description + + gdbus offers a simple commandline utility for + introspecting and calling methods on remote objects. + + + Commands + + + + + Prints out interfaces and property values for a remote object. + For this to work, the owner of the object needs to implement the + org.freedesktop.DBus.Introspectable interface. + + + + + + Monitors one or all objects owned by the owner of + bus_name. + + + + + + Invokes a method on a remote object. Each argument to pass to the + method must be specified as a serialized + GVariant except that strings do + not need explicit quotes. The return values are printed out as + serialized GVariant + values. + + + + + + Prints help and exit. + + + + + + + + Bash Completion + + gdbus ships with a bash completion script to + complete commands, destinations, bus names, object paths and + interface/method names. + + + + + Examples + This shows how to introspect an object - note that the value of each + property is displayed: + +$ gdbus introspect --system \ + --dest org.freedesktop.NetworkManager \ + --object-path /org/freedesktop/NetworkManager/Devices/0 +node /org/freedesktop/NetworkManager/Devices/0 { + interface org.freedesktop.DBus.Introspectable { + methods: + Introspect(out s data); + }; + interface org.freedesktop.DBus.Properties { + methods: + Get(in s interface, + in s propname, + out v value); + Set(in s interface, + in s propname, + in v value); + GetAll(in s interface, + out a{sv} props); + }; + interface org.freedesktop.NetworkManager.Device.Wired { + signals: + PropertiesChanged(a{sv} arg_0); + properties: + readonly b Carrier = false; + readonly u Speed = 0; + readonly s HwAddress = '00:1D:72:88:BE:97'; + }; + interface org.freedesktop.NetworkManager.Device { + methods: + Disconnect(); + signals: + StateChanged(u arg_0, + u arg_1, + u arg_2); + properties: + readonly u DeviceType = 1; + readonly b Managed = true; + readwrite o Ip6Config = '/'; + readwrite o Dhcp4Config = '/'; + readwrite o Ip4Config = '/'; + readonly u State = 2; + readwrite u Ip4Address = 0; + readonly u Capabilities = 3; + readonly s Driver = 'e1000e'; + readwrite s Interface = 'eth0'; + readonly s Udi = '/sys/devices/pci0000:00/0000:00:19.0/net/eth0'; + }; +}; + + + In a similar fashion, the command can be + used to learn details about the Notify method: + + +[...] + interface org.freedesktop.Notifications { + methods: + GetServerInformation(out s return_name, + out s return_vendor, + out s return_version, + out s return_spec_version); + GetCapabilities(out as return_caps); + CloseNotification(in u id); + Notify(in s app_name, + in u id, + in s icon, + in s summary, + in s body, + in as actions, + in a{sv} hints, + in i timeout, + out u return_id); + }; +[...] + + + With this information, it's easy to use the + command to display a notification + + +$ gdbus call --session \ + --dest org.freedesktop.Notifications \ + --object-path /org/freedesktop/Notifications \ + --method org.freedesktop.Notifications.Notify \ + my_app_name \ + 42 \ + gtk-dialog-info \ + "The Summary" \ + "Here's the body of the notification" \ + [] \ + {} \ + 5000 +(uint32 12,) + + + Monitoring all objects on a service: + + +$ gdbus monitor --system --dest org.freedesktop.ConsoleKit +Monitoring signals from all objects owned by org.freedesktop.ConsoleKit +The name org.freedesktop.ConsoleKit is owned by :1.15 +/org/freedesktop/ConsoleKit/Session2: org.freedesktop.ConsoleKit.Session.ActiveChanged (false,) +/org/freedesktop/ConsoleKit/Seat1: org.freedesktop.ConsoleKit.Seat.ActiveSessionChanged ('',) +/org/freedesktop/ConsoleKit/Session2: org.freedesktop.ConsoleKit.Session.ActiveChanged (true,) +/org/freedesktop/ConsoleKit/Seat1: org.freedesktop.ConsoleKit.Seat.ActiveSessionChanged ('/org/freedesktop/ConsoleKit/Session2',) + + + Monitoring a single object on a service: + + +$ gdbus monitor --system --dest org.freedesktop.NetworkManager --object-path /org/freedesktop/NetworkManager/AccessPoint/4141 +Monitoring signals on object /org/freedesktop/NetworkManager/AccessPoint/4141 owned by org.freedesktop.NetworkManager +The name org.freedesktop.NetworkManager is owned by :1.5 +/org/freedesktop/NetworkManager/AccessPoint/4141: org.freedesktop.NetworkManager.AccessPoint.PropertiesChanged ({'Strength': <byte 0x5c>},) +/org/freedesktop/NetworkManager/AccessPoint/4141: org.freedesktop.NetworkManager.AccessPoint.PropertiesChanged ({'Strength': <byte 0x64>},) +/org/freedesktop/NetworkManager/AccessPoint/4141: org.freedesktop.NetworkManager.AccessPoint.PropertiesChanged ({'Strength': <byte 0x5e>},) +/org/freedesktop/NetworkManager/AccessPoint/4141: org.freedesktop.NetworkManager.AccessPoint.PropertiesChanged ({'Strength': <byte 0x64>},) + + + + + + AUTHOR + + Written by David Zeuthen zeuthen@gmail.com with + a lot of help from many others. + + + + + BUGS + + Please send bug reports to either the distribution bug tracker + or the upstream bug tracker at + . + + + + + SEE ALSO + + + dbus-send1 + + + + + + diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml index c63f4f737..151e909f8 100644 --- a/docs/reference/gio/gio-docs.xml +++ b/docs/reference/gio/gio-docs.xml @@ -107,9 +107,11 @@ + + - Highlevel network support + Highlevel network functionallity @@ -125,6 +127,25 @@ + + Lowlevel D-Bus Support + + + + + + + + + + + + Highlevel D-Bus Support + + + + + Utilities @@ -146,10 +167,17 @@ + - + + Migrating to GIO + + + + + Object Hierarchy diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index 4d8e47879..056a76543 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -1804,6 +1804,12 @@ GTcpConnection g_tcp_connection_set_graceful_disconnect g_tcp_connection_get_graceful_disconnect +GUnixConnection +g_unix_connection_receive_fd +g_unix_connection_send_fd +g_unix_connection_receive_credentials +g_unix_connection_send_credentials + g_socket_connection_factory_create_connection g_socket_connection_factory_lookup_type g_socket_connection_factory_register_type @@ -2173,3 +2179,383 @@ G_SETTINGS_SCHEMA_GET_CLASS g_settings_get_type + +
+gunixcredentialsmessage +GUnixCredentialsMessage +GUnixCredentialsMessage +GUnixCredentialsMessageClass +g_unix_credentials_message_new +g_unix_credentials_message_new_with_credentials +g_unix_credentials_message_get_credentials +g_unix_credentials_message_is_supported + +G_IS_UNIX_CREDENTIALS_MESSAGE +G_IS_UNIX_CREDENTIALS_MESSAGE_CLASS +G_TYPE_UNIX_CREDENTIALS_MESSAGE +G_UNIX_CREDENTIALS_MESSAGE +G_UNIX_CREDENTIALS_MESSAGE_CLASS +G_UNIX_CREDENTIALS_MESSAGE_GET_CLASS + +GUnixCredentialsMessagePrivate +g_unix_credentials_message_get_type +
+ +
+gcredentials +GCredentials +GCredentials +GCredentialsClass +g_credentials_new +g_credentials_to_string +g_credentials_get_native +g_credentials_set_native +g_credentials_is_same_user +g_credentials_get_unix_user +g_credentials_set_unix_user + +G_CREDENTIALS +G_IS_CREDENTIALS +G_TYPE_CREDENTIALS +g_credentials_get_type +G_CREDENTIALS_CLASS +G_IS_CREDENTIALS_CLASS +G_CREDENTIALS_GET_CLASS +
+ +
+gdbusaddress +g_dbus_is_address +g_dbus_is_supported_address +g_dbus_address_get_stream +g_dbus_address_get_stream_finish +g_dbus_address_get_stream_sync +g_dbus_address_get_for_bus_sync +
+ +
+gdbusutils +g_dbus_generate_guid +g_dbus_is_guid +g_dbus_is_name +g_dbus_is_unique_name +g_dbus_is_member_name +g_dbus_is_interface_name +
+ +
+gdbusauthobserver +GDBusAuthObserver +GDBusAuthObserver +GDBusAuthObserverClass +g_dbus_auth_observer_new +g_dbus_auth_observer_authorize_authenticated_peer + +G_DBUS_AUTH_OBSERVER +G_IS_DBUS_AUTH_OBSERVER +G_TYPE_DBUS_AUTH_OBSERVER +g_dbus_server_get_gtype +G_DBUS_AUTH_OBSERVER_CLASS +G_IS_DBUS_AUTH_OBSERVER_CLASS +G_DBUS_AUTH_OBSERVER_GET_CLASS +
+ +
+gdbusserver +GDBusServer +GDBusServer +GDBusServerClass +GDBusServerFlags +g_dbus_server_new_sync +g_dbus_server_start +g_dbus_server_stop +g_dbus_server_is_active +g_dbus_server_get_guid +g_dbus_server_get_flags +g_dbus_server_get_client_address + +G_DBUS_SERVER +G_IS_DBUS_SERVER +G_TYPE_DBUS_SERVER +g_dbus_server_get_gtype +G_DBUS_SERVER_CLASS +G_IS_DBUS_SERVER_CLASS +G_DBUS_SERVER_GET_CLASS +
+ +
+gdbusmessage +GDBusMessage +GDBusMessageType +GDBusMessageFlags +GDBusMessageHeaderField +GDBusMessage +GDBusMessageClass +g_dbus_message_new +g_dbus_message_new_signal +g_dbus_message_new_method_call +g_dbus_message_new_method_reply +g_dbus_message_new_method_error +g_dbus_message_new_method_error_valist +g_dbus_message_new_method_error_literal +g_dbus_message_print +g_dbus_message_get_message_type +g_dbus_message_set_message_type +g_dbus_message_get_serial +g_dbus_message_set_serial +g_dbus_message_get_flags +g_dbus_message_set_flags +g_dbus_message_get_body +g_dbus_message_set_body +g_dbus_message_get_unix_fd_list +g_dbus_message_set_unix_fd_list +g_dbus_message_get_header_fields +g_dbus_message_get_header +g_dbus_message_set_header +g_dbus_message_get_destination +g_dbus_message_set_destination +g_dbus_message_get_error_name +g_dbus_message_set_error_name +g_dbus_message_get_interface +g_dbus_message_set_interface +g_dbus_message_get_member +g_dbus_message_set_member +g_dbus_message_get_path +g_dbus_message_set_path +g_dbus_message_get_reply_serial +g_dbus_message_set_reply_serial +g_dbus_message_get_sender +g_dbus_message_set_sender +g_dbus_message_get_signature +g_dbus_message_set_signature +g_dbus_message_get_arg0 +g_dbus_message_to_blob +g_dbus_message_bytes_needed +g_dbus_message_new_from_blob +g_dbus_message_to_gerror + +G_DBUS_MESSAGE +G_IS_DBUS_MESSAGE +G_TYPE_DBUS_MESSAGE +g_dbus_message_get_type +G_DBUS_MESSAGE_CLASS +G_IS_DBUS_MESSAGE_CLASS +G_DBUS_MESSAGE_GET_CLASS +
+ +
+gdbusconnection +GDBusConnection +GBusType +g_bus_get +g_bus_get_finish +g_bus_get_sync +GDBusConnection +GDBusConnectionClass +GDBusConnectionFlags +g_dbus_connection_new +g_dbus_connection_new_finish +g_dbus_connection_new_sync +g_dbus_connection_new_for_address +g_dbus_connection_new_for_address_finish +g_dbus_connection_new_for_address_sync +GDBusCapabilityFlags +g_dbus_connection_close +g_dbus_connection_is_closed +g_dbus_connection_get_exit_on_close +g_dbus_connection_set_exit_on_close +g_dbus_connection_get_stream +g_dbus_connection_get_guid +g_dbus_connection_get_unique_name +g_dbus_connection_get_capabilities +g_dbus_connection_get_peer_credentials +GDBusCallFlags +g_dbus_connection_call +g_dbus_connection_call_finish +g_dbus_connection_call_sync +g_dbus_connection_emit_signal +GDBusSignalCallback +g_dbus_connection_signal_subscribe +g_dbus_connection_signal_unsubscribe +g_dbus_connection_send_message +g_dbus_connection_send_message_with_reply +g_dbus_connection_send_message_with_reply_finish +g_dbus_connection_send_message_with_reply_sync +GDBusMessageFilterFunction +g_dbus_connection_add_filter +g_dbus_connection_remove_filter +GDBusInterfaceVTable +GDBusInterfaceMethodCallFunc +GDBusInterfaceGetPropertyFunc +GDBusInterfaceSetPropertyFunc +g_dbus_connection_register_object +g_dbus_connection_unregister_object +GDBusSubtreeVTable +GDBusSubtreeEnumerateFunc +GDBusSubtreeIntrospectFunc +GDBusSubtreeDispatchFunc +GDBusSubtreeFlags +g_dbus_connection_register_subtree +g_dbus_connection_unregister_subtree + +G_DBUS_CONNECTION +G_IS_DBUS_CONNECTION +G_TYPE_DBUS_CONNECTION +g_dbus_connection_get_type +G_DBUS_CONNECTION_CLASS +G_IS_DBUS_CONNECTION_CLASS +G_DBUS_CONNECTION_GET_CLASS +
+ +
+gdbusmethodinvocation +GDBusMethodInvocation +GDBusMethodInvocation +GDBusMethodInvocationClass +g_dbus_method_invocation_new +g_dbus_method_invocation_get_sender +g_dbus_method_invocation_get_object_path +g_dbus_method_invocation_get_interface_name +g_dbus_method_invocation_get_method_name +g_dbus_method_invocation_get_method_info +g_dbus_method_invocation_get_connection +g_dbus_method_invocation_get_message +g_dbus_method_invocation_get_parameters +g_dbus_method_invocation_get_user_data +g_dbus_method_invocation_return_value +g_dbus_method_invocation_return_error +g_dbus_method_invocation_return_error_valist +g_dbus_method_invocation_return_error_literal +g_dbus_method_invocation_return_gerror +g_dbus_method_invocation_return_dbus_error + +G_DBUS_METHOD_INVOCATION +G_IS_DBUS_METHOD_INVOCATION +G_TYPE_DBUS_METHOD_INVOCATION +g_dbus_method_invocation_get_type +G_DBUS_METHOD_INVOCATION_CLASS +G_IS_DBUS_METHOD_INVOCATION_CLASS +G_DBUS_METHOD_INVOCATION_GET_CLASS +
+ +
+gdbusnameowning +GBusAcquiredCallback +GBusNameAcquiredCallback +GBusNameLostCallback +GBusNameOwnerFlags +g_bus_own_name +g_bus_own_name_on_connection +g_bus_unown_name +
+ +
+gdbusnamewatching +GBusNameAppearedCallback +GBusNameVanishedCallback +GBusNameWatcherFlags +g_bus_watch_name +g_bus_watch_name_on_connection +g_bus_unwatch_name +
+ +
+gdbusproxywatching +GBusProxyAppearedCallback +GBusProxyVanishedCallback +g_bus_watch_proxy +g_bus_watch_proxy_on_connection +g_bus_unwatch_proxy +
+ +
+gdbuserror +GDBusError +G_DBUS_ERROR +g_dbus_error_is_remote_error +g_dbus_error_get_remote_error +g_dbus_error_strip_remote_error +GDBusErrorEntry +g_dbus_error_register_error_domain +g_dbus_error_register_error +g_dbus_error_unregister_error +g_dbus_error_new_for_dbus_error +g_dbus_error_set_dbus_error +g_dbus_error_set_dbus_error_valist +g_dbus_error_encode_gerror +
+ +
+gdbusproxy +GDBusProxy +GDBusProxyFlags +GDBusProxy +GDBusProxyClass +g_dbus_proxy_new +g_dbus_proxy_new_finish +g_dbus_proxy_new_sync +g_dbus_proxy_get_flags +g_dbus_proxy_get_connection +g_dbus_proxy_get_unique_bus_name +g_dbus_proxy_get_object_path +g_dbus_proxy_get_interface_name +g_dbus_proxy_get_default_timeout +g_dbus_proxy_set_default_timeout +g_dbus_proxy_get_cached_property +g_dbus_proxy_set_cached_property +g_dbus_proxy_get_cached_property_names +g_dbus_proxy_set_interface_info +g_dbus_proxy_get_interface_info +g_dbus_proxy_call +g_dbus_proxy_call_finish +g_dbus_proxy_call_sync + +G_DBUS_PROXY +G_IS_DBUS_PROXY +G_TYPE_DBUS_PROXY +g_dbus_proxy_get_type +G_DBUS_PROXY_CLASS +G_IS_DBUS_PROXY_CLASS +G_DBUS_PROXY_GET_CLASS +
+ +
+gdbusintrospection +GDBusAnnotationInfo +GDBusArgInfo +GDBusMethodInfo +GDBusSignalInfo +GDBusPropertyInfoFlags +GDBusPropertyInfo +GDBusInterfaceInfo +GDBusNodeInfo +g_dbus_annotation_info_lookup +g_dbus_interface_info_lookup_method +g_dbus_interface_info_lookup_signal +g_dbus_interface_info_lookup_property +g_dbus_interface_info_generate_xml +g_dbus_node_info_new_for_xml +g_dbus_node_info_lookup_interface +g_dbus_node_info_generate_xml +G_TYPE_DBUS_NODE_INFO +G_TYPE_DBUS_INTERFACE_INFO +G_TYPE_DBUS_METHOD_INFO +G_TYPE_DBUS_SIGNAL_INFO +G_TYPE_DBUS_PROPERTY_INFO +G_TYPE_DBUS_ARG_INFO +G_TYPE_DBUS_ANNOTATION_INFO +g_dbus_node_info_ref +g_dbus_interface_info_ref +g_dbus_method_info_ref +g_dbus_signal_info_ref +g_dbus_property_info_ref +g_dbus_arg_info_ref +g_dbus_annotation_info_ref +g_dbus_node_info_unref +g_dbus_interface_info_unref +g_dbus_method_info_unref +g_dbus_signal_info_unref +g_dbus_property_info_unref +g_dbus_arg_info_unref +g_dbus_annotation_info_unref +
diff --git a/docs/reference/gio/gio.types b/docs/reference/gio/gio.types index e8f973822..38ffb556f 100644 --- a/docs/reference/gio/gio.types +++ b/docs/reference/gio/gio.types @@ -107,3 +107,14 @@ g_volume_monitor_get_type g_zlib_compressor_get_type g_zlib_compressor_format_get_type g_zlib_decompressor_get_type +g_dbus_message_get_type +g_dbus_connection_get_type +g_bus_type_get_type +g_bus_name_owner_flags_get_type +g_dbus_error_get_type +g_dbus_proxy_get_type +g_dbus_method_invocation_get_type +g_dbus_server_get_type +g_dbus_auth_observer_get_type +g_credentials_get_type +g_unix_credentials_message_get_type diff --git a/docs/reference/gio/migrating-gconf.xml b/docs/reference/gio/migrating-gconf.xml new file mode 100644 index 000000000..23c2ddb55 --- /dev/null +++ b/docs/reference/gio/migrating-gconf.xml @@ -0,0 +1,418 @@ + + Migrating from GConf to GSettings + +
+ Before you start + + + Converting individual applications and their settings from GConf to + GSettings can be done at will. But desktop-wide settings like font or + theme settings often have consumers in multiple modules. Therefore, + some consideration has to go into making sure that all users of a setting + are converted to GSettings at the same time or that the program + responsible for configuring that setting continues to update the value in + both places. + + + It is always a good idea to have a look at how others have handled + similar problems before. An examplaric conversion can be found e.g. + in the gsettings-tutorial branch of gnome-utils. + +
+ +
+ Conceptual differences + + + Conceptually, GConf and GSettings are fairly similar. Both + have a concept of pluggable backends. Both keep information + about keys and their types in schemas. Both have a concept of + mandatory values, which lets you implement lock-down. + + + There are some differences in the approach to schemas. GConf + installs the schemas into the database and has API to handle + schema information (gconf_client_get_default_from_schema(), + gconf_value_get_schema(), etc). GSettings on the other hand + assumes that an application knows its own schemas, and does + not provide API to handle schema information at runtime. + GSettings is also more strict about requiring a schema whenever + you want to read or write a key. To deal with more free-form + information that would appear in schema-less entries in GConf, + GSettings allows for schemas to be 'relocatable'. + + + One difference in the way applications interact with their + settings is that with GConf you interact with a tree of + settings (ie the keys you pass to functions when reading + or writing values are actually paths with the actual name + of the key as the last element. With GSettings, you create + a GSettings object which has an implicit prefix that determines + where the settings get stored in the global tree of settings, + but the keys you pass when reading or writing values are just + the key names, not the full path. + +
+ +
+ GConfClient (and GConfBridge) API conversion + + + Most people use GConf via the high-level #GConfClient API. + The corresponding API is the #GSettings object. While not + every GConfClient function has a direct GSettings equivalent, + many do: + + + + GConfClientGSettings + + + gconf_client_get_default()no direct equivalent, + instead you call g_settings_new() for the schemas you use + gconf_client_set()g_settings_set() + gconf_client_get()g_settings_get() + gconf_client_get_bool()g_settings_get_boolean() + gconf_client_set_bool()g_settings_set_boolean() + gconf_client_get_int()g_settings_get_int() + gconf_client_set_int()g_settings_set_int() + gconf_client_get_float()g_settings_get_double() + gconf_client_set_float()g_settings_set_double() + gconf_client_get_string()g_settings_get_string() + gconf_client_set_string()g_settings_set_string() + gconf_client_get_list()for string lists, see g_settings_get_strv(), else see g_settings_get_value() and #GVariant API + gconf_client_set_list()for string lists, see g_settings_set_strv(), else see g_settings_set_value() and #GVariant API + gconf_entry_get_is_writable()g_settings_is_writable() + gconf_client_notify_add()not required, the #GSettings::changed signal is emitted automatically + gconf_client_add_dir()not required, each GSettings instance automatically watches all keys in its path + #GConfChangeSetg_settings_delay(), g_settings_apply() + gconf_client_get_default_from_schema()no equivalent, applications are expected to know their schema + gconf_client_all_entries()no equivalent, applications are expected to know their schema, and GSettings does not allow schema-less entries + gconf_client_get_without_default()no equivalent + gconf_bridge_bind_property()g_settings_bind() + gconf_bridge_bind_property_full()g_settings_bind_with_mapping() + + +
+
+ + GConfBridge was a third-party library that used GConf to bind an object property + to a particular configuration key. GSettings offers this service itself. + + + There is a pattern that is sometimes used for GConf, where a setting can have + explicit 'value A', explicit 'value B' or 'use the system default'. With GConf, + 'use the system default' is sometimes implemented by unsetting the user value. + + + This is not possible in GSettings, since it does not have API to determine if a value + is the default and does not let you unset values. The recommended way (and much + clearer) way in which this can be implemented in GSettings is to have a separate + 'use-system-default' boolean setting. + +
+ +
+ Change notification + + + GConf requires you to call gconf_client_add_dir() and + gconf_client_notify_add() to get change notification. With + GSettings, this is not necessary; signals get emitted automatically + for every change. + + + The #GSettings::changed signal is emitted for each changed key. + There is also a #GSettings::change-event signal that you can handle + if you need to see groups of keys that get changed at the same time. + + + GSettings also notifies you about changes in writability of keys, + with the #GSettings::writable-changed signal (and the + #GSettings::writable-change-event signal). + +
+ +
Change sets + + GConf has a a concept of a set of changes which can be applied or reverted + at once: #GConfChangeSet (GConf doesn't actually apply changes atomically, + which is one of its shortcomings). + + + Instead of a separate object to represent a change set, GSettings has a + 'delayed-apply' mode, which can be turned on for a GSettings object by + calling g_settings_delay(). In this mode, changes done to the GSettings + object are not applied - they are still visible when calling g_settings_get() + on the same object, but not to other GSettings instances + or even other processes. + + + To apply the pending changes all at once (GSettings does + atomicity here), call g_settings_apply(). To revert the pending changes, + call g_settings_revert() or just drop the reference to the #GSettings object. + +
+ +
+ Schema conversion + + + If you are porting your application from GConf, most likely you already + have a GConf schema. GIO comes with a commandline tool + gsettings-schema-convert + that can help with the task of converting a GConf schema into + an equivalent GSettings schema. The tool is not perfect and + may need assistence in some cases. + + An example for using gsettings-schema-convert + Running gsettings-schema-convert --gconf --xml --schema-id "org.gnome.font-rendering" --output org.gnome.font-rendering.gschema.xml destop_gnome_font_rendering.schemas on the following desktop_gnome_font_rendering.schemas file: + + + + + + /schemas/desktop/gnome/font_rendering/dpi + /desktop/gnome/font_rendering/dpi + gnome + int + 96 + + DPI + The resolution used for converting font sizes to pixel sizes, in dots per inch. + + + + +]]> + +produces a org.gnome.font-rendering.gschema.xml file with the following content: + + + + + 96 + DPI + The resolution used for converting font sizes to pixel sizes, in dots per inch. + + + +]]> + + + + + + GSettings schemas are identified at runtime by their id (as specified + in the XML source file). It is recommended to use a dotted name as schema + id, similar in style to a DBus bus name, e.g. "org.gnome.font-rendering". + The filename used for the XML schema source is immaterial, but + schema compiler expects the files to have the extension + .gschema.xml. It is recommended to simply + use the schema id as the filename, followed by this extension, + e.g. org.gnome.font-rendering.gschema.xml. + + + + The XML source file for your GSettings schema needs to get installed + into $datadir/glib-2.0/schemas, and needs to be + compiled into a binary form. At runtime, GSettings looks for compiled + schemas in the glib-2.0/schemas subdirectories + of all XDG_DATA_DIRS directories, so if you install + your schema in a different location, you need to set the + XDG_DATA_DIRS environment variable appropriately. + + + Schemas are compiled into binary form by the + glib-compile-schemas utility. + GIO provides a gschema_compile + variable for the schema compiler, which can be used in + configure.in as follows: + +GLIB_GSETTINGS + + The corresponding Makefile.am fragment looks like + this: + +# gsettingsschemadir and gschema_compile are defined by the GLIB_GSETTINGS +# macro in configure.ac +gsettingsschema_DATA = my.app.gschema.xml +# This rule will check your schemas for validity before installation +@GSETTINGS_CHECK_RULE@ +if GSETTINGS_SCHEMAS_INSTALL +install-data-hook: + $(GLIB_COMPILE_SCHEMAS) $(DESTDIR)$(gsettingsschemadir) +endif + + + + + One possible pitfall in doing schema conversion is that the default + values in GSettings schemas are parsed by the #GVariant parser. + This means that strings need to include quotes in the XML. Also note + that the types are now specified as #GVariant type strings. + +string +rgb +]]> + + becomes + + + 'rgb' + +]]> + + + + Another possible complication is that GConf specifies full paths + for each key, while a GSettings schema has a 'path' attribute that + contains the prefix for all the keys in the schema, and individual + keys just have a simple name. So + +/schemas/desktop/gnome/font_rendering/antialiasing +]]> + + becomes + + + +]]> + + + + Default values can be localized in both GConf and GSettings schemas, + but GSettings uses gettext for the localization. You can specify + the gettext domain to use in the gettext-domain + attribute. Therefore, when converting localized defaults in GConf, + +/schemas/apps/my_app/font_size + + 18 + + + 24 + + +]]> + + becomes + + + ... + + 18 + +]]> + + Note how we used the context attribute to add msgctxt - "18" is not a + good string to look up in gettext by itself. Also note that the value + 24 is not present in the schema anymore. It has to be added to the + gettext catalog for "be" instead. + + + GSettings schemas have optional summary and + description elements for each key which + correspond to the short and + long elements in the GConf schema and + will be used in similar ways by a future gsettings-editor, so you + should use the same conventions for them: The summary is just a short + label with no punctuation, the description can be one or more complete + sentences. Translations for these strings will also be handled + via gettext, so you should arrange for these strings to be + extracted into your gettext catalog. + + + GSettings is a bit more restrictive about key names than GConf. Key + names in GSettings can be at most 32 characters long, and must only + consist of lowercase characters, numbers and dashes, with no + consecutive dashes. The first character must not be a number or dash, + and the last character cannot be '-'. + + + If you are using the GConf backend for GSettings during the + transition, you may want to keep your key names the same they + were in GConf, so that existing settings in the users GConf + database are preserved. You can achieve this by using the + with the + glib-compile-schemas schema + compiler. Note that this option is only meant + to ease the process of porting your application, allowing parts + of your application to continue to access GConf and parts to use + GSettings. By the time you have finished porting your application + you must ensure that all key names are valid. + +
+ +
Data conversion + + GConf comes with a GSettings backend that can be used to + facility the transition to the GSettings API until you are + ready to make the jump to a different backend (most likely + dconf). To use it, you need to set the GSETTINGS_BACKEND + to 'gconf', e.g. by using + + g_setenv ("GSETTINGS_BACKEND", "gconf", TRUE); + + early on in your program. Note that this backend is meant purely + as a transition tool, and should not be used in production. + + + GConf also comes with a utility called + gsettings-data-convert, which is designed to help + with the task of migrating user settings from GConf into another + GSettings backend. It can be run manually, but it is designed to be + executed automatically, every time a user logs in. It keeps track of + the data migrations that it has already done, and it is harmless to + run it more than once. + + + To make use of this utility, you must install a keyfile in the + directory /usr/share/GConf/gsettings which + lists the GSettings keys and GConf paths to map to each other, for + each schema that you want to migrate user data for. + + + Here is an example: + + + + The last key demonstrates that it may be necessary to modify the key + name to comply with stricter GSettings key name rules. Of course, + that means your application must use the new key names when looking + up settings in GSettings. + + + The last group in the example also shows how to handle the case + of 'relocatable' schemas, which don't have a fixed path. You can + specify the path to use in the group name, separated by a colon. + + + There are some limitations: gsettings-data-convert + does not do any transformation of the values. And it does not handle + complex GConf types other than lists of strings or integers. + + + Don't forget to require GConf 2.31.1 or newer in your configure + script if you are making use of the GConf backend or the conversion + utility. + +
+
diff --git a/docs/reference/gio/migrating-gdbus.xml b/docs/reference/gio/migrating-gdbus.xml new file mode 100644 index 000000000..14e70b702 --- /dev/null +++ b/docs/reference/gio/migrating-gdbus.xml @@ -0,0 +1,358 @@ + + Migrating from dbus-glib to GDBus + +
+ Conceptual differences + + + The central concepts of D-Bus are modelled in a very similar way + in dbus-glib and GDBus. Both have a objects representing connections, + proxies and method invocations. But there are some important + differences: + + + dbus-glib uses libdbus, GDBus doesn't. Instead, it relies on GIO + streams as transport layer, and has its own implementation for the + the D-Bus connection setup and authentication. Apart from using + streams as transport, avoiding libdbus also lets GDBus avoid some + thorny multithreading issues. + + + dbus-glib uses the GObject type system for method arguments and + return values, including a homegrown container specialization + mechanism. GDBus relies uses the #GVariant type system which is + explicitly designed to match D-Bus types. + + + The typical way to export an object in dbus-glib involves generating + glue code from XML introspection data using dbus-binding-tool. GDBus does not (yet?) use code generation; you are expected to + embed the introspection data in your application code. + + + +
+ +
+ API comparison + + + dbus-glib APIs and their GDBus counterparts + + + dbus-glibGDBus + + + #DBusGConnection#GDBusConnection + #DBusGProxy#GDBusProxy + #DBusGMethodInvocation#GDBusMethodInvocation + dbus_g_bus_get()g_bus_get_sync(), also see + g_bus_get() + dbus_g_proxy_new_for_name()g_dbus_proxy_new_sync(), also see + g_dbus_proxy_new() + dbus_g_proxy_add_signal()not needed, use the generic #GDBusProxy::g-signal + dbus_g_proxy_connect_signal()use g_signal_connect() with #GDBusProxy::g-signal + dbus_g_connection_register_g_object()g_dbus_connection_register_object() + dbus_g_connection_unregister_g_object()g_dbus_connection_unregister_object() + dbus_g_object_type_install_info()introspection data is installed while registering + an object, see g_dbus_connection_register_object() + dbus_g_proxy_begin_call()g_dbus_proxy_call() + dbus_g_proxy_end_call()g_dbus_proxy_call_finish() + dbus_g_proxy_call()g_dbus_proxy_call_sync() + dbus_g_error_domain_register()g_dbus_error_register_error_domain() + dbus_g_error_has_name()no direct equivalent, see g_dbus_error_get_remote_error() + dbus_g_method_return()g_dbus_method_invocation_return_value() + dbus_g_method_return_error()g_dbus_method_invocation_return_error() and variants + dbus_g_method_get_sender()g_dbus_method_invocation_get_sender() + + +
+
+ +
+ Owning bus names + + Using dbus-glib, you typically call RequestName manually + to own a name, like in the following excerpt: + message); + g_error_free (error); + } + else + { + g_warning ("Failed to acquire %s", NAME_TO_CLAIM); + } + goto out; + } + + if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) + { + if (error != NULL) + { + g_warning ("Failed to acquire %s: %s", + NAME_TO_CLAIM, error->message); + g_error_free (error); + } + else + { + g_warning ("Failed to acquire %s", NAME_TO_CLAIM); + } + exit (1); + } + + dbus_g_proxy_add_signal (system_bus_proxy, "NameLost", + G_TYPE_STRING, G_TYPE_INVALID); + dbus_g_proxy_connect_signal (system_bus_proxy, "NameLost", + G_CALLBACK (on_name_lost), NULL, NULL); + + /* further setup ... */ +]]> + + + + While you can do things this way with GDBus too, using + g_dbus_proxy_call_sync(), it is much nicer to use the high-level API + for this: + + + Note that g_bus_own_name() works asynchronously and requires + you to enter your mainloop to await the on_name_aquired() + callback. Also note that in order to avoid race conditions (e.g. + when your service is activated by a method call), you have to export + your manager object before acquiring the + name. The on_bus_acquired() callback is the right place to do + such preparations. + +
+ +
+ Creating proxies for well-known names + + dbus-glib lets you create proxy objects for well-known names, like the + following example: + + + For a #DBusGProxy constructed like this, method calls will be sent to + the current owner of the name, and that owner can change over time. + + + In contrast, #GDBusProxy instances are always bound to a unique name. + To get a proxy for a well-known name, you either have to call + GetNameOwner yourself and construct a proxy for the unique name + of the current name owner, or use the high-level API. The latter + option is highly recommended: + + + Like g_bus_own_name(), g_bus_watch_proxy() is asynchronous and + you are expected to enter your mainloop to await the on_proxy_appeared() + callback. Note that GDBus also does all the setup operations for the + proxy asynchronously, and only calls your callback when the proxy + is ready for use. + +
+
+ Client-side GObject bindings + + + dbus-glib comes with dbus-binding-tool, which + can produce somewhat nice client-side wrappers for a D-Bus interface. + GDBus does not have code-generation at this point, but #GDBusProxy + is designed to allow the creating of client-side wrappers by + subclassing #GDBusProxy. + + + For an example of a #GDBusProxy-derived class that wraps a D-Bus + interface in a type-safe way, see . The comparison is as + follows: + + Wrapping the org.freedesktop.Accounts.User D-Bus interface in the AccountUser GObject type + + + D-Bus conceptGObject concept + + + + AutomaticLogin property + + AccountsUser:automatic-login GObject property + C getter: accounts_user_get_automatic_login() + Watch changes via the notify::automatic-login signal + + + + RealName property + + AccountsUser:real-name GObject property + C getter: accounts_user_get_real_name() + Watch changes via the notify::real-name signal + + + + UserName property + + AccountsUser:user-name GObject property + C getter: accounts_user_get_user_name() + Watch changes via the notify::user-name signal + + + + Changed signal + + AccountsUser::changed GObject signal + Watch via e.g. g_signal_connect() + + + + Frobnicate method + + Use accounts_user_frobnicate() + accounts_user_frobnicate_finish() or accounts_user_frobnicate_sync() to invoke + + + + +
+
+ GDBusProxy subclass exampleFIXME: MISSING XINCLUDE CONTENT +
+ +
+ Exporting objects + + + With dbus-glib, exporting an object over D-Bus works by generating + a bunch of glue code from your introspection XML with + dbus-binding-tool. The glue code gets included in + your source, and you need to call + + dbus_g_object_type_install_info (TYPE_MYOBJECT, + &dbus_glib_myobject_object_info); + + in your class_init() function to tell dbus-glib about your type. + To actually export an instance, you call + + dbus_g_connection_register_g_object (system_bus_connection, + my_object_path, + G_OBJECT (my_object)); + + + + + The GDBus way of exporting an object works by embedding the + introspection XML in the source, creating introspection data + structures from it with g_dbus_node_info_new_for_xml(), and + passing that along when you register the object: + " + " " + " " + " " + " " + " " + " " + ""; + + /* parse introspection data */ + introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + + / + id = g_dbus_connection_register_object (connection, + "/org/gtk/GDBus/TestObject", + "org.gtk.GDBus.TestPeerInterface", + introspection_data->interfaces[0], + &interface_vtable, + NULL, /* user_data */ + NULL, /* user_data_free_func */ + NULL); /* GError** */ + +]]> + + + + The actual implementation of the exported object is done by specifying + a #GDBusInterfaceVTable that has method_call(), get_property() and + set_property() methods. There is no direct support beyond that for + exporting #GObjects, so there is quite a bit of manual work involved, + as you can see in the following example. + + + Since the VTable methods don't have any direct #GObject support, we + pass the exported object as @user_data. Also note that we have to handle + the emission of the PropertiesChanged signal ourselves, by connecting + to ::notify. + + Exporting a GObjectFIXME: MISSING XINCLUDE CONTENT +
+ +
diff --git a/docs/reference/gio/migrating-gnome-vfs.xml b/docs/reference/gio/migrating-gnome-vfs.xml new file mode 100644 index 000000000..ba3987cad --- /dev/null +++ b/docs/reference/gio/migrating-gnome-vfs.xml @@ -0,0 +1,133 @@ + + Migrating from GnomeVFS to GIO + + + Comparison of GnomeVFS and GIO concepts + + + GnomeVFSGIO + + + GnomeVFSURIGFile + GnomeVFSFileInfoGFileInfo + GnomeVFSResultGError, with G_IO_ERROR values + GnomeVFSHandle & GnomeVFSAsyncHandleGInputStream or GOutputStream + GnomeVFSDirectoryHandleGFileEnumerator + mime typecontent type + GnomeVFSMonitorGFileMonitor + GnomeVFSVolumeMonitorGVolumeMonitor + GnomeVFSVolumeGMount + GnomeVFSDriveGVolume + -GDrive + GnomeVFSContextGCancellable + gnome_vfs_async_cancelg_cancellable_cancel + + +
+ +
+ Trash handling + + + The handling of trashed files has been changed in GIO, compared + to gnome-vfs. gnome-vfs has a home-grown trash implementation that + predates the freedesktop.org Desktop Trash Can specification + that is implemented in GIO. The location for storing trashed files + has changed from $HOME/.Trash to + $HOME/.local/share/Trash (or more correctly + $XDG_DATA_HOME/Trash), which means that + there is a need for migrating files that have been trashed by + gnome-vfs to the new location. + + + In gnome-vfs, the trash:// scheme offering a + merged view of all trash directories was implemented in nautilus, + and trash-handling applications had to find and monitor all trash + directories themselves. With GIO, the trash:// + implementation has been moved to gvfs and applications can simply + monitor that location: + + +static void +file_changed (GFileMonitor *file_monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + switch (event_type) + { + case G_FILE_MONITOR_EVENT_DELETED: + g_print ("'%s' removed from trash\n", g_file_get_basename (child)); + break; + case G_FILE_MONITOR_EVENT_CREATED: + g_print ("'%s' added to trash\n", g_file_get_basename (child)); + break; + default: ; + } +} + +static void +start_monitoring_trash (void) +{ + GFile *file; + GFileMonitor *monitor; + + file = g_file_new_for_uri ("trash://"); + monitor = g_file_monitor_directory (file, 0, NULL, NULL); + g_object_unref (file); + + g_signal_connect (monitor, "changed", G_CALLBACK (file_changed), NULL); + + /* ... */ + +} + + + GIO exposes some useful metadata about trashed files. There are + trash::orig-path and trash::deletion-date attributes. The + standard::icon attribute of the trash:// + itself provides a suitable icon for displaying the trash can on + the desktop. If you are using this icon, make sure to monitor + this attribute for changes, since the icon may be updated to + reflect that state of the trash can. + + + Moving a file to the trash is much simpler with GIO. Instead of + using gnome_vfs_find_directory() with %GNOME_VFS_DIRECTORY_KIND_TRASH + to find out where to move the trashed file, just use the g_file_trash() + function. + +
+ +
+ Operations on multiple files + + + gnome-vfs has the dreaded gnome_vfs_xfer_uri_list() function which + has tons of options and offers the equivalent of cp, mv, ln, mkdir + and rm at the same time. + + + GIO offers a much simpler I/O scheduler functionality instead, that + lets you schedule a function to be called in a separate thread, or + if threads are not available, as an idle in the mainloop. + See g_io_scheduler_push_job(). + + +
+ +
+ Mime monitoring + + + gnome-vfs offered a way to monitor the association between mime types + and default handlers for changes, with the #GnomeVFSMIMEMonitor object. + GIO does not offer a replacement for this functionality at this time, + since we have not found a compelling use case where + #GnomeVFSMIMEMonitor was used. If you think you have such a use + case, please report it at + bugzilla.gnome.org. + +
+
diff --git a/docs/reference/gio/migrating.xml b/docs/reference/gio/migrating-posix.xml similarity index 100% rename from docs/reference/gio/migrating.xml rename to docs/reference/gio/migrating-posix.xml diff --git a/docs/reference/gio/overview.xml b/docs/reference/gio/overview.xml index 613538ee0..1ea257177 100644 --- a/docs/reference/gio/overview.xml +++ b/docs/reference/gio/overview.xml @@ -105,6 +105,31 @@ network connection stream + There is support for connecting to D-Bus, + sending and receiving messages, owning and watching bus names, + and making objects available on the bus: + + + GDBusConnection + a D-Bus connection + + + + GDBusMethodInvocation + for handling remove calls + + + + GDBusServer + helper for accepting connections + + + + GDBusProxy + proxy to access D-Bus interfaces on a remote object + + + Beyond these, GIO provides facilities for file monitoring, asynchronous I/O and filename completion. In addition to the interfaces, GIO provides implementations for the local case. @@ -254,8 +279,8 @@ This variable can be set to the name of a #GSettingsBackend implementation to override the default for debugging purposes. - The keyfile-based implementation that is included in GIO has - the name "keyfile", the one in dconf has the name "dconf-settings". + The memory-based implementation that is included in GIO has + the name "memory", the one in dconf has the name "dconf-settings". @@ -270,15 +295,86 @@ - - <envar>GSETTINGS_KEYFILE_BACKEND_STORE</envar> + + <envar>DBUS_SYSTEM_BUS_ADDRESS</envar> - This variable can be set to the path where the keyfile #GSettings - backend stores its data. By default, the keyfile is stored in - $HOME/.config/gsettings/store. + This variable is consulted to find the address of the D-Bus system + bus. For the format of D-Bus addresses, see the D-Bus + specification. - + + Setting this variable overrides platform-specific ways of determining + the system bus address. + + + + + <envar>DBUS_SESSION_BUS_ADDRESS</envar> + + + This variable is consulted to find the address of the D-Bus session bus. + + + Setting this variable overrides platform-specific ways of determining + the session bus address. + + + + + <envar>DBUS_STARTER_BUS_TYPE</envar> + + + This variable is consulted to find out the 'starter' bus for an + application that has been started via D-Bus activation. The possible + values are 'system' or 'session'. + + + + + <envar>G_DBUS_DEBUG</envar> + + + This variable can be set to a list of debug options, which + cause GLib to print out different types of debugging + information when using the D-Bus routines. + + + message + Show all sent and received D-Bus messages + + + authentication + Information about authentication + + + The special value all can be used to turn on + all debug options. + + + + + <envar>G_DBUS_COOKIE_SHA1_KEYRING_DIR</envar> + + + Can be used to override the directory used to store the + keyring used in the DBUS_COOKIE_SHA1 + authentication mechanism. Normally the directory used is + .dbus-keyrings in the user's home + directory. + + + + + <envar>G_DBUS_COOKIE_SHA1_KEYRING_DIR_IGNORE_PERMISSION</envar> + + + If set, the permissions of the directory used to store the + keyring used in the DBUS_COOKIE_SHA1 + authentication mechanism won't be checked. Normally the + directory must be readable only by the user. + +
diff --git a/docs/reference/glib/tmpl/keyfile.sgml b/docs/reference/glib/tmpl/keyfile.sgml index 4e72ad6ac..8080774f4 100644 --- a/docs/reference/glib/tmpl/keyfile.sgml +++ b/docs/reference/glib/tmpl/keyfile.sgml @@ -365,6 +365,30 @@ Flags which influence the parsing. @Returns: + + + + + +@key_file: +@group_name: +@key: +@error: +@Returns: + + + + + + + +@key_file: +@group_name: +@key: +@error: +@Returns: + + @@ -511,6 +535,28 @@ Flags which influence the parsing. @value: + + + + + +@key_file: +@group_name: +@key: +@value: + + + + + + + +@key_file: +@group_name: +@key: +@value: + + diff --git a/docs/reference/gobject/glib-mkenums.1 b/docs/reference/gobject/glib-mkenums.1 index f44c32089..7ac0f2efe 100644 --- a/docs/reference/gobject/glib-mkenums.1 +++ b/docs/reference/gobject/glib-mkenums.1 @@ -2,21 +2,12 @@ .\" Title: glib-mkenums .\" Author: [FIXME: author] [see http://docbook.sf.net/el/author] .\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: 05/14/2010 +.\" Date: 05/13/2010 .\" Manual: User Commands .\" Source: User Commands .\" Language: English .\" -.TH "GLIB\-MKENUMS" "1" "05/14/2010" "User Commands" "User Commands" -.\" ----------------------------------------------------------------- -.\" * Define some portability stuff -.\" ----------------------------------------------------------------- -.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.\" http://bugs.debian.org/507673 -.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html -.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' +.TH "GLIB\-MKENUMS" "1" "05/13/2010" "User Commands" "User Commands" .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- diff --git a/gio/Makefile.am b/gio/Makefile.am index a8fb2b7e4..e08278e48 100644 --- a/gio/Makefile.am +++ b/gio/Makefile.am @@ -79,6 +79,45 @@ gio-marshal.c: gio-marshal.h gio-marshal.list $(glib_genmarshal) --prefix=_gio_marshal $(srcdir)/gio-marshal.list --body --internal) > $@.tmp && \ mv $@.tmp $@ +gdbus_headers = \ + gdbusauthobserver.h \ + gcredentials.h \ + gdbusutils.h \ + gdbuserror.h \ + gdbusaddress.h \ + gdbusconnection.h \ + gdbusmessage.h \ + gdbusnameowning.h \ + gdbusnamewatching.h \ + gdbusproxywatching.h \ + gdbusproxy.h \ + gdbusintrospection.h \ + gdbusmethodinvocation.h \ + gdbusserver.h \ + $(NULL) + +gdbus_sources = \ + gdbusutils.h gdbusutils.c \ + gdbusaddress.h gdbusaddress.c \ + gdbusauthobserver.h gdbusauthobserver.c \ + gdbusauth.h gdbusauth.c \ + gdbusauthmechanism.h gdbusauthmechanism.c \ + gdbusauthmechanismanon.h gdbusauthmechanismanon.c \ + gdbusauthmechanismexternal.h gdbusauthmechanismexternal.c \ + gdbusauthmechanismsha1.h gdbusauthmechanismsha1.c \ + gdbuserror.h gdbuserror.c \ + gdbusconnection.h gdbusconnection.c \ + gdbusmessage.h gdbusmessage.c \ + gdbusnameowning.h gdbusnameowning.c \ + gdbusnamewatching.h gdbusnamewatching.c \ + gdbusproxywatching.h gdbusproxywatching.c \ + gdbusproxy.h gdbusproxy.c \ + gdbusprivate.h gdbusprivate.c \ + gdbusintrospection.h gdbusintrospection.c \ + gdbusmethodinvocation.h gdbusmethodinvocation.c \ + gdbusserver.h gdbusserver.c \ + $(NULL) + settings_headers = \ gsettingsbackend.h \ gsettings.h @@ -155,17 +194,18 @@ SUBDIRS += fam endif if OS_UNIX -appinfo_sources += gdesktopappinfo.c gdesktopappinfo.h +appinfo_sources += gdesktopappinfo.c platform_libadd += libasyncns/libasyncns.la xdgmime/libxdgmime.la platform_deps += libasyncns/libasyncns.la xdgmime/libxdgmime.la unix_sources = \ + gfiledescriptorbased.c \ gunixconnection.c \ + gunixcredentialsmessage.c \ gunixfdlist.c \ gunixfdmessage.c \ gunixmount.c \ gunixmount.h \ gunixmounts.c \ - gunixmounts.h \ gunixresolver.c \ gunixresolver.h \ gunixsocketaddress.c \ @@ -183,6 +223,7 @@ giounixinclude_HEADERS = \ gdesktopappinfo.h \ gfiledescriptorbased.h \ gunixconnection.h \ + gunixcredentialsmessage.h \ gunixmounts.h \ gunixfdlist.h \ gunixfdmessage.h \ @@ -240,6 +281,7 @@ libgio_2_0_la_SOURCES = \ gconverter.c \ gconverterinputstream.c \ gconverteroutputstream.c \ + gcredentials.c \ gdatainputstream.c \ gdataoutputstream.c \ gdrive.c \ @@ -252,8 +294,6 @@ libgio_2_0_la_SOURCES = \ gfile.c \ gfileattribute.c \ gfileattribute-priv.h \ - gfiledescriptorbased.h \ - gfiledescriptorbased.c \ gfileenumerator.c \ gfileicon.c \ gfileinfo.c \ @@ -327,6 +367,7 @@ libgio_2_0_la_SOURCES = \ $(unix_sources) \ $(win32_sources) \ $(settings_sources) \ + $(gdbus_sources) \ $(local_sources) \ $(marshal_sources) \ $(NULL) @@ -454,6 +495,7 @@ gio_headers = \ gzlibcompressor.h \ gzlibdecompressor.h \ $(settings_headers) \ + $(gdbus_headers) \ $(NULL) gioincludedir=$(includedir)/glib-2.0/gio/ @@ -528,10 +570,22 @@ gsettings_LDADD = \ libgio-2.0.la gsettings_SOURCES = gsettings-tool.c - schemadir = $(datadir)/glib-2.0/schemas dist_schema_DATA = gschema.dtd +# ------------------------------------------------------------------------ +# gdbus(1) tool + +bin_PROGRAMS += gdbus +gdbus_SOURCES = gdbus-tool.c +gdbus_LDADD = libgio-2.0.la + +completiondir = $(sysconfdir)/bash_completion.d +completion_SCRIPTS = gdbus-bash-completion.sh +EXTRA_DIST += $(completion_SCRIPTS) + +# ------------------------------------------------------------------------ + dist-hook: $(BUILT_EXTRA_DIST) ../build/win32/vs9/gio.vcproj files='$(BUILT_EXTRA_DIST)'; \ for f in $$files; do \ diff --git a/gio/gcredentials.c b/gio/gcredentials.c new file mode 100644 index 000000000..feedbf781 --- /dev/null +++ b/gio/gcredentials.c @@ -0,0 +1,345 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include +#include + +#include "gcredentials.h" +#include "gioerror.h" + +#ifdef __linux__ +#define __USE_GNU +#include +#include +#include +#include +#endif + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gcredentials + * @short_description: An object containing credentials + * @include: gio/gio.h + * + * The #GCredentials type is a reference-counted wrapper for the + * native credentials type. This information is typically used for + * identifying, authenticating and authorizing other processes. + * + * Some operating systems supports looking up the credentials of the + * remote peer of a communication endpoint - see e.g. + * g_socket_get_credentials(). + * + * Some operating systems supports securely sending and receiving + * credentials over a Unix Domain Socket, see + * #GUnixCredentialsMessage, g_unix_connection_send_credentials() and + * g_unix_connection_receive_credentials() for details. + * + * On Linux, the native credential type is a struct ucred - see + * the unix(7) man page for details. + */ + +struct _GCredentialsPrivate +{ +#ifdef __linux__ + struct ucred native; +#else +#warning Please add GCredentials support for your OS + guint foo; +#endif +}; + +G_DEFINE_TYPE (GCredentials, g_credentials, G_TYPE_OBJECT); + +static void +g_credentials_finalize (GObject *object) +{ + G_GNUC_UNUSED GCredentials *credentials = G_CREDENTIALS (object); + + if (G_OBJECT_CLASS (g_credentials_parent_class)->finalize != NULL) + G_OBJECT_CLASS (g_credentials_parent_class)->finalize (object); +} + + +static void +g_credentials_class_init (GCredentialsClass *klass) +{ + GObjectClass *gobject_class; + + g_type_class_add_private (klass, sizeof (GCredentialsPrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = g_credentials_finalize; +} + +static void +g_credentials_init (GCredentials *credentials) +{ + credentials->priv = G_TYPE_INSTANCE_GET_PRIVATE (credentials, G_TYPE_CREDENTIALS, GCredentialsPrivate); +#ifdef __linux__ + credentials->priv->native.pid = getpid (); + credentials->priv->native.uid = getuid (); + credentials->priv->native.gid = getgid (); +#endif +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_credentials_new: + * + * Creates a new #GCredentials object with credentials matching the + * the current process. + * + * Returns: A #GCredentials. Free with g_object_unref(). + * + * Since: 2.26 + */ +GCredentials * +g_credentials_new (void) +{ + return g_object_new (G_TYPE_CREDENTIALS, NULL); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_credentials_to_string: + * @credentials: A #GCredentials object. + * + * Creates a human-readable textual representation of @credentials + * that can be used in logging and debug messages. The format of the + * returned string may change in future GLib release. + * + * Returns: A string that should be freed with g_free(). + * + * Since: 2.26 + */ +gchar * +g_credentials_to_string (GCredentials *credentials) +{ + GString *ret; + + g_return_val_if_fail (G_IS_CREDENTIALS (credentials), NULL); + + ret = g_string_new ("GCredentials:"); +#ifdef __linux__ + g_string_append (ret, "linux:"); + if (credentials->priv->native.pid != -1) + g_string_append_printf (ret, "pid=%" G_GINT64_FORMAT ",", (gint64) credentials->priv->native.pid); + if (credentials->priv->native.uid != -1) + g_string_append_printf (ret, "uid=%" G_GINT64_FORMAT ",", (gint64) credentials->priv->native.uid); + if (credentials->priv->native.gid != -1) + g_string_append_printf (ret, "gid=%" G_GINT64_FORMAT ",", (gint64) credentials->priv->native.gid); + if (ret->str[ret->len - 1] == ',') + ret->str[ret->len - 1] = '\0'; +#else + g_string_append (ret, "unknown"); +#endif + + return g_string_free (ret, FALSE); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_credentials_is_same_user: + * @credentials: A #GCredentials. + * @other_credentials: A #GCredentials. + * @error: Return location for error or %NULL. + * + * Checks if @credentials and @other_credentials is the same user. + * + * This operation can fail if #GCredentials is not supported on the + * the OS. + * + * Returns: %TRUE if @credentials and @other_credentials has the same + * user, %FALSE otherwise or if @error is set. + * + * Since: 2.26 + */ +gboolean +g_credentials_is_same_user (GCredentials *credentials, + GCredentials *other_credentials, + GError **error) +{ + gboolean ret; + + g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE); + g_return_val_if_fail (G_IS_CREDENTIALS (other_credentials), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = FALSE; +#ifdef __linux__ + if (credentials->priv->native.uid == other_credentials->priv->native.uid) + ret = TRUE; +#else + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("GCredentials is not implemented on this OS")); +#endif + + return ret; +} + +/** + * g_credentials_get_native: + * @credentials: A #GCredentials. + * + * Gets a pointer to the native credentials structure. + * + * Returns: The pointer or %NULL if there is no #GCredentials support + * for the OS. Do not free the returned data, it is owned by + * @credentials. + * + * Since: 2.26 + */ +gpointer +g_credentials_get_native (GCredentials *credentials) +{ + gpointer ret; + g_return_val_if_fail (G_IS_CREDENTIALS (credentials), NULL); + +#ifdef __linux__ + ret = &credentials->priv->native; +#else + ret = NULL; +#endif + + return ret; +} + +/** + * g_credentials_set_native: + * @credentials: A #GCredentials. + * @native: A pointer to native credentials. + * + * Copies the native credentials from @native into @credentials. + * + * It is a programming error (which will cause an warning to be + * logged) to use this method if there is no #GCredentials support for + * the OS. + * + * Since: 2.26 + */ +void +g_credentials_set_native (GCredentials *credentials, + gpointer native) +{ +#ifdef __linux__ + memcpy (&credentials->priv->native, native, sizeof (struct ucred)); +#else + g_warning ("g_credentials_set_native: Trying to set credentials but GLib has no support " + "for the native credentials type. Please add support."); +#endif +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#ifdef G_OS_UNIX +/** + * g_credentials_get_unix_user: + * @credentials: A #GCredentials + * @error: Return location for error or %NULL. + * + * Tries to get the UNIX user identifier from @credentials. This + * method is only available on UNIX platforms. + * + * This operation can fail if #GCredentials is not supported on the + * OS or if the native credentials type does not contain information + * about the UNIX user. + * + * Returns: The UNIX user identifier or -1 if @error is set. + * + * Since: 2.26 + */ +uid_t +g_credentials_get_unix_user (GCredentials *credentials, + GError **error) +{ + uid_t ret; + + g_return_val_if_fail (G_IS_CREDENTIALS (credentials), -1); + g_return_val_if_fail (error == NULL || *error == NULL, -1); + +#ifdef __linux__ + ret = credentials->priv->native.uid; +#else + ret = -1; + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("There no GCredentials support for your your platform")); +#endif + + return ret; +} + +/** + * g_credentials_set_unix_user: + * @credentials: A #GCredentials. + * @uid: The UNIX user identifier to set. + * @error: Return location for error or %NULL. + * + * Tries to set the UNIX user identifier on @credentials. This method + * is only available on UNIX platforms. + * + * This operation can fail if #GCredentials is not supported on the + * OS or if the native credentials type does not contain information + * about the UNIX user. + * + * Returns: %TRUE if @uid was set, %FALSE if error is set. + * + * Since: 2.26 + */ +gboolean +g_credentials_set_unix_user (GCredentials *credentials, + uid_t uid, + GError **error) +{ + gboolean ret; + + g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE); + g_return_val_if_fail (uid != -1, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = FALSE; +#ifdef __linux__ + credentials->priv->native.uid = uid; + ret = TRUE; +#else + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("GCredentials is not implemented on this OS")); +#endif + + return ret; +} +#endif /* G_OS_UNIX */ + +#define __G_CREDENTIALS_C__ +#include "gioaliasdef.c" diff --git a/gio/gcredentials.h b/gio/gcredentials.h new file mode 100644 index 000000000..be1965f63 --- /dev/null +++ b/gio/gcredentials.h @@ -0,0 +1,108 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_CREDENTIALS_H__ +#define __G_CREDENTIALS_H__ + +#include + +#ifdef G_OS_UNIX +/* To get the uid_t type */ +#include +#include +#endif + +G_BEGIN_DECLS + +#define G_TYPE_CREDENTIALS (g_credentials_get_type ()) +#define G_CREDENTIALS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_CREDENTIALS, GCredentials)) +#define G_CREDENTIALS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_CREDENTIALS, GCredentialsClass)) +#define G_CREDENTIALS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_CREDENTIALS, GCredentialsClass)) +#define G_IS_CREDENTIALS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_CREDENTIALS)) +#define G_IS_CREDENTIALS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_CREDENTIALS)) + +typedef struct _GCredentialsClass GCredentialsClass; +typedef struct _GCredentialsPrivate GCredentialsPrivate; + +/** + * GCredentials: + * + * The #GCredentials structure contains only private data and + * should only be accessed using the provided API. + * + * Since: 2.26 + */ +struct _GCredentials +{ + /*< private >*/ + GObject parent_instance; + GCredentialsPrivate *priv; +}; + +/** + * GCredentialsClass: + * + * Class structure for #GCredentials. + * + * Since: 2.26 + */ +struct _GCredentialsClass +{ + /*< private >*/ + GObjectClass parent_class; + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); +}; + +GType g_credentials_get_type (void) G_GNUC_CONST; + +GCredentials *g_credentials_new (void); + +gchar *g_credentials_to_string (GCredentials *credentials); + +gpointer g_credentials_get_native (GCredentials *credentials); +void g_credentials_set_native (GCredentials *credentials, + gpointer native); + +gboolean g_credentials_is_same_user (GCredentials *credentials, + GCredentials *other_credentials, + GError **error); + +#ifdef G_OS_UNIX +uid_t g_credentials_get_unix_user (GCredentials *credentials, + GError **error); +gboolean g_credentials_set_unix_user (GCredentials *credentials, + uid_t uid, + GError **error); +#endif + +G_END_DECLS + +#endif /* __G_DBUS_PROXY_H__ */ diff --git a/gio/gdbus-bash-completion.sh b/gio/gdbus-bash-completion.sh new file mode 100644 index 000000000..79f4cb4b4 --- /dev/null +++ b/gio/gdbus-bash-completion.sh @@ -0,0 +1,33 @@ + +# Check for bash +[ -z "$BASH_VERSION" ] && return + +#################################################################################################### + + +__gdbus() { + local IFS=$'\n' + local cur=`_get_cword :` + + local suggestions=$(gdbus complete "${COMP_LINE}" ${COMP_POINT}) + COMPREPLY=($(compgen -W "$suggestions" -- "$cur")) + + # Remove colon-word prefix from COMPREPLY items + case "$cur" in + *:*) + case "$COMP_WORDBREAKS" in + *:*) + local colon_word=${cur%${cur##*:}} + local i=${#COMPREPLY[*]} + while [ $((--i)) -ge 0 ]; do + COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} + done + ;; + esac + ;; + esac +} + +#################################################################################################### + +complete -o nospace -F __gdbus gdbus diff --git a/gio/gdbus-tool.c b/gio/gdbus-tool.c new file mode 100644 index 000000000..5b5489354 --- /dev/null +++ b/gio/gdbus-tool.c @@ -0,0 +1,1816 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include + +/* ---------------------------------------------------------------------------------------------------- */ + +G_GNUC_UNUSED static void completion_debug (const gchar *format, ...); + +/* Uncomment to get debug traces in /tmp/gdbus-completion-debug.txt (nice + * to not have it interfere with stdout/stderr) + */ +#if 0 +G_GNUC_UNUSED static void +completion_debug (const gchar *format, ...) +{ + va_list var_args; + gchar *s; + static FILE *f = NULL; + + va_start (var_args, format); + s = g_strdup_vprintf (format, var_args); + if (f == NULL) + { + f = fopen ("/tmp/gdbus-completion-debug.txt", "a+"); + } + fprintf (f, "%s\n", s); + g_free (s); +} +#else +static void +completion_debug (const gchar *format, ...) +{ +} +#endif + +/* ---------------------------------------------------------------------------------------------------- */ + + +static void +remove_arg (gint num, gint *argc, gchar **argv[]) +{ + gint n; + + g_assert (num <= (*argc)); + + for (n = num; (*argv)[n] != NULL; n++) + (*argv)[n] = (*argv)[n+1]; + (*argv)[n] = NULL; + (*argc) = (*argc) - 1; +} + +static void +usage (gint *argc, gchar **argv[], gboolean use_stdout) +{ + GOptionContext *o; + gchar *s; + gchar *program_name; + + o = g_option_context_new (_("COMMAND")); + g_option_context_set_help_enabled (o, FALSE); + /* Ignore parsing result */ + g_option_context_parse (o, argc, argv, NULL); + program_name = g_path_get_basename ((*argv)[0]); + s = g_strdup_printf (_("Commands:\n" + " help Shows this information\n" + " introspect Introspect a remote object\n" + " monitor Monitor a remote object\n" + " call Invoke a method on a remote object\n" + "\n" + "Use \"%s COMMAND --help\" to get help on each command.\n"), + program_name); + g_free (program_name); + g_option_context_set_description (o, s); + g_free (s); + s = g_option_context_get_help (o, FALSE, NULL); + if (use_stdout) + g_print ("%s", s); + else + g_printerr ("%s", s); + g_free (s); + g_option_context_free (o); +} + +static void +modify_argv0_for_command (gint *argc, gchar **argv[], const gchar *command) +{ + gchar *s; + gchar *program_name; + + /* TODO: + * 1. get a g_set_prgname() ?; or + * 2. save old argv[0] and restore later + */ + + g_assert (g_strcmp0 ((*argv)[1], command) == 0); + remove_arg (1, argc, argv); + + program_name = g_path_get_basename ((*argv)[0]); + s = g_strdup_printf ("%s %s", (*argv)[0], command); + (*argv)[0] = s; + g_free (program_name); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +print_methods (GDBusConnection *c, + const gchar *name, + const gchar *path) +{ + GVariant *result; + GError *error; + const gchar *xml_data; + GDBusNodeInfo *node; + guint n; + guint m; + + error = NULL; + result = g_dbus_connection_call_sync (c, + name, + path, + "org.freedesktop.DBus.Introspectable", + "Introspect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 3000, /* 3 secs */ + NULL, + &error); + if (result == NULL) + { + g_printerr (_("Error: %s\n"), error->message); + g_error_free (error); + goto out; + } + if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)"))) + { + g_printerr (_("Error: Result is type `%s', expected `(s)'\n"), + g_variant_get_type_string (result)); + g_variant_unref (result); + goto out; + } + g_variant_get (result, "(&s)", &xml_data); + + error = NULL; + node = g_dbus_node_info_new_for_xml (xml_data, &error); + g_variant_unref (result); + if (node == NULL) + { + g_printerr (_("Error parsing introspection XML: %s\n"), error->message); + g_error_free (error); + goto out; + } + + for (n = 0; node->interfaces != NULL && node->interfaces[n] != NULL; n++) + { + const GDBusInterfaceInfo *iface = node->interfaces[n]; + for (m = 0; iface->methods != NULL && iface->methods[m] != NULL; m++) + { + const GDBusMethodInfo *method = iface->methods[m]; + g_print ("%s.%s \n", iface->name, method->name); + } + } + g_dbus_node_info_unref (node); + + out: + ; +} + +static void +print_paths (GDBusConnection *c, + const gchar *name, + const gchar *path) +{ + GVariant *result; + GError *error; + const gchar *xml_data; + GDBusNodeInfo *node; + guint n; + + error = NULL; + result = g_dbus_connection_call_sync (c, + name, + path, + "org.freedesktop.DBus.Introspectable", + "Introspect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 3000, /* 3 secs */ + NULL, + &error); + if (result == NULL) + { + g_printerr (_("Error: %s\n"), error->message); + g_error_free (error); + goto out; + } + if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)"))) + { + g_printerr (_("Error: Result is type `%s', expected `(s)'\n"), + g_variant_get_type_string (result)); + g_variant_unref (result); + goto out; + } + g_variant_get (result, "(&s)", &xml_data); + + //g_printerr ("xml=`%s'", xml_data); + + error = NULL; + node = g_dbus_node_info_new_for_xml (xml_data, &error); + g_variant_unref (result); + if (node == NULL) + { + g_printerr (_("Error parsing introspection XML: %s\n"), error->message); + g_error_free (error); + goto out; + } + + //g_printerr ("bar `%s'\n", path); + + if (node->interfaces != NULL) + g_print ("%s \n", path); + + for (n = 0; node->nodes != NULL && node->nodes[n] != NULL; n++) + { + gchar *s; + + //g_printerr ("foo `%s'\n", node->nodes[n].path); + + if (g_strcmp0 (path, "/") == 0) + s = g_strdup_printf ("/%s", node->nodes[n]->path); + else + s = g_strdup_printf ("%s/%s", path, node->nodes[n]->path); + + print_paths (c, name, s); + + g_free (s); + } + g_dbus_node_info_unref (node); + + out: + ; +} + +static void +print_names (GDBusConnection *c, + gboolean include_unique_names) +{ + GVariant *result; + GError *error; + GVariantIter *iter; + gchar *str; + GHashTable *name_set; + GList *keys; + GList *l; + + name_set = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + error = NULL; + result = g_dbus_connection_call_sync (c, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "ListNames", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 3000, /* 3 secs */ + NULL, + &error); + if (result == NULL) + { + g_printerr (_("Error: %s\n"), error->message); + g_error_free (error); + goto out; + } + if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(as)"))) + { + g_printerr (_("Error: Result is type `%s', expected `(as)'\n"), g_variant_get_type_string (result)); + g_variant_unref (result); + goto out; + } + g_variant_get (result, "(as)", &iter); + while (g_variant_iter_loop (iter, "s", &str)) + g_hash_table_insert (name_set, str, NULL); + g_variant_iter_free (iter); + g_variant_unref (result); + + error = NULL; + result = g_dbus_connection_call_sync (c, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "ListActivatableNames", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 3000, /* 3 secs */ + NULL, + &error); + if (result == NULL) + { + g_printerr (_("Error: %s\n"), error->message); + g_error_free (error); + goto out; + } + if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(as)"))) + { + g_printerr (_("Error: Result is type `%s', expected `(as)'\n"), g_variant_get_type_string (result)); + g_variant_unref (result); + goto out; + } + g_variant_get (result, "(as)", &iter); + while (g_variant_iter_loop (iter, "s", &str)) + g_hash_table_insert (name_set, str, NULL); + g_variant_iter_free (iter); + g_variant_unref (result); + + keys = g_hash_table_get_keys (name_set); + keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); + for (l = keys; l != NULL; l = l->next) + { + const gchar *name = l->data; + if (!include_unique_names && g_str_has_prefix (name, ":")) + continue; + + g_print ("%s \n", name); + } + g_list_free (keys); + + out: + g_hash_table_unref (name_set); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean opt_connection_system = FALSE; +static gboolean opt_connection_session = FALSE; +static gchar *opt_connection_address = NULL; + +static const GOptionEntry connection_entries[] = +{ + { "system", 'y', 0, G_OPTION_ARG_NONE, &opt_connection_system, N_("Connect to the system bus"), NULL}, + { "session", 'e', 0, G_OPTION_ARG_NONE, &opt_connection_session, N_("Connect to the session bus"), NULL}, + { "address", 'a', 0, G_OPTION_ARG_STRING, &opt_connection_address, N_("Connect to given D-Bus address"), NULL}, + { NULL } +}; + +static GOptionGroup * +connection_get_group (void) +{ + static GOptionGroup *g; + + g = g_option_group_new ("connection", + N_("Connection Endpoint Options:"), + N_("Options specifying the connection endpoint"), + NULL, + NULL); + g_option_group_add_entries (g, connection_entries); + return g; +} + +static GDBusConnection * +connection_get_dbus_connection (GError **error) +{ + GDBusConnection *c; + + c = NULL; + + /* First, ensure we have exactly one connect */ + if (!opt_connection_system && !opt_connection_session && opt_connection_address == NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("No connection endpoint specified")); + goto out; + } + else if ((opt_connection_system && (opt_connection_session || opt_connection_address != NULL)) || + (opt_connection_session && (opt_connection_system || opt_connection_address != NULL)) || + (opt_connection_address != NULL && (opt_connection_system || opt_connection_session))) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Multiple connection endpoints specified")); + goto out; + } + + if (opt_connection_system) + { + c = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); + } + else if (opt_connection_session) + { + c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + } + else if (opt_connection_address != NULL) + { + c = g_dbus_connection_new_for_address_sync (opt_connection_address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, /* GDBusAuthObserver */ + NULL, /* GCancellable */ + error); + } + + out: + return c; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GPtrArray * +call_helper_get_method_in_signature (GDBusConnection *c, + const gchar *dest, + const gchar *path, + const gchar *interface_name, + const gchar *method_name, + GError **error) +{ + GPtrArray *ret; + GVariant *result; + GDBusNodeInfo *node_info; + const gchar *xml_data; + const GDBusInterfaceInfo *interface_info; + const GDBusMethodInfo *method_info; + guint n; + + ret = NULL; + result = NULL; + node_info = NULL; + + result = g_dbus_connection_call_sync (c, + dest, + path, + "org.freedesktop.DBus.Introspectable", + "Introspect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 3000, /* 3 secs */ + NULL, + error); + if (result == NULL) + goto out; + + if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)"))) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Error: Result is type `%s', expected `(s)'\n"), + g_variant_get_type_string (result)); + goto out; + } + + g_variant_get (result, "(&s)", &xml_data); + node_info = g_dbus_node_info_new_for_xml (xml_data, error); + if (node_info == NULL) + goto out; + + interface_info = g_dbus_node_info_lookup_interface (node_info, interface_name); + if (interface_info == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Warning: According to introspection data, interface `%s' does not exist\n"), + interface_name); + goto out; + } + + method_info = g_dbus_interface_info_lookup_method (interface_info, method_name); + if (method_info == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Warning: According to introspection data, method `%s' does not exist on interface `%s'\n"), + method_name, + interface_name); + goto out; + } + + ret = g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_type_free); + for (n = 0; method_info->in_args != NULL && method_info->in_args[n] != NULL; n++) + { + g_ptr_array_add (ret, g_variant_type_new (method_info->in_args[n]->signature)); + } + + out: + if (node_info != NULL) + g_dbus_node_info_unref (node_info); + if (result != NULL) + g_variant_unref (result); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GVariant * +_g_variant_parse_me_harder (GVariantType *type, + const gchar *given_str, + GError **error) +{ + GVariant *value; + gchar *s; + guint n; + GString *str; + + str = g_string_new ("\""); + for (n = 0; given_str[n] != '\0'; n++) + { + if (G_UNLIKELY (given_str[n] == '\"')) + g_string_append (str, "\\\""); + else + g_string_append_c (str, given_str[n]); + } + g_string_append_c (str, '"'); + s = g_string_free (str, FALSE); + + value = g_variant_parse (type, + s, + NULL, + NULL, + error); + g_free (s); + + return value; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar *opt_call_dest = NULL; +static gchar *opt_call_object_path = NULL; +static gchar *opt_call_method = NULL; + +static const GOptionEntry call_entries[] = +{ + { "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_call_dest, N_("Destination name to invoke method on"), NULL}, + { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_call_object_path, N_("Object path to invoke method on"), NULL}, + { "method", 'm', 0, G_OPTION_ARG_STRING, &opt_call_method, N_("Method and interface name"), NULL}, + { NULL } +}; + +static gboolean +handle_call (gint *argc, + gchar **argv[], + gboolean request_completion, + const gchar *completion_cur, + const gchar *completion_prev) +{ + gint ret; + GOptionContext *o; + gchar *s; + GError *error; + GDBusConnection *c; + GVariant *parameters; + gchar *interface_name; + gchar *method_name; + GVariant *result; + GPtrArray *in_signature_types; + gboolean complete_names; + gboolean complete_paths; + gboolean complete_methods; + GVariantBuilder builder; + guint n; + + ret = FALSE; + c = NULL; + parameters = NULL; + interface_name = NULL; + method_name = NULL; + result = NULL; + in_signature_types = NULL; + + modify_argv0_for_command (argc, argv, "call"); + + o = g_option_context_new (NULL); + g_option_context_set_help_enabled (o, FALSE); + g_option_context_set_summary (o, _("Invoke a method on a remote object.")); + g_option_context_add_main_entries (o, call_entries, NULL /* GETTEXT_PACKAGE*/); + g_option_context_add_group (o, connection_get_group ()); + + complete_names = FALSE; + if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--dest") == 0) + { + complete_names = TRUE; + remove_arg ((*argc) - 1, argc, argv); + } + + complete_paths = FALSE; + if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--object-path") == 0) + { + complete_paths = TRUE; + remove_arg ((*argc) - 1, argc, argv); + } + + complete_methods = FALSE; + if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--method") == 0) + { + complete_methods = TRUE; + remove_arg ((*argc) - 1, argc, argv); + } + + if (!g_option_context_parse (o, argc, argv, NULL)) + { + if (!request_completion) + { + s = g_option_context_get_help (o, FALSE, NULL); + g_printerr ("%s", s); + g_free (s); + goto out; + } + } + + error = NULL; + c = connection_get_dbus_connection (&error); + if (c == NULL) + { + if (request_completion) + { + if (g_strcmp0 (completion_prev, "--address") == 0) + { + g_print ("unix:\n" + "tcp:\n" + "nonce-tcp:\n"); + } + else + { + g_print ("--system \n--session \n--address \n"); + } + } + else + { + g_printerr (_("Error connecting: %s\n"), error->message); + g_error_free (error); + } + goto out; + } + + /* validate and complete destination (bus name) */ + if (g_dbus_connection_get_unique_name (c) != NULL) + { + /* this only makes sense on message bus connections */ + if (complete_names) + { + print_names (c, FALSE); + goto out; + } + if (opt_call_dest == NULL) + { + if (request_completion) + g_print ("--dest \n"); + else + g_printerr (_("Error: Destination is not specified\n")); + goto out; + } + if (request_completion && g_strcmp0 ("--dest", completion_prev) == 0) + { + print_names (c, g_str_has_prefix (opt_call_dest, ":")); + goto out; + } + } + + /* validate and complete object path */ + if (complete_paths) + { + print_paths (c, opt_call_dest, "/"); + goto out; + } + if (opt_call_object_path == NULL) + { + if (request_completion) + g_print ("--object-path \n"); + else + g_printerr (_("Error: Object path is not specified\n")); + goto out; + } + if (request_completion && g_strcmp0 ("--object-path", completion_prev) == 0) + { + gchar *p; + s = g_strdup (opt_call_object_path); + p = strrchr (s, '/'); + if (p != NULL) + { + if (p == s) + p++; + *p = '\0'; + } + print_paths (c, opt_call_dest, s); + g_free (s); + goto out; + } + if (!request_completion && !g_variant_is_object_path (opt_call_object_path)) + { + g_printerr (_("Error: %s is not a valid object path\n"), opt_call_object_path); + goto out; + } + + /* validate and complete method (interface + method name) */ + if (complete_methods) + { + print_methods (c, opt_call_dest, opt_call_object_path); + goto out; + } + if (opt_call_method == NULL) + { + if (request_completion) + g_print ("--method \n"); + else + g_printerr (_("Error: Method name is not specified\n")); + goto out; + } + if (request_completion && g_strcmp0 ("--method", completion_prev) == 0) + { + print_methods (c, opt_call_dest, opt_call_object_path); + goto out; + } + s = strrchr (opt_call_method, '.'); + if (!request_completion && s == NULL) + { + g_printerr (_("Error: Method name `%s' is invalid\n"), opt_call_method); + goto out; + } + method_name = g_strdup (s + 1); + interface_name = g_strndup (opt_call_method, s - opt_call_method); + + /* All done with completion now */ + if (request_completion) + goto out; + + /* Introspect, for easy conversion - it's not fatal if we can't do this */ + in_signature_types = call_helper_get_method_in_signature (c, + opt_call_dest, + opt_call_object_path, + interface_name, + method_name, + &error); + if (in_signature_types == NULL) + { + //g_printerr ("Error getting introspection data: %s\n", error->message); + g_error_free (error); + error = NULL; + } + + /* Read parameters */ + g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); + for (n = 1; n < (guint) *argc; n++) + { + GVariant *value; + GVariantType *type; + + type = NULL; + if (in_signature_types != NULL) + { + if (n - 1 >= in_signature_types->len) + { + /* Only warn for the first param */ + if (n - 1 == in_signature_types->len) + { + g_printerr ("Warning: Introspection data indicates %d parameters but more was passed\n", + in_signature_types->len); + } + } + else + { + type = in_signature_types->pdata[n - 1]; + } + } + + error = NULL; + value = g_variant_parse (type, + (*argv)[n], + NULL, + NULL, + &error); + if (value == NULL) + { + g_error_free (error); + error = NULL; + value = _g_variant_parse_me_harder (type, (*argv)[n], &error); + if (value == NULL) + { + if (type != NULL) + { + s = g_variant_type_dup_string (type); + g_printerr (_("Error parsing parameter %d of type `%s': %s\n"), + n, + s, + error->message); + g_free (s); + } + else + { + g_printerr (_("Error parsing parameter %d: %s\n"), + n, + error->message); + } + g_error_free (error); + g_variant_builder_clear (&builder); + goto out; + } + } + g_variant_builder_add_value (&builder, value); + } + parameters = g_variant_builder_end (&builder); + + if (parameters != NULL) + parameters = g_variant_ref_sink (parameters); + result = g_dbus_connection_call_sync (c, + opt_call_dest, + opt_call_object_path, + interface_name, + method_name, + parameters, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (result == NULL) + { + g_printerr (_("Error: %s\n"), error->message); + g_error_free (error); + if (in_signature_types != NULL) + { + GString *s; + s = g_string_new (NULL); + for (n = 0; n < in_signature_types->len; n++) + { + GVariantType *type = in_signature_types->pdata[n]; + g_string_append_len (s, + g_variant_type_peek_string (type), + g_variant_type_get_string_length (type)); + } + g_printerr ("(According to introspection data, you need to pass `%s')\n", s->str); + g_string_free (s, TRUE); + } + goto out; + } + + s = g_variant_print (result, TRUE); + g_print ("%s\n", s); + g_free (s); + + ret = TRUE; + + out: + if (in_signature_types != NULL) + g_ptr_array_unref (in_signature_types); + if (result != NULL) + g_variant_unref (result); + if (c != NULL) + g_object_unref (c); + if (parameters != NULL) + g_variant_unref (parameters); + g_free (interface_name); + g_free (method_name); + g_option_context_free (o); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* TODO: dump annotations */ + +static void +dump_annotation (const GDBusAnnotationInfo *o, + guint indent, + gboolean ignore_indent) +{ + guint n; + g_print ("%*s@%s(\"%s\")\n", + ignore_indent ? 0 : indent, "", + o->key, + o->value); + for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++) + dump_annotation (o->annotations[n], indent + 2, FALSE); +} + +static void +dump_arg (const GDBusArgInfo *o, + guint indent, + const gchar *direction, + gboolean ignore_indent, + gboolean include_newline) +{ + guint n; + + for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++) + { + dump_annotation (o->annotations[n], indent, ignore_indent); + ignore_indent = FALSE; + } + + g_print ("%*s%s%s %s%s", + ignore_indent ? 0 : indent, "", + direction, + o->signature, + o->name, + include_newline ? ",\n" : ""); +} + +static guint +count_args (GDBusArgInfo **args) +{ + guint n; + n = 0; + if (args == NULL) + goto out; + while (args[n] != NULL) + n++; + out: + return n; +} + +static void +dump_method (const GDBusMethodInfo *o, + guint indent) +{ + guint n; + guint m; + guint name_len; + guint total_num_args; + + for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++) + dump_annotation (o->annotations[n], indent, FALSE); + + g_print ("%*s%s(", indent, "", o->name); + name_len = strlen (o->name); + total_num_args = count_args (o->in_args) + count_args (o->out_args); + for (n = 0, m = 0; o->in_args != NULL && o->in_args[n] != NULL; n++, m++) + { + gboolean ignore_indent = (m == 0); + gboolean include_newline = (m != total_num_args - 1); + + dump_arg (o->in_args[n], + indent + name_len + 1, + "in ", + ignore_indent, + include_newline); + } + for (n = 0; o->out_args != NULL && o->out_args[n] != NULL; n++, m++) + { + gboolean ignore_indent = (m == 0); + gboolean include_newline = (m != total_num_args - 1); + dump_arg (o->out_args[n], + indent + name_len + 1, + "out ", + ignore_indent, + include_newline); + } + g_print (");\n"); +} + +static void +dump_signal (const GDBusSignalInfo *o, + guint indent) +{ + guint n; + guint name_len; + guint total_num_args; + + for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++) + dump_annotation (o->annotations[n], indent, FALSE); + + g_print ("%*s%s(", indent, "", o->name); + name_len = strlen (o->name); + total_num_args = count_args (o->args); + for (n = 0; o->args != NULL && o->args[n] != NULL; n++) + { + gboolean ignore_indent = (n == 0); + gboolean include_newline = (n != total_num_args - 1); + dump_arg (o->args[n], + indent + name_len + 1, + "", + ignore_indent, + include_newline); + } + g_print (");\n"); +} + +static void +dump_property (const GDBusPropertyInfo *o, + guint indent, + GVariant *value) +{ + const gchar *access; + guint n; + + if (o->flags == G_DBUS_PROPERTY_INFO_FLAGS_READABLE) + access = "readonly"; + else if (o->flags == G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE) + access = "writeonly"; + else if (o->flags == (G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)) + access = "readwrite"; + else + g_assert_not_reached (); + + for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++) + dump_annotation (o->annotations[n], indent, FALSE); + + if (value != NULL) + { + gchar *s = g_variant_print (value, FALSE); + g_print ("%*s%s %s %s = %s;\n", indent, "", access, o->signature, o->name, s); + g_free (s); + } + else + { + g_print ("%*s%s %s %s;\n", indent, "", access, o->signature, o->name); + } +} + +static void +dump_interface (GDBusConnection *c, + const gchar *name, + const GDBusInterfaceInfo *o, + guint indent, + const gchar *object_path) +{ + guint n; + GHashTable *properties; + + properties = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_variant_unref); + + /* Try to get properties */ + if (c != NULL && name != NULL && object_path != NULL) + { + GVariant *result; + result = g_dbus_connection_call_sync (c, + name, + object_path, + "org.freedesktop.DBus.Properties", + "GetAll", + g_variant_new ("(s)", o->name), + G_DBUS_CALL_FLAGS_NONE, + 3000, + NULL, + NULL); + if (result != NULL) + { + if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(a{sv})"))) + { + GVariantIter *iter; + GVariant *item; + g_variant_get (result, + "(a{sv})", + &iter); + while ((item = g_variant_iter_next_value (iter))) + { + gchar *key; + GVariant *value; + g_variant_get (item, + "{sv}", + &key, + &value); + + g_hash_table_insert (properties, key, g_variant_ref (value)); + } + } + g_variant_unref (result); + } + else + { + guint n; + for (n = 0; o->properties != NULL && o->properties[n] != NULL; n++) + { + result = g_dbus_connection_call_sync (c, + name, + object_path, + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", o->name, o->properties[n]->name), + G_DBUS_CALL_FLAGS_NONE, + 3000, + NULL, + NULL); + if (result != NULL) + { + GVariant *property_value; + g_variant_get (result, + "(v)", + &property_value); + g_hash_table_insert (properties, + g_strdup (o->properties[n]->name), + g_variant_ref (property_value)); + g_variant_unref (result); + } + } + } + } + + for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++) + dump_annotation (o->annotations[n], indent, FALSE); + + g_print ("%*sinterface %s {\n", indent, "", o->name); + if (o->methods != NULL) + { + g_print ("%*s methods:\n", indent, ""); + for (n = 0; o->methods[n] != NULL; n++) + dump_method (o->methods[n], indent + 4); + } + if (o->signals != NULL) + { + g_print ("%*s signals:\n", indent, ""); + for (n = 0; o->signals[n] != NULL; n++) + dump_signal (o->signals[n], indent + 4); + } + if (o->properties != NULL) + { + g_print ("%*s properties:\n", indent, ""); + for (n = 0; o->properties[n] != NULL; n++) + { + dump_property (o->properties[n], + indent + 4, + g_hash_table_lookup (properties, (o->properties[n])->name)); + } + } + g_print ("%*s};\n", + indent, ""); + + g_hash_table_unref (properties); +} + +static void +dump_node (GDBusConnection *c, + const gchar *name, + const GDBusNodeInfo *o, + guint indent, + const gchar *object_path) +{ + guint n; + const gchar *object_path_to_print; + + object_path_to_print = object_path; + if (o->path != NULL) + object_path_to_print = o->path; + + for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++) + dump_annotation (o->annotations[n], indent, FALSE); + + g_print ("%*snode %s", indent, "", object_path_to_print != NULL ? object_path_to_print : "(not set)"); + if (o->interfaces != NULL || o->nodes != NULL) + { + g_print (" {\n"); + for (n = 0; o->interfaces != NULL && o->interfaces[n] != NULL; n++) + dump_interface (c, name, o->interfaces[n], indent + 2, object_path); + for (n = 0; o->nodes != NULL && o->nodes[n] != NULL; n++) + dump_node (NULL, NULL, o->nodes[n], indent + 2, NULL); + g_print ("%*s};\n", + indent, ""); + } + else + { + g_print ("\n"); + } +} + +static gchar *opt_introspect_dest = NULL; +static gchar *opt_introspect_object_path = NULL; + +static const GOptionEntry introspect_entries[] = +{ + { "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_introspect_dest, N_("Destination name to introspect"), NULL}, + { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_introspect_object_path, N_("Object path to introspect"), NULL}, + { NULL } +}; + +static gboolean +handle_introspect (gint *argc, + gchar **argv[], + gboolean request_completion, + const gchar *completion_cur, + const gchar *completion_prev) +{ + gint ret; + GOptionContext *o; + gchar *s; + GError *error; + GDBusConnection *c; + GVariant *result; + const gchar *xml_data; + GDBusNodeInfo *node; + gboolean complete_names; + gboolean complete_paths; + + ret = FALSE; + c = NULL; + node = NULL; + result = NULL; + + modify_argv0_for_command (argc, argv, "introspect"); + + o = g_option_context_new (NULL); + if (request_completion) + g_option_context_set_ignore_unknown_options (o, TRUE); + g_option_context_set_help_enabled (o, FALSE); + g_option_context_set_summary (o, _("Introspect a remote object.")); + g_option_context_add_main_entries (o, introspect_entries, NULL /* GETTEXT_PACKAGE*/); + g_option_context_add_group (o, connection_get_group ()); + + complete_names = FALSE; + if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--dest") == 0) + { + complete_names = TRUE; + remove_arg ((*argc) - 1, argc, argv); + } + + complete_paths = FALSE; + if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--object-path") == 0) + { + complete_paths = TRUE; + remove_arg ((*argc) - 1, argc, argv); + } + + if (!g_option_context_parse (o, argc, argv, NULL)) + { + if (!request_completion) + { + s = g_option_context_get_help (o, FALSE, NULL); + g_printerr ("%s", s); + g_free (s); + goto out; + } + } + + error = NULL; + c = connection_get_dbus_connection (&error); + if (c == NULL) + { + if (request_completion) + { + if (g_strcmp0 (completion_prev, "--address") == 0) + { + g_print ("unix:\n" + "tcp:\n" + "nonce-tcp:\n"); + } + else + { + g_print ("--system \n--session \n--address \n"); + } + } + else + { + g_printerr (_("Error connecting: %s\n"), error->message); + g_error_free (error); + } + goto out; + } + + if (g_dbus_connection_get_unique_name (c) != NULL) + { + if (complete_names) + { + print_names (c, FALSE); + goto out; + } + /* this only makes sense on message bus connections */ + if (opt_introspect_dest == NULL) + { + if (request_completion) + g_print ("--dest \n"); + else + g_printerr (_("Error: Destination is not specified\n")); + goto out; + } + if (request_completion && g_strcmp0 ("--dest", completion_prev) == 0) + { + print_names (c, g_str_has_prefix (opt_introspect_dest, ":")); + goto out; + } + } + if (complete_paths) + { + print_paths (c, opt_introspect_dest, "/"); + goto out; + } + if (opt_introspect_object_path == NULL) + { + if (request_completion) + g_print ("--object-path \n"); + else + g_printerr (_("Error: Object path is not specified\n")); + goto out; + } + if (request_completion && g_strcmp0 ("--object-path", completion_prev) == 0) + { + gchar *p; + s = g_strdup (opt_introspect_object_path); + p = strrchr (s, '/'); + if (p != NULL) + { + if (p == s) + p++; + *p = '\0'; + } + print_paths (c, opt_introspect_dest, s); + g_free (s); + goto out; + } + if (!request_completion && !g_variant_is_object_path (opt_introspect_object_path)) + { + g_printerr (_("Error: %s is not a valid object path\n"), opt_introspect_object_path); + goto out; + } + + /* All done with completion now */ + if (request_completion) + goto out; + + result = g_dbus_connection_call_sync (c, + opt_introspect_dest, + opt_introspect_object_path, + "org.freedesktop.DBus.Introspectable", + "Introspect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 3000, /* 3 sec */ + NULL, + &error); + if (result == NULL) + { + g_printerr (_("Error: %s\n"), error->message); + g_error_free (error); + goto out; + } + if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)"))) + { + g_printerr (_("Error: Result is type `%s', expected `(s)'\n"), + g_variant_get_type_string (result)); + goto out; + } + g_variant_get (result, "(&s)", &xml_data); + + error = NULL; + node = g_dbus_node_info_new_for_xml (xml_data, &error); + if (node == NULL) + { + g_printerr (_("Error parsing introspection XML: %s\n"), error->message); + g_error_free (error); + goto out; + } + + dump_node (c, opt_introspect_dest, node, 0, opt_introspect_object_path); + + ret = TRUE; + + out: + if (node != NULL) + g_dbus_node_info_unref (node); + if (result != NULL) + g_variant_unref (result); + if (c != NULL) + g_object_unref (c); + g_option_context_free (o); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar *opt_monitor_dest = NULL; +static gchar *opt_monitor_object_path = NULL; + +static guint monitor_filter_id = 0; + +static void +monitor_signal_cb (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + gchar *s; + s = g_variant_print (parameters, TRUE); + g_print ("%s: %s.%s %s\n", + object_path, + interface_name, + signal_name, + s); + g_free (s); +} + +static void +monitor_on_name_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + g_print ("The name %s is owned by %s\n", name, name_owner); + g_assert (monitor_filter_id == 0); + monitor_filter_id = g_dbus_connection_signal_subscribe (connection, + name_owner, + NULL, /* any interface */ + NULL, /* any member */ + opt_monitor_object_path, + NULL, /* arg0 */ + monitor_signal_cb, + NULL, /* user_data */ + NULL); /* user_data destroy notify */ +} + +static void +monitor_on_name_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_print ("The name %s does not have an owner\n", name); + + if (monitor_filter_id != 0) + { + g_dbus_connection_signal_unsubscribe (connection, monitor_filter_id); + monitor_filter_id = 0; + } +} + +static const GOptionEntry monitor_entries[] = +{ + { "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_monitor_dest, N_("Destination name to monitor"), NULL}, + { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_monitor_object_path, N_("Object path to monitor"), NULL}, + { NULL } +}; + +static gboolean +handle_monitor (gint *argc, + gchar **argv[], + gboolean request_completion, + const gchar *completion_cur, + const gchar *completion_prev) +{ + gint ret; + GOptionContext *o; + gchar *s; + GError *error; + GDBusConnection *c; + GVariant *result; + GDBusNodeInfo *node; + gboolean complete_names; + gboolean complete_paths; + GMainLoop *loop; + + ret = FALSE; + c = NULL; + node = NULL; + result = NULL; + + modify_argv0_for_command (argc, argv, "monitor"); + + o = g_option_context_new (NULL); + if (request_completion) + g_option_context_set_ignore_unknown_options (o, TRUE); + g_option_context_set_help_enabled (o, FALSE); + g_option_context_set_summary (o, _("Monitor a remote object.")); + g_option_context_add_main_entries (o, monitor_entries, NULL /* GETTEXT_PACKAGE*/); + g_option_context_add_group (o, connection_get_group ()); + + complete_names = FALSE; + if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--dest") == 0) + { + complete_names = TRUE; + remove_arg ((*argc) - 1, argc, argv); + } + + complete_paths = FALSE; + if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--object-path") == 0) + { + complete_paths = TRUE; + remove_arg ((*argc) - 1, argc, argv); + } + + if (!g_option_context_parse (o, argc, argv, NULL)) + { + if (!request_completion) + { + s = g_option_context_get_help (o, FALSE, NULL); + g_printerr ("%s", s); + g_free (s); + goto out; + } + } + + error = NULL; + c = connection_get_dbus_connection (&error); + if (c == NULL) + { + if (request_completion) + { + if (g_strcmp0 (completion_prev, "--address") == 0) + { + g_print ("unix:\n" + "tcp:\n" + "nonce-tcp:\n"); + } + else + { + g_print ("--system \n--session \n--address \n"); + } + } + else + { + g_printerr (_("Error connecting: %s\n"), error->message); + g_error_free (error); + } + goto out; + } + + if (g_dbus_connection_get_unique_name (c) != NULL) + { + if (complete_names) + { + print_names (c, FALSE); + goto out; + } + /* this only makes sense on message bus connections */ + if (opt_monitor_dest == NULL) + { + if (request_completion) + g_print ("--dest \n"); + else + g_printerr (_("Error: Destination is not specified\n")); + goto out; + } + if (request_completion && g_strcmp0 ("--dest", completion_prev) == 0) + { + print_names (c, g_str_has_prefix (opt_monitor_dest, ":")); + goto out; + } + } + if (complete_paths) + { + print_paths (c, opt_monitor_dest, "/"); + goto out; + } + if (opt_monitor_object_path == NULL) + { + if (request_completion) + { + g_print ("--object-path \n"); + goto out; + } + /* it's fine to not have an object path */ + } + if (request_completion && g_strcmp0 ("--object-path", completion_prev) == 0) + { + gchar *p; + s = g_strdup (opt_monitor_object_path); + p = strrchr (s, '/'); + if (p != NULL) + { + if (p == s) + p++; + *p = '\0'; + } + print_paths (c, opt_monitor_dest, s); + g_free (s); + goto out; + } + if (!request_completion && (opt_monitor_object_path != NULL && !g_variant_is_object_path (opt_monitor_object_path))) + { + g_printerr (_("Error: %s is not a valid object path\n"), opt_monitor_object_path); + goto out; + } + + /* All done with completion now */ + if (request_completion) + goto out; + + if (opt_monitor_object_path != NULL) + g_print ("Monitoring signals on object %s owned by %s\n", opt_monitor_object_path, opt_monitor_dest); + else + g_print ("Monitoring signals from all objects owned by %s\n", opt_monitor_dest); + + loop = g_main_loop_new (NULL, FALSE); + g_bus_watch_name_on_connection (c, + opt_monitor_dest, + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + monitor_on_name_appeared, + monitor_on_name_vanished, + NULL, + NULL); + + g_main_loop_run (loop); + g_main_loop_unref (loop); + + ret = TRUE; + + out: + if (node != NULL) + g_dbus_node_info_unref (node); + if (result != NULL) + g_variant_unref (result); + if (c != NULL) + g_object_unref (c); + g_option_context_free (o); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +pick_word_at (const gchar *s, + gint cursor, + gint *out_word_begins_at) +{ + gint begin; + gint end; + + if (s[0] == '\0') + { + if (out_word_begins_at != NULL) + *out_word_begins_at = -1; + return NULL; + } + + if (g_ascii_isspace (s[cursor]) && ((cursor > 0 && g_ascii_isspace(s[cursor-1])) || cursor == 0)) + { + if (out_word_begins_at != NULL) + *out_word_begins_at = cursor; + return g_strdup (""); + } + + while (!g_ascii_isspace (s[cursor - 1]) && cursor > 0) + cursor--; + begin = cursor; + + end = begin; + while (!g_ascii_isspace (s[end]) && s[end] != '\0') + end++; + + if (out_word_begins_at != NULL) + *out_word_begins_at = begin; + + return g_strndup (s + begin, end - begin); +} + +gint +main (gint argc, gchar *argv[]) +{ + gint ret; + const gchar *command; + gboolean request_completion; + gchar *completion_cur; + gchar *completion_prev; + + ret = 1; + completion_cur = NULL; + completion_prev = NULL; + + g_type_init (); + + if (argc < 2) + { + usage (&argc, &argv, FALSE); + goto out; + } + + request_completion = FALSE; + + //completion_debug ("---- argc=%d --------------------------------------------------------", argc); + + again: + command = argv[1]; + if (g_strcmp0 (command, "help") == 0) + { + if (request_completion) + { + /* do nothing */ + } + else + { + usage (&argc, &argv, TRUE); + ret = 0; + } + goto out; + } + else if (g_strcmp0 (command, "call") == 0) + { + if (handle_call (&argc, + &argv, + request_completion, + completion_cur, + completion_prev)) + ret = 0; + goto out; + } + else if (g_strcmp0 (command, "introspect") == 0) + { + if (handle_introspect (&argc, + &argv, + request_completion, + completion_cur, + completion_prev)) + ret = 0; + goto out; + } + else if (g_strcmp0 (command, "monitor") == 0) + { + if (handle_monitor (&argc, + &argv, + request_completion, + completion_cur, + completion_prev)) + ret = 0; + goto out; + } + else if (g_strcmp0 (command, "complete") == 0 && argc == 4 && !request_completion) + { + const gchar *completion_line; + gchar **completion_argv; + gint completion_argc; + gint completion_point; + gchar *endp; + gint cur_begin; + + request_completion = TRUE; + + completion_line = argv[2]; + completion_point = strtol (argv[3], &endp, 10); + if (endp == argv[3] || *endp != '\0') + goto out; + +#if 0 + completion_debug ("completion_point=%d", completion_point); + completion_debug ("----"); + completion_debug (" 0123456789012345678901234567890123456789012345678901234567890123456789"); + completion_debug ("`%s'", completion_line); + completion_debug (" %*s^", + completion_point, ""); + completion_debug ("----"); +#endif + + if (!g_shell_parse_argv (completion_line, + &completion_argc, + &completion_argv, + NULL)) + { + /* it's very possible the command line can't be parsed (for + * example, missing quotes etc) - in that case, we just + * don't autocomplete at all + */ + goto out; + } + + /* compute cur and prev */ + completion_prev = NULL; + completion_cur = pick_word_at (completion_line, completion_point, &cur_begin); + if (cur_begin > 0) + { + gint prev_end; + for (prev_end = cur_begin - 1; prev_end >= 0; prev_end--) + { + if (!g_ascii_isspace (completion_line[prev_end])) + { + completion_prev = pick_word_at (completion_line, prev_end, NULL); + break; + } + } + } +#if 0 + completion_debug (" cur=`%s'", completion_cur); + completion_debug ("prev=`%s'", completion_prev); +#endif + + argc = completion_argc; + argv = completion_argv; + + ret = 0; + + goto again; + } + else + { + if (request_completion) + { + g_print ("help \ncall \nintrospect \nmonitor \n"); + ret = 0; + goto out; + } + else + { + g_printerr ("Unknown command `%s'\n", command); + usage (&argc, &argv, FALSE); + goto out; + } + } + + out: + g_free (completion_cur); + g_free (completion_prev); + return ret; +} diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c new file mode 100644 index 000000000..5e3b87c44 --- /dev/null +++ b/gio/gdbusaddress.c @@ -0,0 +1,1023 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include +#include + +#include "gdbusutils.h" +#include "gdbusaddress.h" +#include "gdbuserror.h" +#include "gioenumtypes.h" +#include "gdbusprivate.h" + +#ifdef G_OS_UNIX +#include +#endif + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusaddress + * @title: D-Bus Addresses + * @short_description: D-Bus connection endpoints + * @include: gio/gio.h + * + * Routines for working with D-Bus addresses. A D-Bus address is a string + * like "unix:tmpdir=/tmp/my-app-name". The exact format of addresses + * is explained in detail in the D-Bus specification. + */ + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_is_address: + * @string: A string. + * + * Checks if @string is a D-Bus address. + * + * This doesn't check if @string is actually supported by #GDBusServer + * or #GDBusConnection - use g_dbus_is_supported_address() to do more + * checks. + * + * Returns: %TRUE if @string is a valid D-Bus address, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_is_address (const gchar *string) +{ + guint n; + gchar **a; + gboolean ret; + + ret = FALSE; + + g_return_val_if_fail (string != NULL, FALSE); + + a = g_strsplit (string, ";", 0); + for (n = 0; a[n] != NULL; n++) + { + if (!_g_dbus_address_parse_entry (a[n], + NULL, + NULL, + NULL)) + goto out; + } + + ret = TRUE; + + out: + g_strfreev (a); + return ret; +} + +static gboolean +is_valid_unix (const gchar *address_entry, + GHashTable *key_value_pairs, + GError **error) +{ + gboolean ret; + GList *keys; + GList *l; + const gchar *path; + const gchar *tmpdir; + const gchar *abstract; + + ret = FALSE; + keys = NULL; + path = NULL; + tmpdir = NULL; + abstract = NULL; + + keys = g_hash_table_get_keys (key_value_pairs); + for (l = keys; l != NULL; l = l->next) + { + const gchar *key = l->data; + if (g_strcmp0 (key, "path") == 0) + path = g_hash_table_lookup (key_value_pairs, key); + else if (g_strcmp0 (key, "tmpdir") == 0) + tmpdir = g_hash_table_lookup (key_value_pairs, key); + else if (g_strcmp0 (key, "abstract") == 0) + abstract = g_hash_table_lookup (key_value_pairs, key); + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Unsupported key `%s' in address entry `%s'"), + key, + address_entry); + goto out; + } + } + + if (path != NULL) + { + if (tmpdir != NULL || abstract != NULL) + goto meaningless; + /* TODO: validate path */ + } + else if (tmpdir != NULL) + { + if (path != NULL || abstract != NULL) + goto meaningless; + /* TODO: validate tmpdir */ + } + else if (abstract != NULL) + { + if (path != NULL || tmpdir != NULL) + goto meaningless; + /* TODO: validate abstract */ + } + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Address `%s' is invalid (need exactly one of path, tmpdir or abstract keys"), + address_entry); + goto out; + } + + + ret= TRUE; + goto out; + + meaningless: + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Meaningless key/value pair combination in address entry `%s'"), + address_entry); + + out: + g_list_free (keys); + + return ret; +} + +static gboolean +is_valid_nonce_tcp (const gchar *address_entry, + GHashTable *key_value_pairs, + GError **error) +{ + gboolean ret; + GList *keys; + GList *l; + const gchar *host; + const gchar *port; + const gchar *family; + const gchar *nonce_file; + gint port_num; + gchar *endp; + + ret = FALSE; + keys = NULL; + host = NULL; + port = NULL; + family = NULL; + nonce_file = NULL; + + keys = g_hash_table_get_keys (key_value_pairs); + for (l = keys; l != NULL; l = l->next) + { + const gchar *key = l->data; + if (g_strcmp0 (key, "host") == 0) + host = g_hash_table_lookup (key_value_pairs, key); + else if (g_strcmp0 (key, "port") == 0) + port = g_hash_table_lookup (key_value_pairs, key); + else if (g_strcmp0 (key, "family") == 0) + family = g_hash_table_lookup (key_value_pairs, key); + else if (g_strcmp0 (key, "noncefile") == 0) + nonce_file = g_hash_table_lookup (key_value_pairs, key); + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Unsupported key `%s' in address entry `%s'"), + key, + address_entry); + goto out; + } + } + + if (port != NULL) + { + port_num = strtol (port, &endp, 10); + if ((*port == '\0' || *endp != '\0') || port_num < 0 || port_num >= 65536) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Error in address `%s' - the port attribute is malformed"), + address_entry); + goto out; + } + } + + if (family != NULL && !(g_strcmp0 (family, "ipv4") == 0 || g_strcmp0 (family, "ipv6") == 0)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Error in address `%s' - the family attribute is malformed"), + address_entry); + goto out; + } + + ret= TRUE; + + out: + g_list_free (keys); + + return ret; +} + +static gboolean +is_valid_tcp (const gchar *address_entry, + GHashTable *key_value_pairs, + GError **error) +{ + gboolean ret; + GList *keys; + GList *l; + const gchar *host; + const gchar *port; + const gchar *family; + gint port_num; + gchar *endp; + + ret = FALSE; + keys = NULL; + host = NULL; + port = NULL; + family = NULL; + + keys = g_hash_table_get_keys (key_value_pairs); + for (l = keys; l != NULL; l = l->next) + { + const gchar *key = l->data; + if (g_strcmp0 (key, "host") == 0) + host = g_hash_table_lookup (key_value_pairs, key); + else if (g_strcmp0 (key, "port") == 0) + port = g_hash_table_lookup (key_value_pairs, key); + else if (g_strcmp0 (key, "family") == 0) + family = g_hash_table_lookup (key_value_pairs, key); + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Unsupported key `%s' in address entry `%s'"), + key, + address_entry); + goto out; + } + } + + if (port != NULL) + { + port_num = strtol (port, &endp, 10); + if ((*port == '\0' || *endp != '\0') || port_num < 0 || port_num >= 65536) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Error in address `%s' - the port attribute is malformed"), + address_entry); + goto out; + } + } + + if (family != NULL && !(g_strcmp0 (family, "ipv4") == 0 || g_strcmp0 (family, "ipv6") == 0)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Error in address `%s' - the family attribute is malformed"), + address_entry); + goto out; + } + + ret= TRUE; + + out: + g_list_free (keys); + + return ret; +} + +/** + * g_dbus_is_supported_address: + * @string: A string. + * @error: Return location for error or %NULL. + * + * Like g_dbus_is_address() but also checks if the library suppors the + * transports in @string and that key/value pairs for each transport + * are valid. + * + * Returns: %TRUE if @string is a valid D-Bus address that is + * supported by this library, %FALSE if @error is set. + * + * Since: 2.26 + */ +gboolean +g_dbus_is_supported_address (const gchar *string, + GError **error) +{ + guint n; + gchar **a; + gboolean ret; + + ret = FALSE; + + g_return_val_if_fail (string != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + a = g_strsplit (string, ";", 0); + for (n = 0; a[n] != NULL; n++) + { + gchar *transport_name; + GHashTable *key_value_pairs; + gboolean supported; + + if (!_g_dbus_address_parse_entry (a[n], + &transport_name, + &key_value_pairs, + error)) + goto out; + + supported = FALSE; + if (g_strcmp0 (transport_name, "unix") == 0) + supported = is_valid_unix (a[n], key_value_pairs, error); + else if (g_strcmp0 (transport_name, "tcp") == 0) + supported = is_valid_tcp (a[n], key_value_pairs, error); + else if (g_strcmp0 (transport_name, "nonce-tcp") == 0) + supported = is_valid_nonce_tcp (a[n], key_value_pairs, error); + + g_free (transport_name); + g_hash_table_unref (key_value_pairs); + + if (!supported) + goto out; + } + + ret = TRUE; + + out: + g_strfreev (a); + + g_assert (ret || (!ret && (error == NULL || *error != NULL))); + + return ret; +} + +gboolean +_g_dbus_address_parse_entry (const gchar *address_entry, + gchar **out_transport_name, + GHashTable **out_key_value_pairs, + GError **error) +{ + gboolean ret; + GHashTable *key_value_pairs; + gchar *transport_name; + gchar **kv_pairs; + const gchar *s; + guint n; + + ret = FALSE; + kv_pairs = NULL; + transport_name = NULL; + key_value_pairs = NULL; + + s = strchr (address_entry, ':'); + if (s == NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Address element `%s', does not contain a colon (:)"), + address_entry); + goto out; + } + + transport_name = g_strndup (address_entry, s - address_entry); + key_value_pairs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + kv_pairs = g_strsplit (s + 1, ",", 0); + for (n = 0; kv_pairs != NULL && kv_pairs[n] != NULL; n++) + { + const gchar *kv_pair = kv_pairs[n]; + gchar *key; + gchar *value; + + s = strchr (kv_pair, '='); + if (s == NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Key/Value pair %d, `%s', in address element `%s', does not contain an equal sign"), + n, + kv_pair, + address_entry); + goto out; + } + + /* TODO: actually validate that no illegal characters are present before and after then '=' sign */ + key = g_uri_unescape_segment (kv_pair, s, NULL); + value = g_uri_unescape_segment (s + 1, kv_pair + strlen (kv_pair), NULL); + g_hash_table_insert (key_value_pairs, key, value); + } + + ret = TRUE; + +out: + g_strfreev (kv_pairs); + if (ret) + { + if (out_transport_name != NULL) + *out_transport_name = transport_name; + else + g_free (transport_name); + if (out_key_value_pairs != NULL) + *out_key_value_pairs = key_value_pairs; + else if (key_value_pairs != NULL) + g_hash_table_unref (key_value_pairs); + } + else + { + g_free (transport_name); + if (key_value_pairs != NULL) + g_hash_table_unref (key_value_pairs); + } + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* TODO: Declare an extension point called GDBusTransport (or similar) + * and move code below to extensions implementing said extension + * point. That way we can implement a D-Bus transport over X11 without + * making libgio link to libX11... + */ +static GIOStream * +g_dbus_address_connect (const gchar *address_entry, + const gchar *transport_name, + GHashTable *key_value_pairs, + GCancellable *cancellable, + GError **error) +{ + GIOStream *ret; + GSocketConnectable *connectable; + const gchar *nonce_file; + + connectable = NULL; + ret = NULL; + nonce_file = NULL; + + if (FALSE) + { + } +#ifdef G_OS_UNIX + else if (g_strcmp0 (transport_name, "unix") == 0) + { + const gchar *path; + const gchar *abstract; + path = g_hash_table_lookup (key_value_pairs, "path"); + abstract = g_hash_table_lookup (key_value_pairs, "abstract"); + if ((path == NULL && abstract == NULL) || (path != NULL && abstract != NULL)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Error in address `%s' - the unix transport requires exactly one of the " + "keys `path' or `abstract' to be set"), + address_entry); + } + else if (path != NULL) + { + connectable = G_SOCKET_CONNECTABLE (g_unix_socket_address_new (path)); + } + else if (abstract != NULL) + { + connectable = G_SOCKET_CONNECTABLE (g_unix_socket_address_new_with_type (abstract, + -1, + G_UNIX_SOCKET_ADDRESS_ABSTRACT)); + } + else + { + g_assert_not_reached (); + } + } +#endif + else if (g_strcmp0 (transport_name, "tcp") == 0 || g_strcmp0 (transport_name, "nonce-tcp") == 0) + { + const gchar *s; + const gchar *host; + guint port; + gchar *endp; + gboolean is_nonce; + + is_nonce = (g_strcmp0 (transport_name, "nonce-tcp") == 0); + + host = g_hash_table_lookup (key_value_pairs, "host"); + if (host == NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Error in address `%s' - the host attribute is missing or malformed"), + address_entry); + goto out; + } + + s = g_hash_table_lookup (key_value_pairs, "port"); + if (s == NULL) + s = "0"; + port = strtol (s, &endp, 10); + if ((*s == '\0' || *endp != '\0') || port < 0 || port >= 65536) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Error in address `%s' - the port attribute is missing or malformed"), + address_entry); + goto out; + } + + + if (is_nonce) + { + nonce_file = g_hash_table_lookup (key_value_pairs, "noncefile"); + if (nonce_file == NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Error in address `%s' - the noncefile attribute is missing or malformed"), + address_entry); + goto out; + } + } + + /* TODO: deal with family */ + connectable = g_network_address_new (host, port); + } + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Unknown or unsupported transport `%s' for address `%s'"), + transport_name, + address_entry); + } + + if (connectable != NULL) + { + GSocketClient *client; + GSocketConnection *connection; + + g_assert (ret == NULL); + client = g_socket_client_new (); + connection = g_socket_client_connect (client, + connectable, + cancellable, + error); + g_object_unref (connectable); + g_object_unref (client); + if (connection == NULL) + goto out; + + ret = G_IO_STREAM (connection); + + if (nonce_file != NULL) + { + gchar *nonce_contents; + gsize nonce_length; + + /* TODO: too dangerous to read the entire file? (think denial-of-service etc.) */ + if (!g_file_get_contents (nonce_file, + &nonce_contents, + &nonce_length, + error)) + { + g_prefix_error (error, _("Error reading nonce file `%s':"), nonce_file); + g_object_unref (ret); + ret = NULL; + goto out; + } + + if (nonce_length != 16) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("The nonce-file `%s' was %" G_GSIZE_FORMAT " bytes. Expected 16 bytes."), + nonce_file, + nonce_length); + g_free (nonce_contents); + g_object_unref (ret); + ret = NULL; + goto out; + } + + if (!g_output_stream_write_all (g_io_stream_get_output_stream (ret), + nonce_contents, + nonce_length, + NULL, + cancellable, + error)) + { + g_prefix_error (error, _("Error write contents of nonce file `%s' to stream:"), nonce_file); + g_object_unref (ret); + ret = NULL; + g_free (nonce_contents); + goto out; + } + g_free (nonce_contents); + } + } + + out: + + return ret; +} + +static GIOStream * +g_dbus_address_try_connect_one (const gchar *address_entry, + gchar **out_guid, + GCancellable *cancellable, + GError **error) +{ + GIOStream *ret; + GHashTable *key_value_pairs; + gchar *transport_name; + const gchar *guid; + + ret = NULL; + transport_name = NULL; + key_value_pairs = NULL; + + if (!_g_dbus_address_parse_entry (address_entry, + &transport_name, + &key_value_pairs, + error)) + goto out; + + ret = g_dbus_address_connect (address_entry, + transport_name, + key_value_pairs, + cancellable, + error); + if (ret == NULL) + goto out; + + /* TODO: validate that guid is of correct format */ + guid = g_hash_table_lookup (key_value_pairs, "guid"); + if (guid != NULL && out_guid != NULL) + *out_guid = g_strdup (guid); + +out: + g_free (transport_name); + if (key_value_pairs != NULL) + g_hash_table_unref (key_value_pairs); + return ret; +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct { + gchar *address; + GIOStream *stream; + gchar *guid; +} GetStreamData; + +static void +get_stream_data_free (GetStreamData *data) +{ + g_free (data->address); + if (data->stream != NULL) + g_object_unref (data->stream); + g_free (data->guid); + g_free (data); +} + +static void +get_stream_thread_func (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GetStreamData *data; + GError *error; + + data = g_simple_async_result_get_op_res_gpointer (res); + + error = NULL; + data->stream = g_dbus_address_get_stream_sync (data->address, + &data->guid, + cancellable, + &error); + if (data->stream == NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } +} + +/** + * g_dbus_address_get_stream: + * @address: A valid D-Bus address. + * @cancellable: A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied. + * @user_data: Data to pass to @callback. + * + * Asynchronously connects to an endpoint specified by @address and + * sets up the connection so it is in a state to run the client-side + * of the D-Bus authentication conversation. + * + * When the operation is finished, @callback will be invoked. You can + * then call g_dbus_address_get_stream_finish() to get the result of + * the operation. + * + * This is an asynchronous failable function. See + * g_dbus_address_get_stream_sync() for the synchronous version. + * + * Since: 2.26 + */ +void +g_dbus_address_get_stream (const gchar *address, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + GetStreamData *data; + + g_return_if_fail (address != NULL); + + res = g_simple_async_result_new (NULL, + callback, + user_data, + g_dbus_address_get_stream); + data = g_new0 (GetStreamData, 1); + data->address = g_strdup (address); + g_simple_async_result_set_op_res_gpointer (res, + data, + (GDestroyNotify) get_stream_data_free); + g_simple_async_result_run_in_thread (res, + get_stream_thread_func, + G_PRIORITY_DEFAULT, + cancellable); + g_object_unref (res); +} + +/** + * g_dbus_address_get_stream_finish: + * @res: A #GAsyncResult obtained from the GAsyncReadyCallback passed to g_dbus_address_get_stream(). + * @out_guid: %NULL or return location to store the GUID extracted from @address, if any. + * @error: Return location for error or %NULL. + * + * Finishes an operation started with g_dbus_address_get_stream(). + * + * Returns: A #GIOStream or %NULL if @error is set. + * + * Since: 2.26 + */ +GIOStream * +g_dbus_address_get_stream_finish (GAsyncResult *res, + gchar **out_guid, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + GetStreamData *data; + GIOStream *ret; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_dbus_address_get_stream); + + ret = NULL; + + data = g_simple_async_result_get_op_res_gpointer (simple); + if (g_simple_async_result_propagate_error (simple, error)) + goto out; + + ret = g_object_ref (data->stream); + if (out_guid != NULL) + *out_guid = g_strdup (data->guid); + + out: + return ret; +} + +/** + * g_dbus_address_get_stream_sync: + * @address: A valid D-Bus address. + * @out_guid: %NULL or return location to store the GUID extracted from @address, if any. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously connects to an endpoint specified by @address and + * sets up the connection so it is in a state to run the client-side + * of the D-Bus authentication conversation. + * + * This is a synchronous failable function. See + * g_dbus_address_get_stream() for the asynchronous version. + * + * Returns: A #GIOStream or %NULL if @error is set. + * + * Since: 2.26 + */ +GIOStream * +g_dbus_address_get_stream_sync (const gchar *address, + gchar **out_guid, + GCancellable *cancellable, + GError **error) +{ + GIOStream *ret; + gchar **addr_array; + guint n; + GError *last_error; + + g_return_val_if_fail (address != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = NULL; + last_error = NULL; + + addr_array = g_strsplit (address, ";", 0); + last_error = NULL; + for (n = 0; addr_array != NULL && addr_array[n] != NULL; n++) + { + const gchar *addr = addr_array[n]; + GError *this_error; + this_error = NULL; + ret = g_dbus_address_try_connect_one (addr, + out_guid, + cancellable, + &this_error); + if (ret != NULL) + { + goto out; + } + else + { + g_assert (this_error != NULL); + if (last_error != NULL) + g_error_free (last_error); + last_error = this_error; + } + } + + out: + if (ret != NULL) + { + if (last_error != NULL) + g_error_free (last_error); + } + else + { + g_assert (last_error != NULL); + g_propagate_error (error, last_error); + } + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* TODO: implement for UNIX, Win32 and OS X */ +static gchar * +get_session_address_platform_specific (void) +{ + return NULL; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_address_get_for_bus_sync: + * @bus_type: A #GBusType. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously looks up the D-Bus address for the well-known message + * bus instance specified by @bus_type. This may involve using various + * platform specific mechanisms. + * + * Returns: A valid D-Bus address string for @bus_type or %NULL if @error is set. + * + * Since: 2.26 + */ +gchar * +g_dbus_address_get_for_bus_sync (GBusType bus_type, + GCancellable *cancellable, + GError **error) +{ + gchar *ret; + const gchar *starter_bus; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = NULL; + + switch (bus_type) + { + case G_BUS_TYPE_SYSTEM: + ret = g_strdup (g_getenv ("DBUS_SYSTEM_BUS_ADDRESS")); + if (ret == NULL) + { + ret = g_strdup ("unix:path=/var/run/dbus/system_bus_socket"); + } + break; + + case G_BUS_TYPE_SESSION: + ret = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS")); + if (ret == NULL) + { + ret = get_session_address_platform_specific (); + if (ret == NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Cannot determine session bus address (TODO: run dbus-launch to find out)")); + } + } + break; + + case G_BUS_TYPE_STARTER: + starter_bus = g_getenv ("DBUS_STARTER_BUS_TYPE"); + if (g_strcmp0 (starter_bus, "session") == 0) + { + ret = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, cancellable, error); + goto out; + } + else if (g_strcmp0 (starter_bus, "system") == 0) + { + ret = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SYSTEM, cancellable, error); + goto out; + } + else + { + if (starter_bus != NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Cannot determine bus address from DBUS_STARTER_BUS_TYPE environment variable" + " - unknown value `%s'"), + starter_bus); + } + else + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Cannot determine bus address because the DBUS_STARTER_BUS_TYPE environment " + "variable is not set")); + } + } + break; + + default: + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Unknown bus type %d"), + bus_type); + break; + } + + out: + return ret; +} + +#define __G_DBUS_ADDRESS_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusaddress.h b/gio/gdbusaddress.h new file mode 100644 index 000000000..389bd83dc --- /dev/null +++ b/gio/gdbusaddress.h @@ -0,0 +1,54 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_ADDRESS_H__ +#define __G_DBUS_ADDRESS_H__ + +#include + +G_BEGIN_DECLS + +gboolean g_dbus_is_address (const gchar *string); +gboolean g_dbus_is_supported_address (const gchar *string, + GError **error); + +void g_dbus_address_get_stream (const gchar *address, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GIOStream *g_dbus_address_get_stream_finish (GAsyncResult *res, + gchar **out_guid, + GError **error); + +GIOStream *g_dbus_address_get_stream_sync (const gchar *address, + gchar **out_guid, + GCancellable *cancellable, + GError **error); + +gchar *g_dbus_address_get_for_bus_sync (GBusType bus_type, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* __G_DBUS_ADDRESS_H__ */ diff --git a/gio/gdbusauth.c b/gio/gdbusauth.c new file mode 100644 index 000000000..a94fcfa14 --- /dev/null +++ b/gio/gdbusauth.c @@ -0,0 +1,1352 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include "gdbusauth.h" +#include "gdbusauthmechanismanon.h" +#include "gdbusauthmechanismexternal.h" +#include "gdbusauthmechanismsha1.h" + +#include "gdbusauthobserver.h" + +#include "gdbuserror.h" +#include "gdbusutils.h" +#include "gioenumtypes.h" +#include "gcredentials.h" +#include "gdbusprivate.h" + +#ifdef G_OS_UNIX +#include +#include "gunixcredentialsmessage.h" +#include +#include +#endif + +#include "glibintl.h" +#include "gioalias.h" + +#define DEBUG_ENABLED 1 + +static void +debug_print (const gchar *message, ...) +{ +#if DEBUG_ENABLED + if (G_UNLIKELY (_g_dbus_debug_authentication ())) + { + gchar *s; + GString *str; + va_list var_args; + guint n; + + va_start (var_args, message); + s = g_strdup_vprintf (message, var_args); + va_end (var_args); + + str = g_string_new (NULL); + for (n = 0; s[n] != '\0'; n++) + { + if (G_UNLIKELY (s[n] == '\r')) + g_string_append (str, "\\r"); + else if (G_UNLIKELY (s[n] == '\n')) + g_string_append (str, "\\n"); + else + g_string_append_c (str, s[n]); + } + g_print ("GDBus-debug:Auth: %s\n", str->str); + g_string_free (str, TRUE); + g_free (s); + } +#endif +} + +typedef struct +{ + const gchar *name; + gint priority; + GType gtype; +} Mechanism; + +static void mechanism_free (Mechanism *m); + +struct _GDBusAuthPrivate +{ + GIOStream *stream; + + /* A list of available Mechanism, sorted according to priority */ + GList *available_mechanisms; +}; + +enum +{ + PROP_0, + PROP_STREAM +}; + +G_DEFINE_TYPE (GDBusAuth, _g_dbus_auth, G_TYPE_OBJECT); + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +_g_dbus_auth_finalize (GObject *object) +{ + GDBusAuth *auth = G_DBUS_AUTH (object); + + if (auth->priv->stream != NULL) + g_object_unref (auth->priv->stream); + g_list_foreach (auth->priv->available_mechanisms, (GFunc) mechanism_free, NULL); + g_list_free (auth->priv->available_mechanisms); + + if (G_OBJECT_CLASS (_g_dbus_auth_parent_class)->finalize != NULL) + G_OBJECT_CLASS (_g_dbus_auth_parent_class)->finalize (object); +} + +static void +_g_dbus_auth_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GDBusAuth *auth = G_DBUS_AUTH (object); + + switch (prop_id) + { + case PROP_STREAM: + g_value_set_object (value, auth->priv->stream); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_g_dbus_auth_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GDBusAuth *auth = G_DBUS_AUTH (object); + + switch (prop_id) + { + case PROP_STREAM: + auth->priv->stream = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_g_dbus_auth_class_init (GDBusAuthClass *klass) +{ + GObjectClass *gobject_class; + + g_type_class_add_private (klass, sizeof (GDBusAuthPrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->get_property = _g_dbus_auth_get_property; + gobject_class->set_property = _g_dbus_auth_set_property; + gobject_class->finalize = _g_dbus_auth_finalize; + + g_object_class_install_property (gobject_class, + PROP_STREAM, + g_param_spec_object ("stream", + P_("IO Stream"), + P_("The underlying GIOStream used for I/O"), + G_TYPE_IO_STREAM, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); +} + +static void +mechanism_free (Mechanism *m) +{ + g_free (m); +} + +static void +add_mechanism (GDBusAuth *auth, + GType mechanism_type) +{ + Mechanism *m; + + m = g_new0 (Mechanism, 1); + m->name = _g_dbus_auth_mechanism_get_name (mechanism_type); + m->priority = _g_dbus_auth_mechanism_get_priority (mechanism_type); + m->gtype = mechanism_type; + + auth->priv->available_mechanisms = g_list_prepend (auth->priv->available_mechanisms, m); +} + +static gint +mech_compare_func (Mechanism *a, Mechanism *b) +{ + gint ret; + /* ensure deterministic order */ + ret = b->priority - a->priority; + if (ret == 0) + ret = g_strcmp0 (b->name, a->name); + return ret; +} + +static void +_g_dbus_auth_init (GDBusAuth *auth) +{ + auth->priv = G_TYPE_INSTANCE_GET_PRIVATE (auth, G_TYPE_DBUS_AUTH, GDBusAuthPrivate); + + /* TODO: trawl extension points */ + add_mechanism (auth, G_TYPE_DBUS_AUTH_MECHANISM_ANON); + add_mechanism (auth, G_TYPE_DBUS_AUTH_MECHANISM_SHA1); + add_mechanism (auth, G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL); + + auth->priv->available_mechanisms = g_list_sort (auth->priv->available_mechanisms, + (GCompareFunc) mech_compare_func); +} + +static GType +find_mech_by_name (GDBusAuth *auth, + const gchar *name) +{ + GType ret; + GList *l; + + ret = (GType) 0; + + for (l = auth->priv->available_mechanisms; l != NULL; l = l->next) + { + Mechanism *m = l->data; + if (g_strcmp0 (name, m->name) == 0) + { + ret = m->gtype; + goto out; + } + } + + out: + return ret; +} + +GDBusAuth * +_g_dbus_auth_new (GIOStream *stream) +{ + return g_object_new (G_TYPE_DBUS_AUTH, + "stream", stream, + NULL); +} + +/* ---------------------------------------------------------------------------------------------------- */ +/* like g_data_input_stream_read_line() but sets error if there's no content to read */ +static gchar * +_my_g_data_input_stream_read_line (GDataInputStream *dis, + gsize *out_line_length, + GCancellable *cancellable, + GError **error) +{ + gchar *ret; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = g_data_input_stream_read_line (dis, + out_line_length, + cancellable, + error); + if (ret == NULL && error != NULL && *error == NULL) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Unexpected lack of content trying to read a line")); + } + + return ret; +} + +/* This function is to avoid situations like this + * + * BEGIN\r\nl\0\0\1... + * + * e.g. where we read into the first D-Bus message while waiting for + * the final line from the client (TODO: file bug against gio for + * this) + */ +static gchar * +_my_g_input_stream_read_line_safe (GInputStream *i, + gsize *out_line_length, + GCancellable *cancellable, + GError **error) +{ + GString *str; + gchar c; + gssize num_read; + gboolean last_was_cr; + + str = g_string_new (NULL); + + last_was_cr = FALSE; + while (TRUE) + { + num_read = g_input_stream_read (i, + &c, + 1, + cancellable, + error); + if (num_read == -1) + goto fail; + if (num_read == 0) + { + if (error != NULL && *error == NULL) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Unexpected lack of content trying to (safely) read a line")); + } + goto fail; + } + + g_string_append_c (str, (gint) c); + if (last_was_cr) + { + if (c == 0x0a) + { + g_assert (str->len >= 2); + g_string_set_size (str, str->len - 2); + goto out; + } + } + last_was_cr = (c == 0x0d); + } + + out: + if (out_line_length != NULL) + *out_line_length = str->len; + return g_string_free (str, FALSE); + + fail: + g_assert (error == NULL || *error != NULL); + g_string_free (str, TRUE); + return NULL; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +append_nibble (GString *s, gint val) +{ + g_string_append_c (s, val >= 10 ? ('a' + val - 10) : ('0' + val)); +} + +static gchar * +hexdecode (const gchar *str, + gsize *out_len, + GError **error) +{ + gchar *ret; + GString *s; + guint n; + + ret = NULL; + s = g_string_new (NULL); + + for (n = 0; str[n] != '\0'; n += 2) + { + gint upper_nibble; + gint lower_nibble; + guint value; + + upper_nibble = g_ascii_xdigit_value (str[n]); + lower_nibble = g_ascii_xdigit_value (str[n + 1]); + if (upper_nibble == -1 || lower_nibble == -1) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Error hexdecoding string `%s' around position %d", + str, n); + goto out; + } + value = (upper_nibble<<4) | lower_nibble; + g_string_append_c (s, value); + } + + ret = g_string_free (s, FALSE); + s = NULL; + + out: + if (s != NULL) + g_string_free (s, TRUE); + return ret; +} + +/* TODO: take len */ +static gchar * +hexencode (const gchar *str) +{ + guint n; + GString *s; + + s = g_string_new (NULL); + for (n = 0; str[n] != '\0'; n++) + { + gint val; + gint upper_nibble; + gint lower_nibble; + + val = ((const guchar *) str)[n]; + upper_nibble = val >> 4; + lower_nibble = val & 0x0f; + + append_nibble (s, upper_nibble); + append_nibble (s, lower_nibble); + } + + return g_string_free (s, FALSE); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusAuthMechanism * +client_choose_mech_and_send_initial_response (GDBusAuth *auth, + GCredentials *credentials_that_were_sent, + const gchar* const *supported_auth_mechs, + GPtrArray *attempted_auth_mechs, + GDataOutputStream *dos, + GCancellable *cancellable, + GError **error) +{ + GDBusAuthMechanism *mech; + GType auth_mech_to_use_gtype; + guint n; + guint m; + gchar *initial_response; + gsize initial_response_len; + gchar *encoded; + gchar *s; + + again: + mech = NULL; + + debug_print ("CLIENT: Trying to choose mechanism"); + + /* find an authentication mechanism to try, if any */ + auth_mech_to_use_gtype = (GType) 0; + for (n = 0; supported_auth_mechs[n] != NULL; n++) + { + gboolean attempted_already; + attempted_already = FALSE; + for (m = 0; m < attempted_auth_mechs->len; m++) + { + if (g_strcmp0 (supported_auth_mechs[n], attempted_auth_mechs->pdata[m]) == 0) + { + attempted_already = TRUE; + break; + } + } + if (!attempted_already) + { + auth_mech_to_use_gtype = find_mech_by_name (auth, supported_auth_mechs[n]); + if (auth_mech_to_use_gtype != (GType) 0) + break; + } + } + + if (auth_mech_to_use_gtype == (GType) 0) + { + guint n; + gchar *available; + GString *tried_str; + + debug_print ("CLIENT: Exhausted all available mechanisms"); + + available = g_strjoinv (", ", (gchar **) supported_auth_mechs); + + tried_str = g_string_new (NULL); + for (n = 0; n < attempted_auth_mechs->len; n++) + { + if (n > 0) + g_string_append (tried_str, ", "); + g_string_append (tried_str, attempted_auth_mechs->pdata[n]); + } + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Exhausted all available authentication mechanisms (tried: %s) (available: %s)"), + tried_str->str, + available); + g_string_free (tried_str, TRUE); + g_free (available); + goto out; + } + + /* OK, decided on a mechanism - let's do this thing */ + mech = g_object_new (auth_mech_to_use_gtype, + "stream", auth->priv->stream, + "credentials", credentials_that_were_sent, + NULL); + debug_print ("CLIENT: Trying mechanism `%s'", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype)); + g_ptr_array_add (attempted_auth_mechs, (gpointer) _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype)); + + /* the auth mechanism may not be supported + * (for example, EXTERNAL only works if credentials were exchanged) + */ + if (!_g_dbus_auth_mechanism_is_supported (mech)) + { + debug_print ("CLIENT: Mechanism `%s' says it is not supported", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype)); + g_object_unref (mech); + mech = NULL; + goto again; + } + + initial_response_len = -1; + initial_response = _g_dbus_auth_mechanism_client_initiate (mech, + &initial_response_len); +#if 0 + g_printerr ("using auth mechanism with name `%s' of type `%s' with initial response `%s'\n", + _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype), + g_type_name (G_TYPE_FROM_INSTANCE (mech)), + initial_response); +#endif + if (initial_response != NULL) + { + //g_printerr ("initial_response = `%s'\n", initial_response); + encoded = hexencode (initial_response); + s = g_strdup_printf ("AUTH %s %s\r\n", + _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype), + encoded); + g_free (initial_response); + g_free (encoded); + } + else + { + s = g_strdup_printf ("AUTH %s\r\n", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype)); + } + debug_print ("CLIENT: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + { + g_object_unref (mech); + mech = NULL; + g_free (s); + goto out; + } + g_free (s); + + out: + return mech; +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef enum +{ + CLIENT_STATE_WAITING_FOR_DATA, + CLIENT_STATE_WAITING_FOR_OK, + CLIENT_STATE_WAITING_FOR_REJECT, + CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD +} ClientState; + +gchar * +_g_dbus_auth_run_client (GDBusAuth *auth, + GDBusCapabilityFlags offered_capabilities, + GDBusCapabilityFlags *out_negotiated_capabilities, + GCancellable *cancellable, + GError **error) +{ + gchar *s; + GDataInputStream *dis; + GDataOutputStream *dos; + GCredentials *credentials; + gchar *ret_guid; + gchar *line; + gsize line_length; + gchar **supported_auth_mechs; + GPtrArray *attempted_auth_mechs; + GDBusAuthMechanism *mech; + ClientState state; + GDBusCapabilityFlags negotiated_capabilities; + + debug_print ("CLIENT: initiating"); + + ret_guid = NULL; + supported_auth_mechs = NULL; + attempted_auth_mechs = g_ptr_array_new (); + mech = NULL; + negotiated_capabilities = 0; + credentials = NULL; + + dis = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream (auth->priv->stream))); + dos = G_DATA_OUTPUT_STREAM (g_data_output_stream_new (g_io_stream_get_output_stream (auth->priv->stream))); + + g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF); + +#ifdef G_OS_UNIX + if (G_IS_UNIX_CONNECTION (auth->priv->stream) && g_unix_credentials_message_is_supported ()) + { + credentials = g_credentials_new (); + if (!g_unix_connection_send_credentials (G_UNIX_CONNECTION (auth->priv->stream), + cancellable, + error)) + goto out; + } + else + { + if (!g_data_output_stream_put_byte (dos, '\0', cancellable, error)) + goto out; + } +#else + if (!g_data_output_stream_put_byte (dos, '\0', cancellable, error)) + goto out; +#endif + + if (credentials != NULL) + { + if (G_UNLIKELY (_g_dbus_debug_authentication ())) + { + s = g_credentials_to_string (credentials); + debug_print ("CLIENT: sent credentials `%s'", s); + g_free (s); + } + } + else + { + debug_print ("CLIENT: didn't send any credentials"); + } + + /* TODO: to reduce roundtrips, try to pick an auth mechanism to start with */ + + /* Get list of supported authentication mechanisms */ + s = "AUTH\r\n"; + debug_print ("CLIENT: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + goto out; + state = CLIENT_STATE_WAITING_FOR_REJECT; + + while (TRUE) + { + switch (state) + { + case CLIENT_STATE_WAITING_FOR_REJECT: + debug_print ("CLIENT: WaitingForReject"); + line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); + if (line == NULL) + goto out; + debug_print ("CLIENT: WaitingForReject, read '%s'", line); + foobar: + if (!g_str_has_prefix (line, "REJECTED ")) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "In WaitingForReject: Expected `REJECTED am1 am2 ... amN', got `%s'", + line); + g_free (line); + goto out; + } + if (supported_auth_mechs == NULL) + { + supported_auth_mechs = g_strsplit (line + sizeof ("REJECTED ") - 1, " ", 0); +#if 0 + for (n = 0; supported_auth_mechs != NULL && supported_auth_mechs[n] != NULL; n++) + g_printerr ("supported_auth_mechs[%d] = `%s'\n", n, supported_auth_mechs[n]); +#endif + } + g_free (line); + mech = client_choose_mech_and_send_initial_response (auth, + credentials, + (const gchar* const *) supported_auth_mechs, + attempted_auth_mechs, + dos, + cancellable, + error); + if (mech == NULL) + goto out; + if (_g_dbus_auth_mechanism_client_get_state (mech) == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA) + state = CLIENT_STATE_WAITING_FOR_DATA; + else + state = CLIENT_STATE_WAITING_FOR_OK; + break; + + case CLIENT_STATE_WAITING_FOR_OK: + debug_print ("CLIENT: WaitingForOK"); + line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); + if (line == NULL) + goto out; + debug_print ("CLIENT: WaitingForOK, read `%s'", line); + if (g_str_has_prefix (line, "OK ")) + { + if (!g_dbus_is_guid (line + 3)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Invalid OK response `%s'", + line); + g_free (line); + goto out; + } + ret_guid = g_strdup (line + 3); + g_free (line); + + if (offered_capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING) + { + s = "NEGOTIATE_UNIX_FD\r\n"; + debug_print ("CLIENT: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + goto out; + state = CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD; + } + else + { + s = "BEGIN\r\n"; + debug_print ("CLIENT: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + goto out; + /* and we're done! */ + goto out; + } + } + else if (g_str_has_prefix (line, "REJECTED ")) + { + goto foobar; + } + else + { + /* TODO: handle other valid responses */ + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "In WaitingForOk: unexpected response `%s'", + line); + g_free (line); + goto out; + } + break; + + case CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD: + debug_print ("CLIENT: WaitingForAgreeUnixFD"); + line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); + if (line == NULL) + goto out; + debug_print ("CLIENT: WaitingForAgreeUnixFD, read=`%s'", line); + if (g_strcmp0 (line, "AGREE_UNIX_FD") == 0) + { + negotiated_capabilities |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING; + s = "BEGIN\r\n"; + debug_print ("CLIENT: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + goto out; + /* and we're done! */ + goto out; + } + else if (g_str_has_prefix (line, "ERROR") && (line[5] == 0 || g_ascii_isspace (line[5]))) + { + //g_strstrip (line + 5); g_debug ("bah, no unix_fd: `%s'", line + 5); + g_free (line); + s = "BEGIN\r\n"; + debug_print ("CLIENT: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + goto out; + /* and we're done! */ + goto out; + } + else + { + /* TODO: handle other valid responses */ + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "In WaitingForAgreeUnixFd: unexpected response `%s'", + line); + g_free (line); + goto out; + } + break; + + case CLIENT_STATE_WAITING_FOR_DATA: + debug_print ("CLIENT: WaitingForData"); + line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); + if (line == NULL) + goto out; + debug_print ("CLIENT: WaitingForData, read=`%s'", line); + if (g_str_has_prefix (line, "DATA ")) + { + gchar *encoded; + gchar *decoded_data; + gsize decoded_data_len; + + encoded = g_strdup (line + 5); + g_free (line); + g_strstrip (encoded); + decoded_data = hexdecode (encoded, &decoded_data_len, error); + g_free (encoded); + if (decoded_data == NULL) + { + g_prefix_error (error, "DATA response is malformed: "); + /* invalid encoding, disconnect! */ + goto out; + } + _g_dbus_auth_mechanism_client_data_receive (mech, decoded_data, decoded_data_len); + g_free (decoded_data); + + if (_g_dbus_auth_mechanism_client_get_state (mech) == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND) + { + gchar *data; + gsize data_len; + gchar *encoded_data; + data = _g_dbus_auth_mechanism_client_data_send (mech, &data_len); + encoded_data = hexencode (data); + s = g_strdup_printf ("DATA %s\r\n", encoded_data); + g_free (encoded_data); + g_free (data); + debug_print ("CLIENT: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + { + g_free (s); + goto out; + } + g_free (s); + } + state = CLIENT_STATE_WAITING_FOR_OK; + } + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "In WaitingForData: unexpected response `%s'", + line); + g_free (line); + goto out; + } + break; + + default: + g_assert_not_reached (); + break; + } + + }; /* main authentication client loop */ + + out: + if (mech != NULL) + g_object_unref (mech); + g_ptr_array_unref (attempted_auth_mechs); + g_strfreev (supported_auth_mechs); + g_object_ref (dis); + g_object_ref (dos); + + /* ensure return value is NULL if error is set */ + if (error != NULL && *error != NULL) + { + g_free (ret_guid); + ret_guid = NULL; + } + + if (ret_guid != NULL) + { + if (out_negotiated_capabilities != NULL) + *out_negotiated_capabilities = negotiated_capabilities; + } + + if (credentials != NULL) + g_object_unref (credentials); + + debug_print ("CLIENT: Done, authenticated=%d", ret_guid != NULL); + + return ret_guid; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +get_auth_mechanisms (GDBusAuth *auth, + gboolean allow_anonymous, + const gchar *prefix, + const gchar *suffix, + const gchar *separator) +{ + GList *l; + GString *str; + gboolean need_sep; + + str = g_string_new (prefix); + need_sep = FALSE; + for (l = auth->priv->available_mechanisms; l != NULL; l = l->next) + { + Mechanism *m = l->data; + + if (!allow_anonymous && g_strcmp0 (m->name, "ANONYMOUS") == 0) + continue; + + if (need_sep) + g_string_append (str, separator); + g_string_append (str, m->name); + need_sep = TRUE; + } + + g_string_append (str, suffix); + return g_string_free (str, FALSE); +} + + +typedef enum +{ + SERVER_STATE_WAITING_FOR_AUTH, + SERVER_STATE_WAITING_FOR_DATA, + SERVER_STATE_WAITING_FOR_BEGIN +} ServerState; + +gboolean +_g_dbus_auth_run_server (GDBusAuth *auth, + GDBusAuthObserver *observer, + const gchar *guid, + gboolean allow_anonymous, + GDBusCapabilityFlags offered_capabilities, + GDBusCapabilityFlags *out_negotiated_capabilities, + GCredentials **out_received_credentials, + GCancellable *cancellable, + GError **error) +{ + gboolean ret; + ServerState state; + GDataInputStream *dis; + GDataOutputStream *dos; + GError *local_error; + guchar byte; + gchar *line; + gsize line_length; + GDBusAuthMechanism *mech; + gchar *s; + GDBusCapabilityFlags negotiated_capabilities; + GCredentials *credentials; + + debug_print ("SERVER: initiating"); + + ret = FALSE; + dis = NULL; + dos = NULL; + mech = NULL; + negotiated_capabilities = 0; + credentials = NULL; + + if (!g_dbus_is_guid (guid)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "The given guid `%s' is not valid", + guid); + goto out; + } + + dis = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream (auth->priv->stream))); + dos = G_DATA_OUTPUT_STREAM (g_data_output_stream_new (g_io_stream_get_output_stream (auth->priv->stream))); + + g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF); + + /* first read the NUL-byte (TODO: read credentials if using a unix domain socket) */ +#ifdef G_OS_UNIX + if (G_IS_UNIX_CONNECTION (auth->priv->stream) && g_unix_credentials_message_is_supported ()) + { + local_error = NULL; + credentials = g_unix_connection_receive_credentials (G_UNIX_CONNECTION (auth->priv->stream), + cancellable, + &local_error); + if (credentials == NULL) + { + g_propagate_error (error, local_error); + goto out; + } + } + else + { + local_error = NULL; + byte = g_data_input_stream_read_byte (dis, cancellable, &local_error); + if (local_error != NULL) + { + g_propagate_error (error, local_error); + goto out; + } + } +#else + local_error = NULL; + byte = g_data_input_stream_read_byte (dis, cancellable, &local_error); + if (local_error != NULL) + { + g_propagate_error (error, local_error); + goto out; + } +#endif + if (credentials != NULL) + { + if (G_UNLIKELY (_g_dbus_debug_authentication ())) + { + s = g_credentials_to_string (credentials); + debug_print ("SERVER: received credentials `%s'", s); + g_free (s); + } + } + else + { + debug_print ("SERVER: didn't receive any credentials"); + } + + state = SERVER_STATE_WAITING_FOR_AUTH; + while (TRUE) + { + switch (state) + { + case SERVER_STATE_WAITING_FOR_AUTH: + debug_print ("SERVER: WaitingForAuth"); + line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); + debug_print ("SERVER: WaitingForAuth, read `%s'", line); + if (line == NULL) + goto out; + if (g_strcmp0 (line, "AUTH") == 0) + { + s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " "); + debug_print ("SERVER: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + { + g_free (s); + goto out; + } + g_free (s); + g_free (line); + } + else if (g_str_has_prefix (line, "AUTH ")) + { + gchar **tokens; + const gchar *encoded; + const gchar *mech_name; + GType auth_mech_to_use_gtype; + + tokens = g_strsplit (line, " ", 0); + g_free (line); + + switch (g_strv_length (tokens)) + { + case 2: + /* no initial response */ + mech_name = tokens[1]; + encoded = NULL; + break; + + case 3: + /* initial response */ + mech_name = tokens[1]; + encoded = tokens[2]; + break; + + default: + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Unexpected line `%s' while in WaitingForAuth state", + line); + g_strfreev (tokens); + goto out; + } + + /* TODO: record that the client has attempted to use this mechanism */ + //g_debug ("client is trying `%s'", mech_name); + + auth_mech_to_use_gtype = find_mech_by_name (auth, mech_name); + if ((auth_mech_to_use_gtype == (GType) 0) || + (!allow_anonymous && g_strcmp0 (mech_name, "ANONYMOUS") == 0)) + { + /* We don't support this auth mechanism */ + g_strfreev (tokens); + s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " "); + debug_print ("SERVER: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + { + g_free (s); + goto out; + } + g_free (s); + + /* stay in WAITING FOR AUTH */ + state = SERVER_STATE_WAITING_FOR_AUTH; + } + else + { + gchar *initial_response; + gsize initial_response_len; + + mech = g_object_new (auth_mech_to_use_gtype, + "stream", auth->priv->stream, + "credentials", credentials, + NULL); + + initial_response = NULL; + initial_response_len = 0; + if (encoded != NULL) + { + initial_response = hexdecode (encoded, &initial_response_len, error); + if (initial_response == NULL) + { + g_prefix_error (error, "Initial response is malformed: "); + /* invalid encoding, disconnect! */ + g_strfreev (tokens); + goto out; + } + } + + _g_dbus_auth_mechanism_server_initiate (mech, + initial_response, + initial_response_len); + g_free (initial_response); + g_strfreev (tokens); + + change_state: + switch (_g_dbus_auth_mechanism_server_get_state (mech)) + { + case G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED: + if (observer != NULL && + !g_dbus_auth_observer_authorize_authenticated_peer (observer, + auth->priv->stream, + credentials)) + { + /* disconnect */ + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Cancelled via GDBusAuthObserver::authorize-authenticated-peer")); + goto out; + } + else + { + s = g_strdup_printf ("OK %s\r\n", guid); + debug_print ("SERVER: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + { + g_free (s); + goto out; + } + g_free (s); + state = SERVER_STATE_WAITING_FOR_BEGIN; + } + break; + + case G_DBUS_AUTH_MECHANISM_STATE_REJECTED: + s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " "); + debug_print ("SERVER: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + { + g_free (s); + goto out; + } + g_free (s); + state = SERVER_STATE_WAITING_FOR_AUTH; + break; + + case G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA: + state = SERVER_STATE_WAITING_FOR_DATA; + break; + + case G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND: + { + gchar *data; + gsize data_len; + gchar *encoded_data; + data = _g_dbus_auth_mechanism_server_data_send (mech, &data_len); + encoded_data = hexencode (data); + s = g_strdup_printf ("DATA %s\r\n", encoded_data); + g_free (encoded_data); + g_free (data); + debug_print ("SERVER: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + { + g_free (s); + goto out; + } + g_free (s); + } + goto change_state; + break; + + default: + /* TODO */ + g_assert_not_reached (); + break; + } + } + } + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Unexpected line `%s' while in WaitingForAuth state", + line); + g_free (line); + goto out; + } + break; + + case SERVER_STATE_WAITING_FOR_DATA: + debug_print ("SERVER: WaitingForData"); + line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); + debug_print ("SERVER: WaitingForData, read `%s'", line); + if (line == NULL) + goto out; + if (g_str_has_prefix (line, "DATA ")) + { + gchar *encoded; + gchar *decoded_data; + gsize decoded_data_len; + + encoded = g_strdup (line + 5); + g_free (line); + g_strstrip (encoded); + decoded_data = hexdecode (encoded, &decoded_data_len, error); + g_free (encoded); + if (decoded_data == NULL) + { + g_prefix_error (error, "DATA response is malformed: "); + /* invalid encoding, disconnect! */ + goto out; + } + _g_dbus_auth_mechanism_server_data_receive (mech, decoded_data, decoded_data_len); + g_free (decoded_data); + /* oh man, this goto-crap is so ugly.. really need to rewrite the state machine */ + goto change_state; + } + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Unexpected line `%s' while in WaitingForData state", + line); + g_free (line); + } + goto out; + + case SERVER_STATE_WAITING_FOR_BEGIN: + debug_print ("SERVER: WaitingForBegin"); + /* Use extremely slow (but reliable) line reader - this basically + * does a recvfrom() system call per character + * + * (the problem with using GDataInputStream's read_line is that because of + * buffering it might start reading into the first D-Bus message that + * appears after "BEGIN\r\n"....) + */ + line = _my_g_input_stream_read_line_safe (g_io_stream_get_input_stream (auth->priv->stream), + &line_length, + cancellable, + error); + debug_print ("SERVER: WaitingForBegin, read `%s'", line); + if (line == NULL) + goto out; + if (g_strcmp0 (line, "BEGIN") == 0) + { + /* YAY, done! */ + ret = TRUE; + g_free (line); + goto out; + } + else if (g_strcmp0 (line, "NEGOTIATE_UNIX_FD") == 0) + { + if (offered_capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING) + { + negotiated_capabilities |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING; + s = "AGREE_UNIX_FD\r\n"; + debug_print ("SERVER: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + goto out; + } + else + { + s = "ERROR \"fd passing not offered\"\r\n"; + debug_print ("SERVER: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + goto out; + } + } + else + { + g_debug ("Unexpected line `%s' while in WaitingForBegin state", line); + g_free (line); + s = "ERROR \"Unknown Command\"\r\n"; + debug_print ("SERVER: writing `%s'", s); + if (!g_data_output_stream_put_string (dos, s, cancellable, error)) + goto out; + } + break; + + default: + g_assert_not_reached (); + break; + } + } + + + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Not implemented (server)"); + + out: + if (mech != NULL) + g_object_unref (mech); + if (dis != NULL) + g_object_ref (dis); + if (dis != NULL) + g_object_ref (dos); + + /* ensure return value is FALSE if error is set */ + if (error != NULL && *error != NULL) + { + ret = FALSE; + } + + if (ret) + { + if (out_negotiated_capabilities != NULL) + *out_negotiated_capabilities = negotiated_capabilities; + if (out_received_credentials != NULL) + *out_received_credentials = credentials != NULL ? g_object_ref (credentials) : NULL; + } + + if (credentials != NULL) + g_object_unref (credentials); + + debug_print ("SERVER: Done, authenticated=%d", ret); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#define __G_DBUS_AUTH_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusauth.h b/gio/gdbusauth.h new file mode 100644 index 000000000..039565932 --- /dev/null +++ b/gio/gdbusauth.h @@ -0,0 +1,86 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (GIO_COMPILATION) +#error "gdbusauth.h is a private header file." +#endif + +#ifndef __G_DBUS_AUTH_H__ +#define __G_DBUS_AUTH_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_DBUS_AUTH (_g_dbus_auth_get_type ()) +#define G_DBUS_AUTH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH, GDBusAuth)) +#define G_DBUS_AUTH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH, GDBusAuthClass)) +#define G_DBUS_AUTH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH, GDBusAuthClass)) +#define G_IS_DBUS_AUTH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH)) +#define G_IS_DBUS_AUTH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH)) + +typedef struct _GDBusAuth GDBusAuth; +typedef struct _GDBusAuthClass GDBusAuthClass; +typedef struct _GDBusAuthPrivate GDBusAuthPrivate; + +struct _GDBusAuthClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +struct _GDBusAuth +{ + GObject parent_instance; + GDBusAuthPrivate *priv; +}; + +GType _g_dbus_auth_get_type (void) G_GNUC_CONST; +GDBusAuth *_g_dbus_auth_new (GIOStream *stream); + +/* TODO: need a way to set allowed authentication mechanisms */ + +/* TODO: need a way to convey credentials etc. */ + +/* TODO: need a way to convey negotiated features (e.g. returning flags from e.g. GDBusConnectionFeatures) */ + +/* TODO: need to expose encode()/decode() from the AuthMechanism (and whether it is needed at all) */ + +gboolean _g_dbus_auth_run_server (GDBusAuth *auth, + GDBusAuthObserver *observer, + const gchar *guid, + gboolean allow_anonymous, + GDBusCapabilityFlags offered_capabilities, + GDBusCapabilityFlags *out_negotiated_capabilities, + GCredentials **out_received_credentials, + GCancellable *cancellable, + GError **error); + +gchar *_g_dbus_auth_run_client (GDBusAuth *auth, + GDBusCapabilityFlags offered_capabilities, + GDBusCapabilityFlags *out_negotiated_capabilities, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* __G_DBUS_AUTH_H__ */ diff --git a/gio/gdbusauthmechanism.c b/gio/gdbusauthmechanism.c new file mode 100644 index 000000000..ede990372 --- /dev/null +++ b/gio/gdbusauthmechanism.c @@ -0,0 +1,345 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include "gdbusauthmechanism.h" +#include "gcredentials.h" +#include "gdbuserror.h" +#include "gioenumtypes.h" +#include "giostream.h" + +#include "glibintl.h" +#include "gioalias.h" + +/* ---------------------------------------------------------------------------------------------------- */ + +struct _GDBusAuthMechanismPrivate +{ + GIOStream *stream; + GCredentials *credentials; +}; + +enum +{ + PROP_0, + PROP_STREAM, + PROP_CREDENTIALS +}; + +G_DEFINE_ABSTRACT_TYPE (GDBusAuthMechanism, _g_dbus_auth_mechanism, G_TYPE_OBJECT); + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +_g_dbus_auth_mechanism_finalize (GObject *object) +{ + GDBusAuthMechanism *mechanism = G_DBUS_AUTH_MECHANISM (object); + + if (mechanism->priv->stream != NULL) + g_object_unref (mechanism->priv->stream); + if (mechanism->priv->credentials != NULL) + g_object_unref (mechanism->priv->credentials); + + G_OBJECT_CLASS (_g_dbus_auth_mechanism_parent_class)->finalize (object); +} + +static void +_g_dbus_auth_mechanism_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GDBusAuthMechanism *mechanism = G_DBUS_AUTH_MECHANISM (object); + + switch (prop_id) + { + case PROP_STREAM: + g_value_set_object (value, mechanism->priv->stream); + break; + + case PROP_CREDENTIALS: + g_value_set_object (value, mechanism->priv->credentials); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_g_dbus_auth_mechanism_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GDBusAuthMechanism *mechanism = G_DBUS_AUTH_MECHANISM (object); + + switch (prop_id) + { + case PROP_STREAM: + mechanism->priv->stream = g_value_dup_object (value); + break; + + case PROP_CREDENTIALS: + mechanism->priv->credentials = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_g_dbus_auth_mechanism_class_init (GDBusAuthMechanismClass *klass) +{ + GObjectClass *gobject_class; + + g_type_class_add_private (klass, sizeof (GDBusAuthMechanismPrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->get_property = _g_dbus_auth_mechanism_get_property; + gobject_class->set_property = _g_dbus_auth_mechanism_set_property; + gobject_class->finalize = _g_dbus_auth_mechanism_finalize; + + g_object_class_install_property (gobject_class, + PROP_STREAM, + g_param_spec_object ("stream", + P_("IO Stream"), + P_("The underlying GIOStream used for I/O"), + G_TYPE_IO_STREAM, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusAuthMechanism:credentials: + * + * If authenticating as a server, this property contains the + * received credentials, if any. + * + * If authenticating as a client, the property contains the + * credentials that were sent, if any. + */ + g_object_class_install_property (gobject_class, + PROP_CREDENTIALS, + g_param_spec_object ("credentials", + P_("Credentials"), + P_("The credentials of the remote peer"), + G_TYPE_CREDENTIALS, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); +} + +static void +_g_dbus_auth_mechanism_init (GDBusAuthMechanism *mechanism) +{ + /* not used for now */ + mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism, + G_TYPE_DBUS_AUTH_MECHANISM, + GDBusAuthMechanismPrivate); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +GIOStream * +_g_dbus_auth_mechanism_get_stream (GDBusAuthMechanism *mechanism) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL); + return mechanism->priv->stream; +} + +GCredentials * +_g_dbus_auth_mechanism_get_credentials (GDBusAuthMechanism *mechanism) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL); + return mechanism->priv->credentials; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +const gchar * +_g_dbus_auth_mechanism_get_name (GType mechanism_type) +{ + const gchar *name; + GDBusAuthMechanismClass *klass; + + g_return_val_if_fail (g_type_is_a (mechanism_type, G_TYPE_DBUS_AUTH_MECHANISM), NULL); + + klass = g_type_class_ref (mechanism_type); + g_assert (klass != NULL); + name = klass->get_name (); + //g_type_class_unref (klass); + + return name; +} + +gint +_g_dbus_auth_mechanism_get_priority (GType mechanism_type) +{ + gint priority; + GDBusAuthMechanismClass *klass; + + g_return_val_if_fail (g_type_is_a (mechanism_type, G_TYPE_DBUS_AUTH_MECHANISM), 0); + + klass = g_type_class_ref (mechanism_type); + g_assert (klass != NULL); + priority = klass->get_priority (); + //g_type_class_unref (klass); + + return priority; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +gboolean +_g_dbus_auth_mechanism_is_supported (GDBusAuthMechanism *mechanism) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), FALSE); + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->is_supported (mechanism); +} + +gchar * +_g_dbus_auth_mechanism_encode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL); + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->encode_data (mechanism, data, data_len, out_data_len); +} + + +gchar * +_g_dbus_auth_mechanism_decode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL); + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->decode_data (mechanism, data, data_len, out_data_len); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +GDBusAuthMechanismState +_g_dbus_auth_mechanism_server_get_state (GDBusAuthMechanism *mechanism) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID); + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_get_state (mechanism); +} + +void +_g_dbus_auth_mechanism_server_initiate (GDBusAuthMechanism *mechanism, + const gchar *initial_response, + gsize initial_response_len) +{ + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism)); + G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_initiate (mechanism, initial_response, initial_response_len); +} + +void +_g_dbus_auth_mechanism_server_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len) +{ + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism)); + G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_data_receive (mechanism, data, data_len); +} + +gchar * +_g_dbus_auth_mechanism_server_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL); + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_data_send (mechanism, out_data_len); +} + +gchar * +_g_dbus_auth_mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL); + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_get_reject_reason (mechanism); +} + +void +_g_dbus_auth_mechanism_server_shutdown (GDBusAuthMechanism *mechanism) +{ + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism)); + G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_shutdown (mechanism); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +GDBusAuthMechanismState +_g_dbus_auth_mechanism_client_get_state (GDBusAuthMechanism *mechanism) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID); + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_get_state (mechanism); +} + +gchar * +_g_dbus_auth_mechanism_client_initiate (GDBusAuthMechanism *mechanism, + gsize *out_initial_response_len) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL); + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_initiate (mechanism, + out_initial_response_len); +} + +void +_g_dbus_auth_mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len) +{ + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism)); + G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_data_receive (mechanism, data, data_len); +} + +gchar * +_g_dbus_auth_mechanism_client_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL); + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_data_send (mechanism, out_data_len); +} + +void +_g_dbus_auth_mechanism_client_shutdown (GDBusAuthMechanism *mechanism) +{ + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism)); + G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_shutdown (mechanism); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#define __G_DBUS_AUTH_MECHANISM_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusauthmechanism.h b/gio/gdbusauthmechanism.h new file mode 100644 index 000000000..fd46d71ca --- /dev/null +++ b/gio/gdbusauthmechanism.h @@ -0,0 +1,174 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (GIO_COMPILATION) +#error "gdbusauthmechanism.h is a private header file." +#endif + +#ifndef __G_DBUS_AUTH_MECHANISM_H__ +#define __G_DBUS_AUTH_MECHANISM_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_DBUS_AUTH_MECHANISM (_g_dbus_auth_mechanism_get_type ()) +#define G_DBUS_AUTH_MECHANISM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM, GDBusAuthMechanism)) +#define G_DBUS_AUTH_MECHANISM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM, GDBusAuthMechanismClass)) +#define G_DBUS_AUTH_MECHANISM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM, GDBusAuthMechanismClass)) +#define G_IS_DBUS_AUTH_MECHANISM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM)) +#define G_IS_DBUS_AUTH_MECHANISM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM)) + +typedef struct _GDBusAuthMechanism GDBusAuthMechanism; +typedef struct _GDBusAuthMechanismClass GDBusAuthMechanismClass; +typedef struct _GDBusAuthMechanismPrivate GDBusAuthMechanismPrivate; + +typedef enum { + G_DBUS_AUTH_MECHANISM_STATE_INVALID, + G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA, + G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, + G_DBUS_AUTH_MECHANISM_STATE_REJECTED, + G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED, +} GDBusAuthMechanismState; + +struct _GDBusAuthMechanismClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + + /* VTable */ + + /* TODO: server_initiate and client_initiate probably needs to have a + * GCredentials parameter... + */ + + gint (*get_priority) (void); + const gchar *(*get_name) (void); + + /* functions shared by server/client */ + gboolean (*is_supported) (GDBusAuthMechanism *mechanism); + gchar *(*encode_data) (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len); + gchar *(*decode_data) (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len); + + /* functions for server-side authentication */ + GDBusAuthMechanismState (*server_get_state) (GDBusAuthMechanism *mechanism); + void (*server_initiate) (GDBusAuthMechanism *mechanism, + const gchar *initial_response, + gsize initial_response_len); + void (*server_data_receive) (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len); + gchar *(*server_data_send) (GDBusAuthMechanism *mechanism, + gsize *out_data_len); + gchar *(*server_get_reject_reason) (GDBusAuthMechanism *mechanism); + void (*server_shutdown) (GDBusAuthMechanism *mechanism); + + /* functions for client-side authentication */ + GDBusAuthMechanismState (*client_get_state) (GDBusAuthMechanism *mechanism); + gchar *(*client_initiate) (GDBusAuthMechanism *mechanism, + gsize *out_initial_response_len); + void (*client_data_receive) (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len); + gchar *(*client_data_send) (GDBusAuthMechanism *mechanism, + gsize *out_data_len); + void (*client_shutdown) (GDBusAuthMechanism *mechanism); + + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); + void (*_g_reserved9) (void); + void (*_g_reserved10) (void); + void (*_g_reserved11) (void); + void (*_g_reserved12) (void); + void (*_g_reserved13) (void); + void (*_g_reserved14) (void); + void (*_g_reserved15) (void); + void (*_g_reserved16) (void); +}; + +struct _GDBusAuthMechanism +{ + GObject parent_instance; + GDBusAuthMechanismPrivate *priv; +}; + +GType _g_dbus_auth_mechanism_get_type (void) G_GNUC_CONST; + +gint _g_dbus_auth_mechanism_get_priority (GType mechanism_type); +const gchar *_g_dbus_auth_mechanism_get_name (GType mechanism_type); + +GIOStream *_g_dbus_auth_mechanism_get_stream (GDBusAuthMechanism *mechanism); +GCredentials *_g_dbus_auth_mechanism_get_credentials (GDBusAuthMechanism *mechanism); + +gboolean _g_dbus_auth_mechanism_is_supported (GDBusAuthMechanism *mechanism); +gchar *_g_dbus_auth_mechanism_encode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len); +gchar *_g_dbus_auth_mechanism_decode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len); + +GDBusAuthMechanismState _g_dbus_auth_mechanism_server_get_state (GDBusAuthMechanism *mechanism); +void _g_dbus_auth_mechanism_server_initiate (GDBusAuthMechanism *mechanism, + const gchar *initial_response, + gsize initial_response_len); +void _g_dbus_auth_mechanism_server_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len); +gchar *_g_dbus_auth_mechanism_server_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); +gchar *_g_dbus_auth_mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism); +void _g_dbus_auth_mechanism_server_shutdown (GDBusAuthMechanism *mechanism); + +GDBusAuthMechanismState _g_dbus_auth_mechanism_client_get_state (GDBusAuthMechanism *mechanism); +gchar *_g_dbus_auth_mechanism_client_initiate (GDBusAuthMechanism *mechanism, + gsize *out_initial_response_len); +void _g_dbus_auth_mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len); +gchar *_g_dbus_auth_mechanism_client_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); +void _g_dbus_auth_mechanism_client_shutdown (GDBusAuthMechanism *mechanism); + + +G_END_DECLS + +#endif /* __G_DBUS_AUTH_MECHANISM_H__ */ diff --git a/gio/gdbusauthmechanismanon.c b/gio/gdbusauthmechanismanon.c new file mode 100644 index 000000000..2ab3f3ba1 --- /dev/null +++ b/gio/gdbusauthmechanismanon.c @@ -0,0 +1,331 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include "gdbusauthmechanismanon.h" +#include "gdbuserror.h" +#include "gioenumtypes.h" + +#include "glibintl.h" +#include "gioalias.h" + +struct _GDBusAuthMechanismAnonPrivate +{ + gboolean is_client; + gboolean is_server; + GDBusAuthMechanismState state; +}; + +static gint mechanism_get_priority (void); +static const gchar *mechanism_get_name (void); + +static gboolean mechanism_is_supported (GDBusAuthMechanism *mechanism); +static gchar *mechanism_encode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len); +static gchar *mechanism_decode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len); +static GDBusAuthMechanismState mechanism_server_get_state (GDBusAuthMechanism *mechanism); +static void mechanism_server_initiate (GDBusAuthMechanism *mechanism, + const gchar *initial_response, + gsize initial_response_len); +static void mechanism_server_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len); +static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); +static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism); +static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism); +static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism); +static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism, + gsize *out_initial_response_len); +static void mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len); +static gchar *mechanism_client_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); +static void mechanism_client_shutdown (GDBusAuthMechanism *mechanism); + +/* ---------------------------------------------------------------------------------------------------- */ + +G_DEFINE_TYPE (GDBusAuthMechanismAnon, _g_dbus_auth_mechanism_anon, G_TYPE_DBUS_AUTH_MECHANISM); + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +_g_dbus_auth_mechanism_anon_finalize (GObject *object) +{ + //GDBusAuthMechanismAnon *mechanism = G_DBUS_AUTH_MECHANISM_ANON (object); + + if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_anon_parent_class)->finalize != NULL) + G_OBJECT_CLASS (_g_dbus_auth_mechanism_anon_parent_class)->finalize (object); +} + +static void +_g_dbus_auth_mechanism_anon_class_init (GDBusAuthMechanismAnonClass *klass) +{ + GObjectClass *gobject_class; + GDBusAuthMechanismClass *mechanism_class; + + g_type_class_add_private (klass, sizeof (GDBusAuthMechanismAnonPrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = _g_dbus_auth_mechanism_anon_finalize; + + mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass); + mechanism_class->get_priority = mechanism_get_priority; + mechanism_class->get_name = mechanism_get_name; + mechanism_class->is_supported = mechanism_is_supported; + mechanism_class->encode_data = mechanism_encode_data; + mechanism_class->decode_data = mechanism_decode_data; + mechanism_class->server_get_state = mechanism_server_get_state; + mechanism_class->server_initiate = mechanism_server_initiate; + mechanism_class->server_data_receive = mechanism_server_data_receive; + mechanism_class->server_data_send = mechanism_server_data_send; + mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason; + mechanism_class->server_shutdown = mechanism_server_shutdown; + mechanism_class->client_get_state = mechanism_client_get_state; + mechanism_class->client_initiate = mechanism_client_initiate; + mechanism_class->client_data_receive = mechanism_client_data_receive; + mechanism_class->client_data_send = mechanism_client_data_send; + mechanism_class->client_shutdown = mechanism_client_shutdown; +} + +static void +_g_dbus_auth_mechanism_anon_init (GDBusAuthMechanismAnon *mechanism) +{ + mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism, + G_TYPE_DBUS_AUTH_MECHANISM_ANON, + GDBusAuthMechanismAnonPrivate); +} + +/* ---------------------------------------------------------------------------------------------------- */ + + +static gint +mechanism_get_priority (void) +{ + /* We prefer ANONYMOUS to most other mechanism (such as DBUS_COOKIE_SHA1) but not to EXTERNAL */ + return 50; +} + + +static const gchar * +mechanism_get_name (void) +{ + return "ANONYMOUS"; +} + +static gboolean +mechanism_is_supported (GDBusAuthMechanism *mechanism) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), FALSE); + return TRUE; +} + +static gchar * +mechanism_encode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len) +{ + return NULL; +} + + +static gchar * +mechanism_decode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len) +{ + return NULL; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusAuthMechanismState +mechanism_server_get_state (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID); + g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID); + + return m->priv->state; +} + +static void +mechanism_server_initiate (GDBusAuthMechanism *mechanism, + const gchar *initial_response, + gsize initial_response_len) +{ + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism)); + g_return_if_fail (!m->priv->is_server && !m->priv->is_client); + + if (initial_response != NULL) + g_debug ("ANONYMOUS: initial_response was `%s'", initial_response); + + m->priv->is_server = TRUE; + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED; +} + +static void +mechanism_server_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len) +{ + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism)); + g_return_if_fail (m->priv->is_server && !m->priv->is_client); + g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA); + + /* can never end up here because we are never in the WAITING_FOR_DATA state */ + g_assert_not_reached (); +} + +static gchar * +mechanism_server_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len) +{ + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL); + g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL); + + /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */ + g_assert_not_reached (); + + return NULL; +} + +static gchar * +mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL); + g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL); + + /* can never end up here because we are never in the REJECTED state */ + g_assert_not_reached (); + + return NULL; +} + +static void +mechanism_server_shutdown (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism)); + g_return_if_fail (m->priv->is_server && !m->priv->is_client); + + m->priv->is_server = FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusAuthMechanismState +mechanism_client_get_state (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID); + g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID); + + return m->priv->state; +} + +static gchar * +mechanism_client_initiate (GDBusAuthMechanism *mechanism, + gsize *out_initial_response_len) +{ + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL); + g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL); + + m->priv->is_client = TRUE; + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED; + + *out_initial_response_len = -1; + + /* just return our library name and version */ + return g_strdup ("GDBus 0.1"); +} + +static void +mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len) +{ + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism)); + g_return_if_fail (m->priv->is_client && !m->priv->is_server); + g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA); + + /* can never end up here because we are never in the WAITING_FOR_DATA state */ + g_assert_not_reached (); +} + +static gchar * +mechanism_client_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len) +{ + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL); + g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL); + + /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */ + g_assert_not_reached (); + + return NULL; +} + +static void +mechanism_client_shutdown (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism)); + g_return_if_fail (m->priv->is_client && !m->priv->is_server); + + m->priv->is_client = FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#define __G_DBUS_AUTH_MECHANISM_ANON_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusauthmechanismanon.h b/gio/gdbusauthmechanismanon.h new file mode 100644 index 000000000..b770e20b3 --- /dev/null +++ b/gio/gdbusauthmechanismanon.h @@ -0,0 +1,82 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (GIO_COMPILATION) +#error "gdbusauthmechanismanon.h is a private header file." +#endif + +#ifndef __G_DBUS_AUTH_MECHANISM_ANON_H__ +#define __G_DBUS_AUTH_MECHANISM_ANON_H__ + +#include +#include + +G_BEGIN_DECLS + +#define G_TYPE_DBUS_AUTH_MECHANISM_ANON (_g_dbus_auth_mechanism_anon_get_type ()) +#define G_DBUS_AUTH_MECHANISM_ANON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM_ANON, GDBusAuthMechanismAnon)) +#define G_DBUS_AUTH_MECHANISM_ANON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM_ANON, GDBusAuthMechanismAnonClass)) +#define G_DBUS_AUTH_MECHANISM_ANON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM_ANON, GDBusAuthMechanismAnonClass)) +#define G_IS_DBUS_AUTH_MECHANISM_ANON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM_ANON)) +#define G_IS_DBUS_AUTH_MECHANISM_ANON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM_ANON)) + +typedef struct _GDBusAuthMechanismAnon GDBusAuthMechanismAnon; +typedef struct _GDBusAuthMechanismAnonClass GDBusAuthMechanismAnonClass; +typedef struct _GDBusAuthMechanismAnonPrivate GDBusAuthMechanismAnonPrivate; + +struct _GDBusAuthMechanismAnonClass +{ + /*< private >*/ + GDBusAuthMechanismClass parent_class; + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); + void (*_g_reserved9) (void); + void (*_g_reserved10) (void); + void (*_g_reserved11) (void); + void (*_g_reserved12) (void); + void (*_g_reserved13) (void); + void (*_g_reserved14) (void); + void (*_g_reserved15) (void); + void (*_g_reserved16) (void); +}; + +struct _GDBusAuthMechanismAnon +{ + GDBusAuthMechanism parent_instance; + GDBusAuthMechanismAnonPrivate *priv; +}; + +GType _g_dbus_auth_mechanism_anon_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __G_DBUS_AUTH_MECHANISM_ANON_H__ */ diff --git a/gio/gdbusauthmechanismexternal.c b/gio/gdbusauthmechanismexternal.c new file mode 100644 index 000000000..0ec56b934 --- /dev/null +++ b/gio/gdbusauthmechanismexternal.c @@ -0,0 +1,409 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include + +#include "gdbusauthmechanismexternal.h" +#include "gcredentials.h" +#include "gdbuserror.h" +#include "gioenumtypes.h" + +#ifdef G_OS_UNIX +#include +#include +#endif + +#include "glibintl.h" +#include "gioalias.h" + +struct _GDBusAuthMechanismExternalPrivate +{ + gboolean is_client; + gboolean is_server; + GDBusAuthMechanismState state; +}; + +static gint mechanism_get_priority (void); +static const gchar *mechanism_get_name (void); + +static gboolean mechanism_is_supported (GDBusAuthMechanism *mechanism); +static gchar *mechanism_encode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len); +static gchar *mechanism_decode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len); +static GDBusAuthMechanismState mechanism_server_get_state (GDBusAuthMechanism *mechanism); +static void mechanism_server_initiate (GDBusAuthMechanism *mechanism, + const gchar *initial_response, + gsize initial_response_len); +static void mechanism_server_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len); +static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); +static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism); +static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism); +static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism); +static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism, + gsize *out_initial_response_len); +static void mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len); +static gchar *mechanism_client_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); +static void mechanism_client_shutdown (GDBusAuthMechanism *mechanism); + +/* ---------------------------------------------------------------------------------------------------- */ + +G_DEFINE_TYPE (GDBusAuthMechanismExternal, _g_dbus_auth_mechanism_external, G_TYPE_DBUS_AUTH_MECHANISM); + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +_g_dbus_auth_mechanism_external_finalize (GObject *object) +{ + //GDBusAuthMechanismExternal *mechanism = G_DBUS_AUTH_MECHANISM_EXTERNAL (object); + + if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_external_parent_class)->finalize != NULL) + G_OBJECT_CLASS (_g_dbus_auth_mechanism_external_parent_class)->finalize (object); +} + +static void +_g_dbus_auth_mechanism_external_class_init (GDBusAuthMechanismExternalClass *klass) +{ + GObjectClass *gobject_class; + GDBusAuthMechanismClass *mechanism_class; + + g_type_class_add_private (klass, sizeof (GDBusAuthMechanismExternalPrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = _g_dbus_auth_mechanism_external_finalize; + + mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass); + mechanism_class->get_name = mechanism_get_name; + mechanism_class->get_priority = mechanism_get_priority; + mechanism_class->is_supported = mechanism_is_supported; + mechanism_class->encode_data = mechanism_encode_data; + mechanism_class->decode_data = mechanism_decode_data; + mechanism_class->server_get_state = mechanism_server_get_state; + mechanism_class->server_initiate = mechanism_server_initiate; + mechanism_class->server_data_receive = mechanism_server_data_receive; + mechanism_class->server_data_send = mechanism_server_data_send; + mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason; + mechanism_class->server_shutdown = mechanism_server_shutdown; + mechanism_class->client_get_state = mechanism_client_get_state; + mechanism_class->client_initiate = mechanism_client_initiate; + mechanism_class->client_data_receive = mechanism_client_data_receive; + mechanism_class->client_data_send = mechanism_client_data_send; + mechanism_class->client_shutdown = mechanism_client_shutdown; +} + +static void +_g_dbus_auth_mechanism_external_init (GDBusAuthMechanismExternal *mechanism) +{ + mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism, + G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL, + GDBusAuthMechanismExternalPrivate); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +mechanism_is_supported (GDBusAuthMechanism *mechanism) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), FALSE); + /* This mechanism is only available if credentials has been exchanged */ + if (_g_dbus_auth_mechanism_get_credentials (mechanism) != NULL) + return TRUE; + else + return FALSE; +} + +static gint +mechanism_get_priority (void) +{ + /* We prefer EXTERNAL to most other mechanism (DBUS_COOKIE_SHA1 and ANONYMOUS) */ + return 100; +} + +static const gchar * +mechanism_get_name (void) +{ + return "EXTERNAL"; +} + +static gchar * +mechanism_encode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len) +{ + return NULL; +} + + +static gchar * +mechanism_decode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len) +{ + return NULL; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusAuthMechanismState +mechanism_server_get_state (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID); + g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID); + + return m->priv->state; +} + +static gboolean +data_matches_credentials (const gchar *data, + GCredentials *credentials) +{ + gboolean match; + + match = FALSE; + + if (credentials == NULL) + goto out; + + if (data == NULL || strlen (data) == 0) + goto out; + +#if defined(G_OS_UNIX) + { + gint64 alleged_uid; + gchar *endp; + + /* on UNIX, this is the uid as a string in base 10 */ + alleged_uid = g_ascii_strtoll (data, &endp, 10); + if (*endp == '\0') + { + if (g_credentials_get_unix_user (credentials, NULL) == alleged_uid) + { + match = TRUE; + } + } + } +#else + /* TODO: Dont know how to compare credentials on this OS. Please implement. */ +#endif + + out: + return match; +} + +static void +mechanism_server_initiate (GDBusAuthMechanism *mechanism, + const gchar *initial_response, + gsize initial_response_len) +{ + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism)); + g_return_if_fail (!m->priv->is_server && !m->priv->is_client); + + m->priv->is_server = TRUE; + + if (initial_response != NULL) + { + if (data_matches_credentials (initial_response, _g_dbus_auth_mechanism_get_credentials (mechanism))) + { + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED; + } + else + { + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; + } + } + else + { + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA; + } +} + +static void +mechanism_server_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len) +{ + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism)); + g_return_if_fail (m->priv->is_server && !m->priv->is_client); + g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA); + + if (data_matches_credentials (data, _g_dbus_auth_mechanism_get_credentials (mechanism))) + { + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED; + } + else + { + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; + } +} + +static gchar * +mechanism_server_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len) +{ + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL); + g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL); + + /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */ + g_assert_not_reached (); + + return NULL; +} + +static gchar * +mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL); + g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL); + + /* can never end up here because we are never in the REJECTED state */ + g_assert_not_reached (); + + return NULL; +} + +static void +mechanism_server_shutdown (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism)); + g_return_if_fail (m->priv->is_server && !m->priv->is_client); + + m->priv->is_server = FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusAuthMechanismState +mechanism_client_get_state (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID); + g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID); + + return m->priv->state; +} + +static gchar * +mechanism_client_initiate (GDBusAuthMechanism *mechanism, + gsize *out_initial_response_len) +{ + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + gchar *initial_response; + GCredentials *credentials; + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL); + g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL); + + m->priv->is_client = TRUE; + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED; + + *out_initial_response_len = -1; + + credentials = _g_dbus_auth_mechanism_get_credentials (mechanism); + g_assert (credentials != NULL); + + /* return the uid */ +#if defined(G_OS_UNIX) + initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) g_credentials_get_unix_user (credentials, NULL)); +#elif defined(G_OS_WIN32) + initial_response = g_strdup_printf ("%s", g_credentials_get_windows_user ()); +#else +#warning Dont know how to send credentials on this OS. Please implement. + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; +#endif + return initial_response; +} + +static void +mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len) +{ + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism)); + g_return_if_fail (m->priv->is_client && !m->priv->is_server); + g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA); + + /* can never end up here because we are never in the WAITING_FOR_DATA state */ + g_assert_not_reached (); +} + +static gchar * +mechanism_client_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len) +{ + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL); + g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL); + + /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */ + g_assert_not_reached (); + + return NULL; +} + +static void +mechanism_client_shutdown (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism)); + g_return_if_fail (m->priv->is_client && !m->priv->is_server); + + m->priv->is_client = FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#define __G_DBUS_AUTH_MECHANISM_EXTERNAL_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusauthmechanismexternal.h b/gio/gdbusauthmechanismexternal.h new file mode 100644 index 000000000..552dd2e71 --- /dev/null +++ b/gio/gdbusauthmechanismexternal.h @@ -0,0 +1,82 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (GIO_COMPILATION) +#error "gdbusauthmechanismexternal.h is a private header file." +#endif + +#ifndef __G_DBUS_AUTH_MECHANISM_EXTERNAL_H__ +#define __G_DBUS_AUTH_MECHANISM_EXTERNAL_H__ + +#include +#include + +G_BEGIN_DECLS + +#define G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL (_g_dbus_auth_mechanism_external_get_type ()) +#define G_DBUS_AUTH_MECHANISM_EXTERNAL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL, GDBusAuthMechanismExternal)) +#define G_DBUS_AUTH_MECHANISM_EXTERNAL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL, GDBusAuthMechanismExternalClass)) +#define G_DBUS_AUTH_MECHANISM_EXTERNAL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL, GDBusAuthMechanismExternalClass)) +#define G_IS_DBUS_AUTH_MECHANISM_EXTERNAL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL)) +#define G_IS_DBUS_AUTH_MECHANISM_EXTERNAL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL)) + +typedef struct _GDBusAuthMechanismExternal GDBusAuthMechanismExternal; +typedef struct _GDBusAuthMechanismExternalClass GDBusAuthMechanismExternalClass; +typedef struct _GDBusAuthMechanismExternalPrivate GDBusAuthMechanismExternalPrivate; + +struct _GDBusAuthMechanismExternalClass +{ + /*< private >*/ + GDBusAuthMechanismClass parent_class; + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); + void (*_g_reserved9) (void); + void (*_g_reserved10) (void); + void (*_g_reserved11) (void); + void (*_g_reserved12) (void); + void (*_g_reserved13) (void); + void (*_g_reserved14) (void); + void (*_g_reserved15) (void); + void (*_g_reserved16) (void); +}; + +struct _GDBusAuthMechanismExternal +{ + GDBusAuthMechanism parent_instance; + GDBusAuthMechanismExternalPrivate *priv; +}; + +GType _g_dbus_auth_mechanism_external_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __G_DBUS_AUTH_MECHANISM_EXTERNAL_H__ */ diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c new file mode 100644 index 000000000..405698e00 --- /dev/null +++ b/gio/gdbusauthmechanismsha1.c @@ -0,0 +1,1222 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include + +#include "gdbusauthmechanismsha1.h" +#include "gcredentials.h" +#include "gdbuserror.h" +#include "gioenumtypes.h" +#include "gioerror.h" + +#ifdef G_OS_UNIX +#include +#include +#include +#endif + +#include + +#include + +#include "glibintl.h" +#include "gioalias.h" + +struct _GDBusAuthMechanismSha1Private +{ + gboolean is_client; + gboolean is_server; + GDBusAuthMechanismState state; + + /* used on the client side */ + gchar *to_send; + + /* used on the server side */ + gchar *cookie; + gchar *server_challenge; +}; + +static gint mechanism_get_priority (void); +static const gchar *mechanism_get_name (void); + +static gboolean mechanism_is_supported (GDBusAuthMechanism *mechanism); +static gchar *mechanism_encode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len); +static gchar *mechanism_decode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len); +static GDBusAuthMechanismState mechanism_server_get_state (GDBusAuthMechanism *mechanism); +static void mechanism_server_initiate (GDBusAuthMechanism *mechanism, + const gchar *initial_response, + gsize initial_response_len); +static void mechanism_server_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len); +static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); +static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism); +static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism); +static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism); +static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism, + gsize *out_initial_response_len); +static void mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len); +static gchar *mechanism_client_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); +static void mechanism_client_shutdown (GDBusAuthMechanism *mechanism); + +/* ---------------------------------------------------------------------------------------------------- */ + +G_DEFINE_TYPE (GDBusAuthMechanismSha1, _g_dbus_auth_mechanism_sha1, G_TYPE_DBUS_AUTH_MECHANISM); + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +_g_dbus_auth_mechanism_sha1_finalize (GObject *object) +{ + GDBusAuthMechanismSha1 *mechanism = G_DBUS_AUTH_MECHANISM_SHA1 (object); + + g_free (mechanism->priv->to_send); + + g_free (mechanism->priv->cookie); + g_free (mechanism->priv->server_challenge); + + if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_sha1_parent_class)->finalize != NULL) + G_OBJECT_CLASS (_g_dbus_auth_mechanism_sha1_parent_class)->finalize (object); +} + +static void +_g_dbus_auth_mechanism_sha1_class_init (GDBusAuthMechanismSha1Class *klass) +{ + GObjectClass *gobject_class; + GDBusAuthMechanismClass *mechanism_class; + + g_type_class_add_private (klass, sizeof (GDBusAuthMechanismSha1Private)); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = _g_dbus_auth_mechanism_sha1_finalize; + + mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass); + mechanism_class->get_priority = mechanism_get_priority; + mechanism_class->get_name = mechanism_get_name; + mechanism_class->is_supported = mechanism_is_supported; + mechanism_class->encode_data = mechanism_encode_data; + mechanism_class->decode_data = mechanism_decode_data; + mechanism_class->server_get_state = mechanism_server_get_state; + mechanism_class->server_initiate = mechanism_server_initiate; + mechanism_class->server_data_receive = mechanism_server_data_receive; + mechanism_class->server_data_send = mechanism_server_data_send; + mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason; + mechanism_class->server_shutdown = mechanism_server_shutdown; + mechanism_class->client_get_state = mechanism_client_get_state; + mechanism_class->client_initiate = mechanism_client_initiate; + mechanism_class->client_data_receive = mechanism_client_data_receive; + mechanism_class->client_data_send = mechanism_client_data_send; + mechanism_class->client_shutdown = mechanism_client_shutdown; +} + +static void +_g_dbus_auth_mechanism_sha1_init (GDBusAuthMechanismSha1 *mechanism) +{ + mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism, + G_TYPE_DBUS_AUTH_MECHANISM_SHA1, + GDBusAuthMechanismSha1Private); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gint +mechanism_get_priority (void) +{ + return 0; +} + +static const gchar * +mechanism_get_name (void) +{ + return "DBUS_COOKIE_SHA1"; +} + +static gboolean +mechanism_is_supported (GDBusAuthMechanism *mechanism) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), FALSE); + return TRUE; +} + +static gchar * +mechanism_encode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len) +{ + return NULL; +} + + +static gchar * +mechanism_decode_data (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len, + gsize *out_data_len) +{ + return NULL; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gint +random_ascii (void) +{ + gint ret; + ret = g_random_int_range (0, 60); + if (ret < 25) + ret += 'A'; + else if (ret < 50) + ret += 'a' - 25; + else + ret += '0' - 50; + return ret; +} + +static gchar * +random_ascii_string (guint len) +{ + GString *challenge; + guint n; + + challenge = g_string_new (NULL); + for (n = 0; n < len; n++) + g_string_append_c (challenge, random_ascii ()); + return g_string_free (challenge, FALSE); +} + +static gchar * +random_blob (guint len) +{ + GString *challenge; + guint n; + + challenge = g_string_new (NULL); + for (n = 0; n < len; n++) + g_string_append_c (challenge, g_random_int_range (0, 256)); + return g_string_free (challenge, FALSE); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* ensure keyring dir exists and permissions are correct */ +static gchar * +ensure_keyring_directory (GError **error) +{ + gchar *path; + const gchar *e; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + e = g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR"); + if (e != NULL) + { + path = g_strdup (e); + } + else + { + path = g_build_filename (g_get_home_dir (), + ".dbus-keyrings", + NULL); + } + + if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) + { + if (g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR_IGNORE_PERMISSION") == NULL) + { +#ifdef G_OS_UNIX + struct stat statbuf; + if (stat (path, &statbuf) != 0) + { + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error statting directory `%s': %s"), + path, + strerror (errno)); + g_free (path); + path = NULL; + goto out; + } + if ((statbuf.st_mode & 0777) != 0700) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Permissions on directory `%s' are malformed. Expected mode 0700, got 0%o"), + path, + statbuf.st_mode & 0777); + g_free (path); + path = NULL; + goto out; + } +#else +#error Please implement permission checking on non-UNIX platforms +#endif + } + goto out; + } + + if (g_mkdir (path, 0700) != 0) + { + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error creating directory `%s': %s"), + path, + strerror (errno)); + g_free (path); + path = NULL; + goto out; + } + +out: + return path; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +append_nibble (GString *s, gint val) +{ + g_string_append_c (s, val >= 10 ? ('a' + val - 10) : ('0' + val)); +} + +static gchar * +hexencode (const gchar *str, + gssize len) +{ + guint n; + GString *s; + + if (len == -1) + len = strlen (str); + + s = g_string_new (NULL); + for (n = 0; n < len; n++) + { + gint val; + gint upper_nibble; + gint lower_nibble; + + val = ((const guchar *) str)[n]; + upper_nibble = val >> 4; + lower_nibble = val & 0x0f; + + append_nibble (s, upper_nibble); + append_nibble (s, lower_nibble); + } + + return g_string_free (s, FALSE); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* looks up an entry in the keyring */ +static gchar * +keyring_lookup_entry (const gchar *cookie_context, + gint cookie_id, + GError **error) +{ + gchar *ret; + gchar *keyring_dir; + gchar *contents; + gchar *path; + guint n; + gchar **lines; + + g_return_val_if_fail (cookie_context != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = NULL; + path = NULL; + contents = NULL; + lines = NULL; + + keyring_dir = ensure_keyring_directory (error); + if (keyring_dir == NULL) + goto out; + + path = g_build_filename (keyring_dir, cookie_context, NULL); + + if (!g_file_get_contents (path, + &contents, + NULL, + error)) + { + g_prefix_error (error, + _("Error opening keyring `%s' for reading: "), + path); + goto out; + } + g_assert (contents != NULL); + + lines = g_strsplit (contents, "\n", 0); + for (n = 0; lines[n] != NULL; n++) + { + const gchar *line = lines[n]; + gchar **tokens; + gchar *endp; + gint line_id; + guint64 line_when; + + if (line[0] == '\0') + continue; + + tokens = g_strsplit (line, " ", 0); + if (g_strv_length (tokens) != 3) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Line %d of the keyring at `%s' with content `%s' is malformed"), + n + 1, + path, + line); + g_strfreev (tokens); + goto out; + } + + line_id = g_ascii_strtoll (tokens[0], &endp, 10); + if (*endp != '\0') + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("First token of line %d of the keyring at `%s' with content `%s' is malformed"), + n + 1, + path, + line); + g_strfreev (tokens); + goto out; + } + + line_when = g_ascii_strtoll (tokens[1], &endp, 10); + if (*endp != '\0') + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Second token of line %d of the keyring at `%s' with content `%s' is malformed"), + n + 1, + path, + line); + g_strfreev (tokens); + goto out; + } + + if (line_id == cookie_id) + { + /* YAY, success */ + ret = tokens[2]; /* steal pointer */ + tokens[2] = NULL; + g_strfreev (tokens); + goto out; + } + + g_strfreev (tokens); + } + + /* BOOH, didn't find the cookie */ + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Didn't find cookie with id %d in the keyring at `%s'"), + cookie_id, + path); + + out: + g_free (keyring_dir); + g_free (path); + g_free (contents); + g_strfreev (lines); + return ret; +} + +/* function for logging important events that the system administrator should take notice of */ +static void +_log (const gchar *message, + ...) +{ + gchar *s; + va_list var_args; + + va_start (var_args, message); + s = g_strdup_vprintf (message, var_args); + va_end (var_args); + + /* TODO: might want to send this to syslog instead */ + g_printerr ("GDBus-DBUS_COOKIE_SHA1: %s\n", s); + g_free (s); +} + +static gint +keyring_acquire_lock (const gchar *path, + GError **error) +{ + gchar *lock; + gint ret; + guint num_tries; + guint num_create_tries; + + g_return_val_if_fail (path != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = -1; + lock = g_strdup_printf ("%s.lock", path); + + /* This is what the D-Bus spec says + * + * Create a lockfile name by appending ".lock" to the name of the + * cookie file. The server should attempt to create this file using + * O_CREAT | O_EXCL. If file creation fails, the lock + * fails. Servers should retry for a reasonable period of time, + * then they may choose to delete an existing lock to keep users + * from having to manually delete a stale lock. [1] + * + * [1] : Lockfiles are used instead of real file locking fcntl() because + * real locking implementations are still flaky on network filesystems + */ + + num_create_tries = 0; +#ifdef EEXISTS + again: +#endif + num_tries = 0; + while (g_file_test (lock, G_FILE_TEST_EXISTS)) + { + /* sleep 10ms, then try again */ + g_usleep (1000*10); + num_tries++; + if (num_tries == 50) + { + /* ok, we slept 50*10ms = 0.5 seconds.. Conclude that the lock-file must be + * stale (nuke the it from orbit) + */ + if (g_unlink (lock) != 0) + { + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error deleting stale lock-file `%s': %s"), + lock, + strerror (errno)); + goto out; + } + _log ("Deleted stale lock-file `%s'", lock); + break; + } + } + + ret = g_open (lock, O_CREAT | +#ifdef O_EXCL + O_EXCL, +#else + 0, +#endif + 0700); + if (ret == -1) + { +#ifdef EEXISTS + /* EEXIST: pathname already exists and O_CREAT and O_EXCL were used. */ + if (errno == EEXISTS) + { + num_create_tries++; + if (num_create_tries < 5) + goto again; + } +#endif + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error creating lock-file `%s': %s"), + lock, + strerror (errno)); + goto out; + } + + out: + g_free (lock); + return ret; +} + +static gboolean +keyring_release_lock (const gchar *path, + gint lock_fd, + GError **error) +{ + gchar *lock; + gboolean ret; + + g_return_val_if_fail (path != NULL, FALSE); + g_return_val_if_fail (lock_fd != -1, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = FALSE; + lock = g_strdup_printf ("%s.lock", path); + if (g_unlink (lock) != 0) + { + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error unlinking lock-file `%s': %s"), + lock, + strerror (errno)); + goto out; + } + if (close (lock_fd) != 0) + { + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error closing (unlinked) lock-file `%s': %s"), + lock, + strerror (errno)); + goto out; + } + + ret = TRUE; + + out: + g_free (lock); + return ret; +} + + +/* adds an entry to the keyring, taking care of locking and deleting stale/future entries */ +static gboolean +keyring_generate_entry (const gchar *cookie_context, + gint *out_id, + gchar **out_cookie, + GError **error) +{ + gboolean ret; + gchar *keyring_dir; + gchar *path; + gchar *contents; + GError *local_error; + gchar **lines; + gint max_line_id; + GString *new_contents; + guint64 now; + gboolean have_id; + gint use_id; + gchar *use_cookie; + gboolean changed_file; + gint lock_fd; + + g_return_val_if_fail (cookie_context != NULL, FALSE); + g_return_val_if_fail (out_id != NULL, FALSE); + g_return_val_if_fail (out_cookie != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = FALSE; + path = NULL; + contents = NULL; + lines = NULL; + new_contents = NULL; + have_id = FALSE; + use_cookie = NULL; + lock_fd = -1; + + keyring_dir = ensure_keyring_directory (error); + if (keyring_dir == NULL) + goto out; + + path = g_build_filename (keyring_dir, cookie_context, NULL); + + lock_fd = keyring_acquire_lock (path, error); + if (lock_fd == -1) + goto out; + + local_error = NULL; + contents = NULL; + if (!g_file_get_contents (path, + &contents, + NULL, + &local_error)) + { + if (local_error->domain == G_FILE_ERROR && local_error->code == G_FILE_ERROR_NOENT) + { + /* file doesn't have to exist */ + g_error_free (local_error); + } + else + { + g_propagate_prefixed_error (error, + local_error, + _("Error opening keyring `%s' for writing: "), + path); + goto out; + } + } + + new_contents = g_string_new (NULL); + now = time (NULL); + changed_file = FALSE; + + max_line_id = 0; + if (contents != NULL) + { + guint n; + lines = g_strsplit (contents, "\n", 0); + for (n = 0; lines[n] != NULL; n++) + { + const gchar *line = lines[n]; + gchar **tokens; + gchar *endp; + gint line_id; + guint64 line_when; + gboolean keep_entry; + + if (line[0] == '\0') + continue; + + tokens = g_strsplit (line, " ", 0); + if (g_strv_length (tokens) != 3) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Line %d of the keyring at `%s' with content `%s' is malformed"), + n + 1, + path, + line); + g_strfreev (tokens); + goto out; + } + + line_id = g_ascii_strtoll (tokens[0], &endp, 10); + if (*endp != '\0') + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("First token of line %d of the keyring at `%s' with content `%s' is malformed"), + n + 1, + path, + line); + g_strfreev (tokens); + goto out; + } + + line_when = g_ascii_strtoll (tokens[1], &endp, 10); + if (*endp != '\0') + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Second token of line %d of the keyring at `%s' with content `%s' is malformed"), + n + 1, + path, + line); + g_strfreev (tokens); + goto out; + } + + /* D-Bus spec says: + * + * Once the lockfile has been created, the server loads the + * cookie file. It should then delete any cookies that are + * old (the timeout can be fairly short), or more than a + * reasonable time in the future (so that cookies never + * accidentally become permanent, if the clock was set far + * into the future at some point). If no recent keys remain, + * the server may generate a new key. + * + */ + keep_entry = TRUE; + if (line_when > now) + { + /* Oddball case: entry is more recent than our current wall-clock time.. + * This is OK, it means that another server on another machine but with + * same $HOME wrote the entry. + * + * So discard the entry if it's more than 1 day in the future ("reasonable + * time in the future"). + */ + if (line_when - now > 24*60*60) + { + keep_entry = FALSE; + _log ("Deleted SHA1 cookie from %" G_GUINT64_FORMAT " seconds in the future", line_when - now); + } + } + else + { + /* Discard entry if it's older than 15 minutes ("can be fairly short") */ + if (now - line_when > 15*60) + { + keep_entry = FALSE; + } + } + + if (!keep_entry) + { + changed_file = FALSE; + } + else + { + g_string_append_printf (new_contents, + "%d %" G_GUINT64_FORMAT " %s\n", + line_id, + line_when, + tokens[2]); + max_line_id = MAX (line_id, max_line_id); + /* Only reuse entry if not older than 10 minutes. + * + * (We need a bit of grace time compared to 15 minutes above.. otherwise + * there's a race where we reuse the 14min59.9 secs old entry and a + * split-second later another server purges the now 15 minute old entry.) + */ + if (now - line_when < 10 * 60) + { + if (!have_id) + { + use_id = line_id; + use_cookie = tokens[2]; /* steal memory */ + tokens[2] = NULL; + have_id = TRUE; + } + } + } + g_strfreev (tokens); + } + } /* for each line */ + + ret = TRUE; + + if (have_id) + { + *out_id = use_id; + *out_cookie = use_cookie; + use_cookie = NULL; + } + else + { + gchar *raw_cookie; + *out_id = max_line_id + 1; + raw_cookie = random_blob (32); + *out_cookie = hexencode (raw_cookie, 32); + g_free (raw_cookie); + + g_string_append_printf (new_contents, + "%d %" G_GUINT64_FORMAT " %s\n", + *out_id, + (guint64) time (NULL), + *out_cookie); + changed_file = TRUE; + } + + /* and now actually write the cookie file if there are changes (this is atomic) */ + if (changed_file) + { + if (!g_file_set_contents (path, + new_contents->str, + -1, + error)) + { + *out_id = 0; + *out_cookie = 0; + g_free (*out_cookie); + ret = FALSE; + goto out; + } + } + + out: + + if (lock_fd != -1) + { + GError *local_error; + local_error = NULL; + if (!keyring_release_lock (path, lock_fd, &local_error)) + { + if (error != NULL) + { + if (*error == NULL) + { + *error = local_error; + } + else + { + g_prefix_error (error, + _("(Additionally, releasing the lock for `%s' also failed: %s) "), + path, + local_error->message); + } + } + else + { + g_error_free (local_error); + } + } + } + + g_free (keyring_dir); + g_free (path); + g_strfreev (lines); + g_free (contents); + if (new_contents != NULL) + g_string_free (new_contents, TRUE); + g_free (use_cookie); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +generate_sha1 (const gchar *server_challenge, + const gchar *client_challenge, + const gchar *cookie) +{ + GString *str; + gchar *sha1; + + str = g_string_new (server_challenge); + g_string_append_c (str, ':'); + g_string_append (str, client_challenge); + g_string_append_c (str, ':'); + g_string_append (str, cookie); + sha1 = g_compute_checksum_for_string (G_CHECKSUM_SHA1, str->str, -1); + g_string_free (str, TRUE); + + return sha1; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusAuthMechanismState +mechanism_server_get_state (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID); + g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID); + + return m->priv->state; +} + +static void +mechanism_server_initiate (GDBusAuthMechanism *mechanism, + const gchar *initial_response, + gsize initial_response_len) +{ + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism)); + g_return_if_fail (!m->priv->is_server && !m->priv->is_client); + + m->priv->is_server = TRUE; + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; + + if (initial_response != NULL && strlen (initial_response) > 0) + { +#ifdef G_OS_UNIX + gint64 uid; + gchar *endp; + + uid = g_ascii_strtoll (initial_response, &endp, 10); + if (*endp == '\0') + { + if (uid == getuid ()) + { + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND; + } + } +#elif defined(G_OS_WIN32) + GCredentials *credentials; + credentials = g_credentials_new_for_process (); + if (g_strcmp0 (g_credentials_get_windows_user (credentials), initial_response) == 0) + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND; + g_object_unref (credentials); +#else +#error Please implement for your OS +#endif + } +} + +static void +mechanism_server_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len) +{ + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + gchar **tokens; + const gchar *client_challenge; + const gchar *alleged_sha1; + gchar *sha1; + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism)); + g_return_if_fail (m->priv->is_server && !m->priv->is_client); + g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA); + + tokens = NULL; + sha1 = NULL; + + tokens = g_strsplit (data, " ", 0); + if (g_strv_length (tokens) != 2) + { + g_warning ("Malformed data `%s'", data); + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; + goto out; + } + + client_challenge = tokens[0]; + alleged_sha1 = tokens[1]; + + sha1 = generate_sha1 (m->priv->server_challenge, client_challenge, m->priv->cookie); + + if (g_strcmp0 (sha1, alleged_sha1) == 0) + { + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED; + } + else + { + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; + } + + out: + g_strfreev (tokens); + g_free (sha1); +} + +static gchar * +mechanism_server_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len) +{ + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + gchar *s; + gint cookie_id; + const gchar *cookie_context; + GError *error; + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL); + g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL); + + s = NULL; + + /* TODO: use GDBusAuthObserver here to get the cookie context to use? */ + cookie_context = "org_gtk_gdbus_general"; + + error = NULL; + if (!keyring_generate_entry (cookie_context, + &cookie_id, + &m->priv->cookie, + &error)) + { + g_warning ("Error adding entry to keyring: %s", error->message); + g_error_free (error); + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; + goto out; + } + + m->priv->server_challenge = random_ascii_string (16); + s = g_strdup_printf ("%s %d %s", + cookie_context, + cookie_id, + m->priv->server_challenge); + + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA; + + out: + return s; +} + +static gchar * +mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL); + g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL); + + /* can never end up here because we are never in the REJECTED state */ + g_assert_not_reached (); + + return NULL; +} + +static void +mechanism_server_shutdown (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism)); + g_return_if_fail (m->priv->is_server && !m->priv->is_client); + + m->priv->is_server = FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusAuthMechanismState +mechanism_client_get_state (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID); + g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID); + + return m->priv->state; +} + +static gchar * +mechanism_client_initiate (GDBusAuthMechanism *mechanism, + gsize *out_initial_response_len) +{ + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + gchar *initial_response; + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL); + g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL); + + m->priv->is_client = TRUE; + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA; + + *out_initial_response_len = -1; + +#ifdef G_OS_UNIX + initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) getuid ()); +#elif defined (G_OS_WIN32) + { + GCredentials *credentials; + credentials = g_credentials_new_for_process (); + initial_response = g_strdup (g_credentials_get_windows_user (credentials)); + g_object_unref (credentials); + } +#else +#endif + g_assert (initial_response != NULL); + + return initial_response; +} + +static void +mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, + gsize data_len) +{ + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + gchar **tokens; + const gchar *cookie_context; + guint cookie_id; + const gchar *server_challenge; + gchar *client_challenge; + gchar *endp; + gchar *cookie; + GError *error; + gchar *sha1; + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism)); + g_return_if_fail (m->priv->is_client && !m->priv->is_server); + g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA); + + tokens = NULL; + cookie = NULL; + client_challenge = NULL; + + tokens = g_strsplit (data, " ", 0); + if (g_strv_length (tokens) != 3) + { + g_warning ("Malformed data `%s'", data); + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; + goto out; + } + + cookie_context = tokens[0]; + cookie_id = g_ascii_strtoll (tokens[1], &endp, 10); + if (*endp != '\0') + { + g_warning ("Malformed cookie_id `%s'", tokens[1]); + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; + goto out; + } + server_challenge = tokens[2]; + + error = NULL; + cookie = keyring_lookup_entry (cookie_context, cookie_id, &error); + if (cookie == NULL) + { + g_warning ("Problems looking up entry in keyring: %s", error->message); + g_error_free (error); + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; + goto out; + } + + client_challenge = random_ascii_string (16); + sha1 = generate_sha1 (server_challenge, client_challenge, cookie); + m->priv->to_send = g_strdup_printf ("%s %s", client_challenge, sha1); + g_free (sha1); + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND; + + out: + g_strfreev (tokens); + g_free (cookie); + g_free (client_challenge); +} + +static gchar * +mechanism_client_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len) +{ + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL); + g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL); + + g_assert (m->priv->to_send != NULL); + + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED; + + return g_strdup (m->priv->to_send); +} + +static void +mechanism_client_shutdown (GDBusAuthMechanism *mechanism) +{ + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + + g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism)); + g_return_if_fail (m->priv->is_client && !m->priv->is_server); + + m->priv->is_client = FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#define __G_DBUS_AUTH_MECHANISM_SHA1_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusauthmechanismsha1.h b/gio/gdbusauthmechanismsha1.h new file mode 100644 index 000000000..762fc5abf --- /dev/null +++ b/gio/gdbusauthmechanismsha1.h @@ -0,0 +1,82 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (GIO_COMPILATION) +#error "gdbusauthmechanismsha1.h is a private header file." +#endif + +#ifndef __G_DBUS_AUTH_MECHANISM_SHA1_H__ +#define __G_DBUS_AUTH_MECHANISM_SHA1_H__ + +#include +#include + +G_BEGIN_DECLS + +#define G_TYPE_DBUS_AUTH_MECHANISM_SHA1 (_g_dbus_auth_mechanism_sha1_get_type ()) +#define G_DBUS_AUTH_MECHANISM_SHA1(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM_SHA1, GDBusAuthMechanismSha1)) +#define G_DBUS_AUTH_MECHANISM_SHA1_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM_SHA1, GDBusAuthMechanismSha1Class)) +#define G_DBUS_AUTH_MECHANISM_SHA1_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM_SHA1, GDBusAuthMechanismSha1Class)) +#define G_IS_DBUS_AUTH_MECHANISM_SHA1(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM_SHA1)) +#define G_IS_DBUS_AUTH_MECHANISM_SHA1_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM_SHA1)) + +typedef struct _GDBusAuthMechanismSha1 GDBusAuthMechanismSha1; +typedef struct _GDBusAuthMechanismSha1Class GDBusAuthMechanismSha1Class; +typedef struct _GDBusAuthMechanismSha1Private GDBusAuthMechanismSha1Private; + +struct _GDBusAuthMechanismSha1Class +{ + /*< private >*/ + GDBusAuthMechanismClass parent_class; + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); + void (*_g_reserved9) (void); + void (*_g_reserved10) (void); + void (*_g_reserved11) (void); + void (*_g_reserved12) (void); + void (*_g_reserved13) (void); + void (*_g_reserved14) (void); + void (*_g_reserved15) (void); + void (*_g_reserved16) (void); +}; + +struct _GDBusAuthMechanismSha1 +{ + GDBusAuthMechanism parent_instance; + GDBusAuthMechanismSha1Private *priv; +}; + +GType _g_dbus_auth_mechanism_sha1_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __G_DBUS_AUTH_MECHANISM_SHA1_H__ */ diff --git a/gio/gdbusauthobserver.c b/gio/gdbusauthobserver.c new file mode 100644 index 000000000..d7fb73aad --- /dev/null +++ b/gio/gdbusauthobserver.c @@ -0,0 +1,247 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include "gdbusauthobserver.h" +#include "gio-marshal.h" +#include "gcredentials.h" +#include "gioenumtypes.h" +#include "giostream.h" + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusauthobserver + * @short_description: Object used for authenticating connections + * @include: gio/gio.h + * + * The #GDBusAuthObserver type provides a mechanism for participating + * in how a #GDBusServer (or a #GDBusConnection) authenticates remote + * peers. Simply instantiate a #GDBusAuthObserver and connect to the + * signals you are interested in. Note that new signals may be added + * in the future + * + * For example, if you only want to allow D-Bus connections from + * processes owned by the same uid as the server, you would do this: + * Controlling Authentication + * static gboolean + * on_authorize_authenticated_peer (GDBusAuthObserver *observer, + * GIOStream *stream, + * GCredentials *credentials, + * gpointer user_data) + * { + * GCredentials *me; + * gboolean authorized; + * + * authorized = FALSE; + * me = g_credentials_new (); + * + * if (credentials != NULL && + * !g_credentials_is_same_user (credentials, me)) + * authorized = TRUE; + * + * g_object_unref (me); + * + * return authorized; + * } + * + * static gboolean + * on_new_connection (GDBusServer *server, + * GDBusConnection *connection, + * gpointer user_data) + * { + * /* Guaranteed here that @connection is from a process owned by the same user */ + * } + * + * [...] + * + * GDBusAuthObserver *observer; + * GDBusServer *server; + * GError *error; + * + * error = NULL; + * observer = g_dbus_auth_observer_new (); + * server = g_dbus_server_new_sync ("unix:tmpdir=/tmp/my-app-name", + * G_DBUS_SERVER_FLAGS_NONE, + * observer, + * NULL, /* GCancellable */ + * &error); + * g_signal_connect (observer, + * "authorize-authenticated-peer", + * G_CALLBACK (on_authorize_authenticated_peer), + * NULL); + * g_signal_connect (server, + * "new-connection", + * G_CALLBACK (on_new_connection), + * NULL); + * g_object_unref (observer); + * g_dbus_server_start (server); + * + */ + +struct _GDBusAuthObserverPrivate +{ + gint foo; +}; + +enum +{ + AUTHORIZE_AUTHENTICATED_PEER_SIGNAL, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (GDBusAuthObserver, g_dbus_auth_observer, G_TYPE_OBJECT); + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +g_dbus_auth_observer_finalize (GObject *object) +{ + G_OBJECT_CLASS (g_dbus_auth_observer_parent_class)->finalize (object); +} + +static gboolean +g_dbus_auth_observer_authorize_authenticated_peer_real (GDBusAuthObserver *observer, + GIOStream *stream, + GCredentials *credentials) +{ + return TRUE; +} + +gboolean +_g_signal_accumulator_false_handled (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy) +{ + gboolean continue_emission; + gboolean signal_handled; + + signal_handled = g_value_get_boolean (handler_return); + g_value_set_boolean (return_accu, signal_handled); + continue_emission = signal_handled; + + return continue_emission; +} + +static void +g_dbus_auth_observer_class_init (GDBusAuthObserverClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_dbus_auth_observer_finalize; + + klass->authorize_authenticated_peer = g_dbus_auth_observer_authorize_authenticated_peer_real; + + /** + * GDBusAuthObserver::authorize-authenticated-peer: + * @observer: The #GDBusAuthObserver emitting the signal. + * @stream: A #GIOStream for the #GDBusConnection. + * @credentials: Credentials received from the peer or %NULL. + * + * Emitted to check if a peer that is successfully authenticated + * is authorized. + * + * Returns: %TRUE if the peer is authorized, %FALSE if not. + * + * Since: 2.26 + */ + signals[AUTHORIZE_AUTHENTICATED_PEER_SIGNAL] = + g_signal_new ("authorize-authenticated-peer", + G_TYPE_DBUS_AUTH_OBSERVER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GDBusAuthObserverClass, authorize_authenticated_peer), + _g_signal_accumulator_false_handled, + NULL, /* accu_data */ + _gio_marshal_BOOLEAN__OBJECT_OBJECT, + G_TYPE_BOOLEAN, + 2, + G_TYPE_IO_STREAM, + G_TYPE_CREDENTIALS); + + + g_type_class_add_private (klass, sizeof (GDBusAuthObserverPrivate)); +} + +static void +g_dbus_auth_observer_init (GDBusAuthObserver *observer) +{ + /* not used for now */ + observer->priv = G_TYPE_INSTANCE_GET_PRIVATE (observer, + G_TYPE_DBUS_AUTH_OBSERVER, + GDBusAuthObserverPrivate);; +} + +/** + * g_dbus_auth_observer_new: + * + * Creates a new #GDBusAuthObserver object. + * + * Returns: A #GDBusAuthObserver. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusAuthObserver * +g_dbus_auth_observer_new (void) +{ + return g_object_new (G_TYPE_DBUS_AUTH_OBSERVER, NULL); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_auth_observer_authorize_authenticated_peer: + * @observer: A #GDBusAuthObserver. + * @stream: A #GIOStream for the #GDBusConnection. + * @credentials: Credentials received from the peer or %NULL. + * + * Emits the #GDBusAuthObserver::authorize-authenticated-peer signal on @observer. + * + * Returns: %TRUE if the peer should be denied, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_auth_observer_authorize_authenticated_peer (GDBusAuthObserver *observer, + GIOStream *stream, + GCredentials *credentials) +{ + gboolean denied; + + denied = FALSE; + g_signal_emit (observer, + signals[AUTHORIZE_AUTHENTICATED_PEER_SIGNAL], + 0, + stream, + credentials, + &denied); + return denied; +} + + + +#define __G_DBUS_AUTH_OBSERVER_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusauthobserver.h b/gio/gdbusauthobserver.h new file mode 100644 index 000000000..b40836508 --- /dev/null +++ b/gio/gdbusauthobserver.h @@ -0,0 +1,104 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_AUTH_OBSERVER_H__ +#define __G_DBUS_AUTH_OBSERVER_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_DBUS_AUTH_OBSERVER (g_dbus_auth_observer_get_type ()) +#define G_DBUS_AUTH_OBSERVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_OBSERVER, GDBusAuthObserver)) +#define G_DBUS_AUTH_OBSERVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_OBSERVER, GDBusAuthObserverClass)) +#define G_DBUS_AUTH_OBSERVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_OBSERVER, GDBusAuthObserverClass)) +#define G_IS_DBUS_AUTH_OBSERVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_OBSERVER)) +#define G_IS_DBUS_AUTH_OBSERVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_OBSERVER)) + +typedef struct _GDBusAuthObserverClass GDBusAuthObserverClass; +typedef struct _GDBusAuthObserverPrivate GDBusAuthObserverPrivate; + + +/** + * GDBusAuthObserverClass: + * @authorize_authenticated_peer: Signal class handler for the #GDBusAuthObserver::authorize-authenticated-peer signal. + * + * Class structure for #GDBusAuthObserverClass. + * + * Since: 2.26 + */ +struct _GDBusAuthObserverClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + + /* Signals */ + gboolean (*authorize_authenticated_peer) (GDBusAuthObserver *observer, + GIOStream *stream, + GCredentials *credentials); + + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); + void (*_g_reserved9) (void); + void (*_g_reserved10) (void); + void (*_g_reserved11) (void); + void (*_g_reserved12) (void); + void (*_g_reserved13) (void); + void (*_g_reserved14) (void); + void (*_g_reserved15) (void); + void (*_g_reserved16) (void); +}; + +/** + * GDBusAuthObserver: + * + * The #GDBusAuthObserver structure contains only private data and + * should only be accessed using the provided API. + * + * Since: 2.26 + */ +struct _GDBusAuthObserver +{ + GObject parent_instance; + GDBusAuthObserverPrivate *priv; +}; + +GType g_dbus_auth_observer_get_type (void) G_GNUC_CONST; +GDBusAuthObserver *g_dbus_auth_observer_new (void); +gboolean g_dbus_auth_observer_authorize_authenticated_peer (GDBusAuthObserver *observer, + GIOStream *stream, + GCredentials *credentials); + +G_END_DECLS + +#endif /* _G_DBUS_AUTH_OBSERVER_H__ */ diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c new file mode 100644 index 000000000..c3d17af55 --- /dev/null +++ b/gio/gdbusconnection.c @@ -0,0 +1,5448 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +/* + * TODO for GDBus: + * + * - Validate all data (e.g. UTF-8) and check all the required D-Bus headers + * are present and forbidden ones aren't + * - When writing: g_dbus_message_to_blob() + * - When reading: g_dbus_message_new_from_blob() + * + * - would be nice to expose GDBusAuthMechanism and an extension point + * + * - Need to rewrite GDBusAuth and rework GDBusAuthMechanism. In particular + * the mechanism VFuncs need to be able to set an error. + * + * - Need to document other mechanisms/sources for determining the D-Bus + * address of a well-known bus. + * + * - e.g. on Win32 we need code like from here + * + * http://cgit.freedesktop.org/~david/gdbus-standalone/tree/gdbus/gdbusaddress.c#n900 + * + * that was never copied over here because it originally was copy-paste + * from the GPLv2 / AFL 2.1 libdbus sources. + * + * - on OS X we need to look in launchd for the address + * + * https://bugs.freedesktop.org/show_bug.cgi?id=14259 + * + * - on X11 we need to look in a X11 property on the X server + * - (we can also just use dbus-launch(1) from the D-Bus + * distribution) + * + * - (ideally) this requires D-Bus spec work because none of + * this has never really been specced out properly (excect + * the X11 bits) + * + * - Related to the above, we also need to be able to launch a message bus + * instance.... Since we don't want to write our own bus daemon we should + * launch dbus-daemon(1) (thus: Win32 and OS X need to bundle it) + * + * - probably want a G_DBUS_NONCE_TCP_TMPDIR environment variable + * to specify where the nonce is stored. This will allow people to use + * G_DBUS_NONCE_TCP_TMPDIR=/mnt/secure.company.server/dbus-nonce-dir + * to easily acheive secure RPC via nonce-tcp. + * + * - need to expose an extension point for resolving D-Bus address and + * turning them into GIOStream objects. This will allow us to implement + * e.g. X11 D-Bus transports without dlopen()'ing or linking against + * libX11 from libgio. + * - see g_dbus_address_connect() in gdbusaddress.c + * + * - would be cute to use kernel-specific APIs to resolve fds for + * debug output when using G_DBUS_DEBUG=messages, e.g. in addition to + * + * fd 21: dev=8:1,mode=0100644,ino=1171231,uid=0,gid=0,rdev=0:0,size=234,atime=1273070640,mtime=1267126160,ctime=1267126160 + * + * maybe we can show more information about what fd 21 really is. + * Ryan suggests looking in /proc/self/fd for clues / symlinks! + * Initial experiments on Linux 2.6 suggests that the symlink looks + * like this: + * + * 3 -> /proc/18068/fd + * + * e.g. not of much use. + * + * - GDBus High-Level docs + * - Proxy: properties, signals... + * - Connection: IOStream based, ::close, connection setup steps + * mainloop integration, threading + * - Differences from libdbus (extend "Migrating from") + * - the message handling thread + * - Using GVariant instead of GValue + * - Explain why the high-level API is a good thing and what + * kind of pitfalls it avoids + * - Export objects before claiming names + * - Talk about auto-starting services (cf. GBusNameWatcherFlags) + */ + +#include "config.h" + +#include +#include + +#include "gdbusauth.h" +#include "gdbusutils.h" +#include "gdbusaddress.h" +#include "gdbusmessage.h" +#include "gdbusconnection.h" +#include "gdbuserror.h" +#include "gioenumtypes.h" +#include "gdbusintrospection.h" +#include "gdbusmethodinvocation.h" +#include "gdbusprivate.h" +#include "gdbusauthobserver.h" +#include "gio-marshal.h" +#include "ginitable.h" +#include "gasyncinitable.h" +#include "giostream.h" +#include "gasyncresult.h" +#include "gsimpleasyncresult.h" + +#ifdef G_OS_UNIX +#include +#include +#include +#include +#endif + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusconnection + * @short_description: D-Bus Connections + * @include: gio/gio.h + * + * The #GDBusConnection type is used for D-Bus connections to remote + * peers such as a message buses. It is a low-level API that offers a + * lot of flexibility. For instance, it lets you establish a connection + * over any transport that can by represented as an #GIOStream. + * + * This class is rarely used directly in D-Bus clients. If you are writing + * an D-Bus client, it is often easier to use the g_bus_own_name(), + * g_bus_watch_name() or g_bus_watch_proxy() APIs. + * + * D-Bus server exampleFIXME: MISSING XINCLUDE CONTENT + * + * D-Bus subtree exampleFIXME: MISSING XINCLUDE CONTENT + * + * D-Bus UNIX File Descriptor exampleFIXME: MISSING XINCLUDE CONTENT + * + * Exporting a GObjectFIXME: MISSING XINCLUDE CONTENT + */ + +/* ---------------------------------------------------------------------------------------------------- */ + +G_LOCK_DEFINE_STATIC (message_bus_lock); + +static GDBusConnection *the_session_bus = NULL; +static GDBusConnection *the_system_bus = NULL; + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +_g_strv_has_string (const gchar* const *haystack, + const gchar *needle) +{ + guint n; + + for (n = 0; haystack != NULL && haystack[n] != NULL; n++) + { + if (g_strcmp0 (haystack[n], needle) == 0) + return TRUE; + } + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#ifdef G_OS_WIN32 +#define CONNECTION_ENSURE_LOCK(obj) do { ; } while (FALSE) +#else +// TODO: for some reason this doesn't work on Windows +#define CONNECTION_ENSURE_LOCK(obj) do { \ + if (G_UNLIKELY (g_mutex_trylock((obj)->priv->lock))) \ + { \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "CONNECTION_ENSURE_LOCK: GDBusConnection object lock is not locked"); \ + } \ + } while (FALSE) +#endif + +#define CONNECTION_LOCK(obj) do { \ + g_mutex_lock ((obj)->priv->lock); \ + } while (FALSE) + +#define CONNECTION_UNLOCK(obj) do { \ + g_mutex_unlock ((obj)->priv->lock); \ + } while (FALSE) + +struct _GDBusConnectionPrivate +{ + /* ------------------------------------------------------------------------ */ + /* -- General object state ------------------------------------------------ */ + /* ------------------------------------------------------------------------ */ + + /* object-wide lock */ + GMutex *lock; + + /* A lock used in the init() method of the GInitable interface - see comments + * in initable_init() for why a separate lock is needed + */ + GMutex *init_lock; + + /* Set (by loading the contents of /var/lib/dbus/machine-id) the first time + * someone calls org.freedesktop.DBus.GetMachineId() + */ + gchar *machine_id; + + /* The underlying stream used for communication */ + GIOStream *stream; + + /* The object used for authentication (if any) */ + GDBusAuth *auth; + + /* Set to TRUE if the connection has been closed */ + gboolean closed; + + /* Last serial used */ + guint32 last_serial; + + /* The object used to send/receive message */ + GDBusWorker *worker; + + /* If connected to a message bus, this contains the unique name assigned to + * us by the bus (e.g. ":1.42") + */ + gchar *bus_unique_name; + + /* The GUID returned by the other side if we authenticed as a client or + * the GUID to use if authenticating as a server + */ + gchar *guid; + + /* set to TRUE exactly when initable_init() has finished running */ + gboolean is_initialized; + + /* If the connection could not be established during initable_init(), this GError will set */ + GError *initialization_error; + + /* The result of g_main_context_get_thread_default() when the object + * was created (the GObject _init() function) - this is used for delivery + * of the :closed GObject signal. + */ + GMainContext *main_context_at_construction; + + /* construct properties */ + gchar *address; + GDBusConnectionFlags flags; + + /* Map used for managing method replies */ + GHashTable *map_method_serial_to_send_message_data; /* guint32 -> SendMessageData* */ + + /* Maps used for managing signal subscription */ + GHashTable *map_rule_to_signal_data; /* gchar* -> SignalData */ + GHashTable *map_id_to_signal_data; /* guint -> SignalData */ + GHashTable *map_sender_to_signal_data_array; /* gchar* -> GPtrArray* of SignalData */ + + /* Maps used for managing exported objects and subtrees */ + GHashTable *map_object_path_to_eo; /* gchar* -> ExportedObject* */ + GHashTable *map_id_to_ei; /* guint -> ExportedInterface* */ + GHashTable *map_object_path_to_es; /* gchar* -> ExportedSubtree* */ + GHashTable *map_id_to_es; /* guint -> ExportedSubtree* */ + + /* Structure used for message filters */ + GPtrArray *filters; + + /* Whether to exit on close */ + gboolean exit_on_close; + + /* Capabilities negotiated during authentication */ + GDBusCapabilityFlags capabilities; + + GDBusAuthObserver *authentication_observer; + GCredentials *crendentials; +}; + +typedef struct ExportedObject ExportedObject; +static void exported_object_free (ExportedObject *eo); + +typedef struct ExportedSubtree ExportedSubtree; +static void exported_subtree_free (ExportedSubtree *es); + +enum +{ + CLOSED_SIGNAL, + LAST_SIGNAL, +}; + +enum +{ + PROP_0, + PROP_STREAM, + PROP_ADDRESS, + PROP_FLAGS, + PROP_GUID, + PROP_UNIQUE_NAME, + PROP_CLOSED, + PROP_EXIT_ON_CLOSE, + PROP_CAPABILITY_FLAGS, + PROP_AUTHENTICATION_OBSERVER, +}; + +static void distribute_signals (GDBusConnection *connection, + GDBusMessage *message); + +static void distribute_method_call (GDBusConnection *connection, + GDBusMessage *message); + +static gboolean handle_generic_unlocked (GDBusConnection *connection, + GDBusMessage *message); + + +static void purge_all_signal_subscriptions (GDBusConnection *connection); +static void purge_all_filters (GDBusConnection *connection); + +#define _G_ENSURE_LOCK(name) do { \ + if (G_UNLIKELY (G_TRYLOCK(name))) \ + { \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "_G_ENSURE_LOCK: Lock `" #name "' is not locked"); \ + } \ + } while (FALSE) \ + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void initable_iface_init (GInitableIface *initable_iface); +static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface); + +G_DEFINE_TYPE_WITH_CODE (GDBusConnection, g_dbus_connection, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init) + ); + +static void +g_dbus_connection_dispose (GObject *object) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (object); + + G_LOCK (message_bus_lock); + //g_debug ("disposing %p", connection); + if (connection == the_session_bus) + { + the_session_bus = NULL; + } + else if (connection == the_system_bus) + { + the_system_bus = NULL; + } + if (connection->priv->worker != NULL) + { + _g_dbus_worker_stop (connection->priv->worker); + connection->priv->worker = NULL; + } + G_UNLOCK (message_bus_lock); + + if (G_OBJECT_CLASS (g_dbus_connection_parent_class)->dispose != NULL) + G_OBJECT_CLASS (g_dbus_connection_parent_class)->dispose (object); +} + +static void +g_dbus_connection_finalize (GObject *object) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (object); + + if (connection->priv->authentication_observer != NULL) + g_object_unref (connection->priv->authentication_observer); + + if (connection->priv->auth != NULL) + g_object_unref (connection->priv->auth); + + //g_debug ("finalizing %p", connection); + if (connection->priv->stream != NULL) + { + /* We don't really care if closing the stream succeeds or not */ + g_io_stream_close_async (connection->priv->stream, + G_PRIORITY_DEFAULT, + NULL, /* GCancellable */ + NULL, /* GAsyncReadyCallback */ + NULL); /* userdata */ + g_object_unref (connection->priv->stream); + connection->priv->stream = NULL; + } + + g_free (connection->priv->address); + + g_free (connection->priv->guid); + g_free (connection->priv->bus_unique_name); + + if (connection->priv->initialization_error != NULL) + g_error_free (connection->priv->initialization_error); + + g_hash_table_unref (connection->priv->map_method_serial_to_send_message_data); + + purge_all_signal_subscriptions (connection); + g_hash_table_unref (connection->priv->map_rule_to_signal_data); + g_hash_table_unref (connection->priv->map_id_to_signal_data); + g_hash_table_unref (connection->priv->map_sender_to_signal_data_array); + + g_hash_table_unref (connection->priv->map_id_to_ei); + g_hash_table_unref (connection->priv->map_object_path_to_eo); + g_hash_table_unref (connection->priv->map_id_to_es); + g_hash_table_unref (connection->priv->map_object_path_to_es); + + purge_all_filters (connection); + g_ptr_array_unref (connection->priv->filters); + + if (connection->priv->main_context_at_construction != NULL) + g_main_context_unref (connection->priv->main_context_at_construction); + + g_free (connection->priv->machine_id); + + g_mutex_free (connection->priv->init_lock); + g_mutex_free (connection->priv->lock); + + G_OBJECT_CLASS (g_dbus_connection_parent_class)->finalize (object); +} + +static void +g_dbus_connection_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (object); + + switch (prop_id) + { + case PROP_STREAM: + g_value_set_object (value, g_dbus_connection_get_stream (connection)); + break; + + case PROP_GUID: + g_value_set_string (value, g_dbus_connection_get_guid (connection)); + break; + + case PROP_UNIQUE_NAME: + g_value_set_string (value, g_dbus_connection_get_unique_name (connection)); + break; + + case PROP_CLOSED: + g_value_set_boolean (value, g_dbus_connection_is_closed (connection)); + break; + + case PROP_EXIT_ON_CLOSE: + g_value_set_boolean (value, g_dbus_connection_get_exit_on_close (connection)); + break; + + case PROP_CAPABILITY_FLAGS: + g_value_set_flags (value, g_dbus_connection_get_capabilities (connection)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_dbus_connection_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (object); + + switch (prop_id) + { + case PROP_STREAM: + connection->priv->stream = g_value_dup_object (value); + break; + + case PROP_GUID: + connection->priv->guid = g_value_dup_string (value); + break; + + case PROP_ADDRESS: + connection->priv->address = g_value_dup_string (value); + break; + + case PROP_FLAGS: + connection->priv->flags = g_value_get_flags (value); + break; + + case PROP_EXIT_ON_CLOSE: + g_dbus_connection_set_exit_on_close (connection, g_value_get_boolean (value)); + break; + + case PROP_AUTHENTICATION_OBSERVER: + connection->priv->authentication_observer = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_dbus_connection_real_closed (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error) +{ + if (remote_peer_vanished && connection->priv->exit_on_close) + { + g_print ("%s: Remote peer vanished. Exiting.\n", G_STRFUNC); + raise (SIGTERM); + } +} + +static void +g_dbus_connection_class_init (GDBusConnectionClass *klass) +{ + GObjectClass *gobject_class; + + g_type_class_add_private (klass, sizeof (GDBusConnectionPrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_dbus_connection_finalize; + gobject_class->dispose = g_dbus_connection_dispose; + gobject_class->set_property = g_dbus_connection_set_property; + gobject_class->get_property = g_dbus_connection_get_property; + + klass->closed = g_dbus_connection_real_closed; + + /** + * GDBusConnection:stream: + * + * The underlying #GIOStream used for I/O. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_STREAM, + g_param_spec_object ("stream", + P_("IO Stream"), + P_("The underlying streams used for I/O"), + G_TYPE_IO_STREAM, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusConnection:address: + * + * A D-Bus address specifying potential endpoints that can be used + * when establishing the connection. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_ADDRESS, + g_param_spec_string ("address", + P_("Address"), + P_("D-Bus address specifying potential socket endpoints"), + NULL, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusConnection:flags: + * + * Flags from the #GDBusConnectionFlags enumeration. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_FLAGS, + g_param_spec_flags ("flags", + P_("Flags"), + P_("Flags"), + G_TYPE_DBUS_CONNECTION_FLAGS, + G_DBUS_CONNECTION_FLAGS_NONE, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusConnection:guid: + * + * The GUID of the peer performing the role of server when + * authenticating. + * + * If you are constructing a #GDBusConnection and pass + * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER in the + * #GDBusConnection:flags property then you MUST also set this + * property to a valid guid. + * + * If you are constructing a #GDBusConnection and pass + * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT in the + * #GDBusConnection:flags property you will be able to read the GUID + * of the other peer here after the connection has been successfully + * initialized. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_GUID, + g_param_spec_string ("guid", + P_("GUID"), + P_("GUID of the server peer"), + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusConnection:unique-name: + * + * The unique name as assigned by the message bus or %NULL if the + * connection is not open or not a message bus connection. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_UNIQUE_NAME, + g_param_spec_string ("unique-name", + P_("unique-name"), + P_("Unique name of bus connection"), + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusConnection:closed: + * + * A boolean specifying whether the connection has been closed. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_CLOSED, + g_param_spec_boolean ("closed", + P_("Closed"), + P_("Whether the connection is closed"), + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusConnection:exit-on-close: + * + * A boolean specifying whether the process will be terminated (by + * calling raise(SIGTERM)) if the connection + * is closed by the remote peer. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_EXIT_ON_CLOSE, + g_param_spec_boolean ("exit-on-close", + P_("Exit on close"), + P_("Whether the process is terminated when the connection is closed"), + FALSE, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusConnection:capabilities: + * + * Flags from the #GDBusCapabilityFlags enumeration + * representing connection features negotiated with the other peer. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_CAPABILITY_FLAGS, + g_param_spec_flags ("capabilities", + P_("Capabilities"), + P_("Capabilities"), + G_TYPE_DBUS_CAPABILITY_FLAGS, + G_DBUS_CAPABILITY_FLAGS_NONE, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusConnection:authentication-observer: + * + * A #GDBusAuthObserver object to assist in the authentication process or %NULL. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_AUTHENTICATION_OBSERVER, + g_param_spec_object ("authentication-observer", + P_("Authentication Observer"), + P_("Object used to assist in the authentication process"), + G_TYPE_DBUS_AUTH_OBSERVER, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusConnection::closed: + * @connection: The #GDBusConnection emitting the signal. + * @remote_peer_vanished: %TRUE if @connection is closed because the + * remote peer closed its end of the connection. + * @error: A #GError with more details about the event or %NULL. + * + * Emitted when the connection is closed. + * + * The cause of this event can be + * + * + * If g_dbus_connection_close() is called. In this case + * @remote_peer_vanished is set to %FALSE and @error is %NULL. + * + * + * If the remote peer closes the connection. In this case + * @remote_peer_vanished is set to %TRUE and @error is set. + * + * + * If the remote peer sends invalid or malformed data. In this + * case @remote_peer_vanished is set to %FALSE and @error + * is set. + * + * + * + * Upon receiving this signal, you should give up your reference to + * @connection. You are guaranteed that this signal is emitted only + * once. + * + * Since: 2.26 + */ + signals[CLOSED_SIGNAL] = g_signal_new ("closed", + G_TYPE_DBUS_CONNECTION, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GDBusConnectionClass, closed), + NULL, + NULL, + _gio_marshal_VOID__BOOLEAN_BOXED, + G_TYPE_NONE, + 2, + G_TYPE_BOOLEAN, + G_TYPE_ERROR); +} + +static void +g_dbus_connection_init (GDBusConnection *connection) +{ + connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (connection, G_TYPE_DBUS_CONNECTION, GDBusConnectionPrivate); + + connection->priv->lock = g_mutex_new (); + connection->priv->init_lock = g_mutex_new (); + + connection->priv->map_method_serial_to_send_message_data = g_hash_table_new (g_direct_hash, g_direct_equal); + + connection->priv->map_rule_to_signal_data = g_hash_table_new (g_str_hash, + g_str_equal); + connection->priv->map_id_to_signal_data = g_hash_table_new (g_direct_hash, + g_direct_equal); + connection->priv->map_sender_to_signal_data_array = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + + connection->priv->map_object_path_to_eo = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) exported_object_free); + + connection->priv->map_id_to_ei = g_hash_table_new (g_direct_hash, + g_direct_equal); + + connection->priv->map_object_path_to_es = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) exported_subtree_free); + + connection->priv->map_id_to_es = g_hash_table_new (g_direct_hash, + g_direct_equal); + + connection->priv->main_context_at_construction = g_main_context_get_thread_default (); + if (connection->priv->main_context_at_construction != NULL) + g_main_context_ref (connection->priv->main_context_at_construction); + + connection->priv->filters = g_ptr_array_new (); +} + +/** + * g_dbus_connection_get_stream: + * @connection: a #GDBusConnection + * + * Gets the underlying stream used for IO. + * + * Returns: the stream used for IO + * + * Since: 2.26 + */ +GIOStream * +g_dbus_connection_get_stream (GDBusConnection *connection) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + return connection->priv->stream; +} + + +/** + * g_dbus_connection_is_closed: + * @connection: A #GDBusConnection. + * + * Gets whether @connection is closed. + * + * Returns: %TRUE if the connection is closed, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_connection_is_closed (GDBusConnection *connection) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); + return connection->priv->closed; +} + +/** + * g_dbus_connection_get_capabilities: + * @connection: A #GDBusConnection. + * + * Gets the capabilities negotiated with the remote peer + * + * Returns: Zero or more flags from the #GDBusCapabilityFlags enumeration. + * + * Since: 2.26 + */ +GDBusCapabilityFlags +g_dbus_connection_get_capabilities (GDBusConnection *connection) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), G_DBUS_CAPABILITY_FLAGS_NONE); + return connection->priv->capabilities; +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GDBusConnection *connection; + GError *error; + gboolean remote_peer_vanished; +} EmitClosedData; + +static void +emit_closed_data_free (EmitClosedData *data) +{ + g_object_unref (data->connection); + if (data->error != NULL) + g_error_free (data->error); + g_free (data); +} + +static gboolean +emit_closed_in_idle (gpointer user_data) +{ + EmitClosedData *data = user_data; + gboolean result; + + g_object_notify (G_OBJECT (data->connection), "closed"); + g_signal_emit (data->connection, + signals[CLOSED_SIGNAL], + 0, + data->remote_peer_vanished, + data->error, + &result); + return FALSE; +} + +/* Can be called from any thread, must hold lock */ +static void +set_closed_unlocked (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error) +{ + GSource *idle_source; + EmitClosedData *data; + + CONNECTION_ENSURE_LOCK (connection); + + g_assert (!connection->priv->closed); + + connection->priv->closed = TRUE; + + data = g_new0 (EmitClosedData, 1); + data->connection = g_object_ref (connection); + data->remote_peer_vanished = remote_peer_vanished; + data->error = error != NULL ? g_error_copy (error) : NULL; + + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); + g_source_set_callback (idle_source, + emit_closed_in_idle, + data, + (GDestroyNotify) emit_closed_data_free); + g_source_attach (idle_source, connection->priv->main_context_at_construction); + g_source_unref (idle_source); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_connection_close: + * @connection: A #GDBusConnection. + * + * Closes @connection. Note that this never causes the process to + * exit (this might only happen if the other end of a shared message + * bus connection disconnects). + * + * If @connection is already closed, this method does nothing. + * + * Since: 2.26 + */ +void +g_dbus_connection_close (GDBusConnection *connection) +{ + g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); + + CONNECTION_LOCK (connection); + if (!connection->priv->closed) + { + GError *error = NULL; + + /* TODO: do this async */ + //g_debug ("closing connection %p's stream %p", connection, connection->priv->stream); + if (!g_io_stream_close (connection->priv->stream, NULL, &error)) + { + g_warning ("Error closing stream: %s", error->message); + g_error_free (error); + } + + set_closed_unlocked (connection, FALSE, NULL); + } + CONNECTION_UNLOCK (connection); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +g_dbus_connection_send_message_unlocked (GDBusConnection *connection, + GDBusMessage *message, + volatile guint32 *out_serial, + GError **error) +{ + guchar *blob; + gsize blob_size; + guint32 serial_to_use; + gboolean ret; + + CONNECTION_ENSURE_LOCK (connection); + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE); + + /* TODO: check all necessary headers are present */ + + ret = FALSE; + blob = NULL; + + if (out_serial != NULL) + *out_serial = 0; + + if (connection->priv->closed) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_CLOSED, + _("The connection is closed")); + goto out; + } + + blob = g_dbus_message_to_blob (message, + &blob_size, + connection->priv->capabilities, + error); + if (blob == NULL) + goto out; + + serial_to_use = ++connection->priv->last_serial; /* TODO: handle overflow */ + + switch (blob[0]) + { + case 'l': + ((guint32 *) blob)[2] = GUINT32_TO_LE (serial_to_use); + break; + case 'B': + ((guint32 *) blob)[2] = GUINT32_TO_BE (serial_to_use); + break; + default: + g_assert_not_reached (); + break; + } + +#if 0 + g_printerr ("Writing message of %" G_GSIZE_FORMAT " bytes (serial %d) on %p:\n", + blob_size, serial_to_use, connection); + g_printerr ("----\n"); + hexdump (blob, blob_size); + g_printerr ("----\n"); +#endif + + /* TODO: use connection->priv->auth to encode the blob */ + + if (out_serial != NULL) + *out_serial = serial_to_use; + + g_dbus_message_set_serial (message, serial_to_use); + + _g_dbus_worker_send_message (connection->priv->worker, + message, + (gchar*) blob, + blob_size); + blob = NULL; /* since _g_dbus_worker_send_message() steals the blob */ + + ret = TRUE; + + out: + g_free (blob); + + return ret; +} + +/** + * g_dbus_connection_send_message: + * @connection: A #GDBusConnection. + * @message: A #GDBusMessage + * @out_serial: Return location for serial number assigned to @message when sending it or %NULL. + * @error: Return location for error or %NULL. + * + * Asynchronously sends @message to the peer represented by @connection. + * + * If @out_serial is not %NULL, then the serial number assigned to + * @message by @connection will be written to this location prior to + * submitting the message to the underlying transport. + * + * If @connection is closed then the operation will fail with + * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will + * fail with %G_IO_ERROR_CANCELLED. If @message is not well-formed, + * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT. + * + * See and for an example of how to use this + * low-level API to send and receive UNIX file descriptors. + * + * Returns: %TRUE if the message was well-formed and queued for + * transmission, %FALSE if @error is set. + * + * Since: 2.26 + */ +gboolean +g_dbus_connection_send_message (GDBusConnection *connection, + GDBusMessage *message, + volatile guint32 *out_serial, + GError **error) +{ + gboolean ret; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + CONNECTION_LOCK (connection); + ret = g_dbus_connection_send_message_unlocked (connection, message, out_serial, error); + CONNECTION_UNLOCK (connection); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + volatile gint ref_count; + GDBusConnection *connection; + guint32 serial; + GSimpleAsyncResult *simple; + + GMainContext *main_context; + + GCancellable *cancellable; + + gulong cancellable_handler_id; + + GSource *timeout_source; + + gboolean delivered; +} SendMessageData; + +static SendMessageData * +send_message_data_ref (SendMessageData *data) +{ + g_atomic_int_inc (&data->ref_count); + return data; +} + +static void +send_message_data_unref (SendMessageData *data) +{ + if (g_atomic_int_dec_and_test (&data->ref_count)) + { + g_assert (data->timeout_source == NULL); + g_assert (data->simple == NULL); + g_assert (data->cancellable_handler_id == 0); + g_object_unref (data->connection); + if (data->cancellable != NULL) + g_object_unref (data->cancellable); + if (data->main_context != NULL) + g_main_context_unref (data->main_context); + g_free (data); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* can be called from any thread with lock held - caller must have prepared GSimpleAsyncResult already */ +static void +send_message_with_reply_deliver (SendMessageData *data) +{ + CONNECTION_ENSURE_LOCK (data->connection); + + g_assert (!data->delivered); + + data->delivered = TRUE; + + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + data->simple = NULL; + + if (data->timeout_source != NULL) + { + g_source_destroy (data->timeout_source); + data->timeout_source = NULL; + } + if (data->cancellable_handler_id > 0) + { + g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); + data->cancellable_handler_id = 0; + } + + g_warn_if_fail (g_hash_table_remove (data->connection->priv->map_method_serial_to_send_message_data, + GUINT_TO_POINTER (data->serial))); + + send_message_data_unref (data); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* must hold lock */ +static void +send_message_data_deliver_reply_unlocked (SendMessageData *data, + GDBusMessage *reply) +{ + if (data->delivered) + goto out; + + g_simple_async_result_set_op_res_gpointer (data->simple, + g_object_ref (reply), + g_object_unref); + + send_message_with_reply_deliver (data); + + out: + ; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +send_message_with_reply_cancelled_idle_cb (gpointer user_data) +{ + SendMessageData *data = user_data; + + CONNECTION_LOCK (data->connection); + if (data->delivered) + goto out; + + g_simple_async_result_set_error (data->simple, + G_IO_ERROR, + G_IO_ERROR_CANCELLED, + _("Operation was cancelled")); + + send_message_with_reply_deliver (data); + + out: + CONNECTION_UNLOCK (data->connection); + return FALSE; +} + +/* Can be called from any thread with or without lock held */ +static void +send_message_with_reply_cancelled_cb (GCancellable *cancellable, + gpointer user_data) +{ + SendMessageData *data = user_data; + GSource *idle_source; + + /* postpone cancellation to idle handler since we may be called directly + * via g_cancellable_connect() (e.g. holding lock) + */ + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); + g_source_set_callback (idle_source, + send_message_with_reply_cancelled_idle_cb, + send_message_data_ref (data), + (GDestroyNotify) send_message_data_unref); + g_source_attach (idle_source, data->main_context); + g_source_unref (idle_source); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +send_message_with_reply_timeout_cb (gpointer user_data) +{ + SendMessageData *data = user_data; + + CONNECTION_LOCK (data->connection); + if (data->delivered) + goto out; + + g_simple_async_result_set_error (data->simple, + G_IO_ERROR, + G_IO_ERROR_TIMED_OUT, + _("Timeout was reached")); + + send_message_with_reply_deliver (data); + + out: + CONNECTION_UNLOCK (data->connection); + + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +g_dbus_connection_send_message_with_reply_unlocked (GDBusConnection *connection, + GDBusMessage *message, + gint timeout_msec, + volatile guint32 *out_serial, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + SendMessageData *data; + GError *error; + volatile guint32 serial; + + data = NULL; + + if (out_serial == NULL) + out_serial = &serial; + + if (timeout_msec == -1) + timeout_msec = 25 * 1000; + + simple = g_simple_async_result_new (G_OBJECT (connection), + callback, + user_data, + g_dbus_connection_send_message_with_reply); + + if (g_cancellable_is_cancelled (cancellable)) + { + g_simple_async_result_set_error (simple, + G_IO_ERROR, + G_IO_ERROR_CANCELLED, + _("Operation was cancelled")); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + goto out; + } + + if (connection->priv->closed) + { + g_simple_async_result_set_error (simple, + G_IO_ERROR, + G_IO_ERROR_CLOSED, + _("The connection is closed")); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + goto out; + } + + error = NULL; + if (!g_dbus_connection_send_message_unlocked (connection, message, out_serial, &error)) + { + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + goto out; + } + + data = g_new0 (SendMessageData, 1); + data->ref_count = 1; + data->connection = g_object_ref (connection); + data->simple = simple; + data->serial = *out_serial; + data->main_context = g_main_context_get_thread_default (); + if (data->main_context != NULL) + g_main_context_ref (data->main_context); + + if (cancellable != NULL) + { + data->cancellable = g_object_ref (cancellable); + data->cancellable_handler_id = g_cancellable_connect (cancellable, + G_CALLBACK (send_message_with_reply_cancelled_cb), + send_message_data_ref (data), + (GDestroyNotify) send_message_data_unref); + g_object_set_data_full (G_OBJECT (simple), + "cancellable", + g_object_ref (cancellable), + (GDestroyNotify) g_object_unref); + } + + data->timeout_source = g_timeout_source_new (timeout_msec); + g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT); + g_source_set_callback (data->timeout_source, + send_message_with_reply_timeout_cb, + send_message_data_ref (data), + (GDestroyNotify) send_message_data_unref); + g_source_attach (data->timeout_source, data->main_context); + g_source_unref (data->timeout_source); + + g_hash_table_insert (connection->priv->map_method_serial_to_send_message_data, + GUINT_TO_POINTER (*out_serial), + data); + + out: + ; +} + +/** + * g_dbus_connection_send_message_with_reply: + * @connection: A #GDBusConnection. + * @message: A #GDBusMessage. + * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout. + * @out_serial: Return location for serial number assigned to @message when sending it or %NULL. + * @cancellable: A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't + * care about the result. + * @user_data: The data to pass to @callback. + * + * Asynchronously sends @message to the peer represented by @connection. + * + * If @out_serial is not %NULL, then the serial number assigned to + * @message by @connection will be written to this location prior to + * submitting the message to the underlying transport. + * + * If @connection is closed then the operation will fail with + * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will + * fail with %G_IO_ERROR_CANCELLED. If @message is not well-formed, + * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT. + * + * This is an asynchronous method. When the operation is finished, @callback will be invoked + * in the thread-default main loop + * of the thread you are calling this method from. You can then call + * g_dbus_connection_send_message_with_reply_finish() to get the result of the operation. + * See g_dbus_connection_send_message_with_reply_sync() for the synchronous version. + * + * See and for an example of how to use this + * low-level API to send and receive UNIX file descriptors. + * + * Since: 2.26 + */ +void +g_dbus_connection_send_message_with_reply (GDBusConnection *connection, + GDBusMessage *message, + gint timeout_msec, + volatile guint32 *out_serial, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (timeout_msec >= 0 || timeout_msec == -1); + + CONNECTION_LOCK (connection); + g_dbus_connection_send_message_with_reply_unlocked (connection, + message, + timeout_msec, + out_serial, + cancellable, + callback, + user_data); + CONNECTION_UNLOCK (connection); +} + +/** + * g_dbus_connection_send_message_with_reply_finish: + * @connection: a #GDBusConnection + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_send_message_with_reply(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with g_dbus_connection_send_message_with_reply(). + * + * Note that @error is only set if a local in-process error + * occured. That is to say that the returned #GDBusMessage object may + * be of type %G_DBUS_MESSAGE_TYPE_ERROR. Use + * g_dbus_message_to_gerror() to transcode this to a #GError. + * + * See and for an example of how to use this + * low-level API to send and receive UNIX file descriptors. + * + * Returns: A #GDBusMessage or %NULL if @error is set. + * + * Since: 2.26 + */ +GDBusMessage * +g_dbus_connection_send_message_with_reply_finish (GDBusConnection *connection, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + GDBusMessage *reply; + GCancellable *cancellable; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + reply = NULL; + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_dbus_connection_send_message_with_reply); + + if (g_simple_async_result_propagate_error (simple, error)) + goto out; + + reply = g_object_ref (g_simple_async_result_get_op_res_gpointer (simple)); + cancellable = g_object_get_data (G_OBJECT (simple), "cancellable"); + if (cancellable != NULL && g_cancellable_is_cancelled (cancellable)) + { + g_object_unref (reply); + reply = NULL; + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_CANCELLED, + _("Operation was cancelled")); + } + out: + return reply; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GAsyncResult *res; + GMainContext *context; + GMainLoop *loop; +} SendMessageSyncData; + +static void +send_message_with_reply_sync_cb (GDBusConnection *connection, + GAsyncResult *res, + gpointer user_data) +{ + SendMessageSyncData *data = user_data; + data->res = g_object_ref (res); + g_main_loop_quit (data->loop); +} + +/** + * g_dbus_connection_send_message_with_reply_sync: + * @connection: A #GDBusConnection. + * @message: A #GDBusMessage. + * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout. + * @out_serial: Return location for serial number assigned to @message when sending it or %NULL. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously sends @message to the peer represented by @connection + * and blocks the calling thread until a reply is received or the + * timeout is reached. See g_dbus_connection_send_message_with_reply() + * for the asynchronous version of this method. + * + * If @out_serial is not %NULL, then the serial number assigned to + * @message by @connection will be written to this location prior to + * submitting the message to the underlying transport. + * + * If @connection is closed then the operation will fail with + * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will + * fail with %G_IO_ERROR_CANCELLED. If @message is not well-formed, + * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT. + * + * Note that @error is only set if a local in-process error + * occured. That is to say that the returned #GDBusMessage object may + * be of type %G_DBUS_MESSAGE_TYPE_ERROR. Use + * g_dbus_message_to_gerror() to transcode this to a #GError. + * + * See and for an example of how to use this + * low-level API to send and receive UNIX file descriptors. + * + * Returns: A #GDBusMessage that is the reply to @message or %NULL if @error is set. + * + * Since: 2.26 + */ +GDBusMessage * +g_dbus_connection_send_message_with_reply_sync (GDBusConnection *connection, + GDBusMessage *message, + gint timeout_msec, + volatile guint32 *out_serial, + GCancellable *cancellable, + GError **error) +{ + SendMessageSyncData *data; + GDBusMessage *reply; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + g_return_val_if_fail (timeout_msec >= 0 || timeout_msec == -1, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + data = g_new0 (SendMessageSyncData, 1); + data->context = g_main_context_new (); + data->loop = g_main_loop_new (data->context, FALSE); + + g_main_context_push_thread_default (data->context); + + g_dbus_connection_send_message_with_reply (connection, + message, + timeout_msec, + out_serial, + cancellable, + (GAsyncReadyCallback) send_message_with_reply_sync_cb, + data); + g_main_loop_run (data->loop); + reply = g_dbus_connection_send_message_with_reply_finish (connection, + data->res, + error); + + g_main_context_pop_thread_default (data->context); + + g_main_context_unref (data->context); + g_main_loop_unref (data->loop); + g_object_unref (data->res); + g_free (data); + + return reply; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GDBusMessageFilterFunction func; + gpointer user_data; +} FilterCallback; + +typedef struct +{ + guint id; + GDBusMessageFilterFunction filter_function; + gpointer user_data; + GDestroyNotify user_data_free_func; +} FilterData; + +/* Called in worker's thread - we must not block */ +static void +on_worker_message_received (GDBusWorker *worker, + GDBusMessage *message, + gpointer user_data) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (user_data); + FilterCallback *filters; + gboolean consumed_by_filter; + guint num_filters; + guint n; + + //g_debug ("in on_worker_message_received"); + + g_object_ref (connection); + + /* First collect the set of callback functions */ + CONNECTION_LOCK (connection); + num_filters = connection->priv->filters->len; + filters = g_new0 (FilterCallback, num_filters); + for (n = 0; n < num_filters; n++) + { + FilterData *data = connection->priv->filters->pdata[n]; + filters[n].func = data->filter_function; + filters[n].user_data = data->user_data; + } + CONNECTION_UNLOCK (connection); + + /* the call the filters in order (without holding the lock) */ + consumed_by_filter = FALSE; + for (n = 0; n < num_filters; n++) + { + consumed_by_filter = filters[n].func (connection, + message, + filters[n].user_data); + if (consumed_by_filter) + break; + } + + /* Standard dispatch unless the filter ate the message */ + if (!consumed_by_filter) + { + GDBusMessageType message_type; + + message_type = g_dbus_message_get_message_type (message); + if (message_type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN || message_type == G_DBUS_MESSAGE_TYPE_ERROR) + { + guint32 reply_serial; + SendMessageData *send_message_data; + + reply_serial = g_dbus_message_get_reply_serial (message); + CONNECTION_LOCK (connection); + send_message_data = g_hash_table_lookup (connection->priv->map_method_serial_to_send_message_data, + GUINT_TO_POINTER (reply_serial)); + if (send_message_data != NULL) + { + //g_debug ("delivering reply/error for serial %d for %p", reply_serial, connection); + send_message_data_deliver_reply_unlocked (send_message_data, message); + } + else + { + //g_debug ("message reply/error for serial %d but no SendMessageData found for %p", reply_serial, connection); + } + CONNECTION_UNLOCK (connection); + } + else if (message_type == G_DBUS_MESSAGE_TYPE_SIGNAL) + { + CONNECTION_LOCK (connection); + distribute_signals (connection, message); + CONNECTION_UNLOCK (connection); + } + else if (message_type == G_DBUS_MESSAGE_TYPE_METHOD_CALL) + { + CONNECTION_LOCK (connection); + distribute_method_call (connection, message); + CONNECTION_UNLOCK (connection); + } + } + + g_object_unref (connection); + g_free (filters); +} + +/* Called in worker's thread - we must not block */ +static void +on_worker_closed (GDBusWorker *worker, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (user_data); + + //g_debug ("in on_worker_closed: %s", error->message); + + CONNECTION_LOCK (connection); + if (!connection->priv->closed) + set_closed_unlocked (connection, remote_peer_vanished, error); + CONNECTION_UNLOCK (connection); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* Determines the biggest set of capabilities we can support on this connection */ +static GDBusCapabilityFlags +get_offered_capabilities_max (GDBusConnection *connection) +{ + GDBusCapabilityFlags ret; + ret = G_DBUS_CAPABILITY_FLAGS_NONE; +#ifdef G_OS_UNIX + if (G_IS_UNIX_CONNECTION (connection->priv->stream)) + ret |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING; +#endif + return ret; +} + +static gboolean +initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (initable); + gboolean ret; + + /* This method needs to be idempotent to work with the singleton + * pattern. See the docs for g_initable_init(). We implement this by + * locking. + * + * Unfortunately we can't use the main lock since the on_worker_*() + * callbacks above needs the lock during initialization (for message + * bus connections we do a synchronous Hello() call on the bus). + */ + g_mutex_lock (connection->priv->init_lock); + + ret = FALSE; + + if (connection->priv->is_initialized) + { + if (connection->priv->stream != NULL) + ret = TRUE; + else + g_assert (connection->priv->initialization_error != NULL); + goto out; + } + g_assert (connection->priv->initialization_error == NULL); + + /* The user can pass multiple (but mutally exclusive) construct + * properties: + * + * - stream (of type GIOStream) + * - address (of type gchar*) + * + * At the end of the day we end up with a non-NULL GIOStream + * object in connection->priv->stream. + */ + if (connection->priv->address != NULL) + { + g_assert (connection->priv->stream == NULL); + + if ((connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER) || + (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS)) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Unsupported flags encountered when constructing a client-side connection")); + goto out; + } + + connection->priv->stream = g_dbus_address_get_stream_sync (connection->priv->address, + NULL, /* TODO: out_guid */ + cancellable, + &connection->priv->initialization_error); + if (connection->priv->stream == NULL) + goto out; + } + else if (connection->priv->stream != NULL) + { + /* nothing to do */ + } + else + { + g_assert_not_reached (); + } + + /* Authenticate the connection */ + if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER) + { + g_assert (!(connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT)); + g_assert (connection->priv->guid != NULL); + connection->priv->auth = _g_dbus_auth_new (connection->priv->stream); + if (!_g_dbus_auth_run_server (connection->priv->auth, + connection->priv->authentication_observer, + connection->priv->guid, + (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS), + get_offered_capabilities_max (connection), + &connection->priv->capabilities, + &connection->priv->crendentials, + cancellable, + &connection->priv->initialization_error)) + goto out; + } + else if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT) + { + g_assert (!(connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER)); + g_assert (connection->priv->guid == NULL); + connection->priv->auth = _g_dbus_auth_new (connection->priv->stream); + connection->priv->guid = _g_dbus_auth_run_client (connection->priv->auth, + get_offered_capabilities_max (connection), + &connection->priv->capabilities, + cancellable, + &connection->priv->initialization_error); + if (connection->priv->guid == NULL) + goto out; + } + + if (connection->priv->authentication_observer != NULL) + { + g_object_unref (connection->priv->authentication_observer); + connection->priv->authentication_observer = NULL; + } + + //g_output_stream_flush (G_SOCKET_CONNECTION (connection->priv->stream) + + //g_debug ("haz unix fd passing powers: %d", connection->priv->capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING); + + /* Hack used until + * + * https://bugzilla.gnome.org/show_bug.cgi?id=616458 + * + * has been resolved + */ + if (G_IS_SOCKET_CONNECTION (connection->priv->stream)) + { + g_socket_set_blocking (g_socket_connection_get_socket (G_SOCKET_CONNECTION (connection->priv->stream)), FALSE); + } + + connection->priv->worker = _g_dbus_worker_new (connection->priv->stream, + connection->priv->capabilities, + on_worker_message_received, + on_worker_closed, + connection); + + /* if a bus connection, invoke org.freedesktop.DBus.Hello - this is how we're getting a name */ + if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION) + { + GVariant *hello_result; + + hello_result = g_dbus_connection_call_sync (connection, + "org.freedesktop.DBus", /* name */ + "/org/freedesktop/DBus", /* path */ + "org.freedesktop.DBus", /* interface */ + "Hello", + NULL, /* parameters */ + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, /* TODO: cancellable */ + &connection->priv->initialization_error); + if (hello_result == NULL) + goto out; + + g_variant_get (hello_result, "(s)", &connection->priv->bus_unique_name); + g_variant_unref (hello_result); + //g_debug ("unique name is `%s'", connection->priv->bus_unique_name); + } + + connection->priv->is_initialized = TRUE; + + ret = TRUE; + out: + if (!ret) + { + g_assert (connection->priv->initialization_error != NULL); + g_propagate_error (error, g_error_copy (connection->priv->initialization_error)); + } + + g_mutex_unlock (connection->priv->init_lock); + + return ret; +} + +static void +initable_iface_init (GInitableIface *initable_iface) +{ + initable_iface->init = initable_init; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +async_init_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GError *error = NULL; + + if (!g_initable_init (G_INITABLE (object), cancellable, &error)) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } +} + +static void +async_initable_init_async (GAsyncInitable *initable, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + + g_return_if_fail (G_IS_INITABLE (initable)); + + res = g_simple_async_result_new (G_OBJECT (initable), callback, user_data, + async_initable_init_async); + g_simple_async_result_run_in_thread (res, async_init_thread, + io_priority, cancellable); + g_object_unref (res); +} + +static gboolean +async_initable_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error) +{ + return TRUE; /* Errors handled by base impl */ +} + +static void +async_initable_iface_init (GAsyncInitableIface *async_initable_iface) +{ + /* We basically just want to use GIO's default implementation - though that one is + * unfortunately broken, see #615111. So we copy-paste a fixed-up version. + */ + async_initable_iface->init_async = async_initable_init_async; + async_initable_iface->init_finish = async_initable_init_finish; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_connection_new: + * @stream: A #GIOStream. + * @guid: The GUID to use if a authenticating as a server or %NULL. + * @flags: Flags describing how to make the connection. + * @observer: A #GDBusAuthObserver or %NULL. + * @cancellable: A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied. + * @user_data: The data to pass to @callback. + * + * Asynchronously sets up a D-Bus connection for exchanging D-Bus messages + * with the end represented by @stream. + * + * If @observer is not %NULL it may be used to control the + * authentication process. + * + * When the operation is finished, @callback will be invoked. You can + * then call g_dbus_connection_new_finish() to get the result of the + * operation. + * + * This is a asynchronous failable constructor. See + * g_dbus_connection_new_sync() for the synchronous + * version. + * + * Since: 2.26 + */ +void +g_dbus_connection_new (GIOStream *stream, + const gchar *guid, + GDBusConnectionFlags flags, + GDBusAuthObserver *observer, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (G_IS_IO_STREAM (stream)); + g_async_initable_new_async (G_TYPE_DBUS_CONNECTION, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + "stream", stream, + "guid", guid, + "flags", flags, + "authentication-observer", observer, + NULL); +} + +/** + * g_dbus_connection_new_finish: + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_new(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with g_dbus_connection_new(). + * + * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusConnection * +g_dbus_connection_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *object; + GObject *source_object; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + source_object = g_async_result_get_source_object (res); + g_assert (source_object != NULL); + object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), + res, + error); + g_object_unref (source_object); + if (object != NULL) + return G_DBUS_CONNECTION (object); + else + return NULL; +} + +/** + * g_dbus_connection_new_sync: + * @stream: A #GIOStream. + * @guid: The GUID to use if a authenticating as a server or %NULL. + * @flags: Flags describing how to make the connection. + * @observer: A #GDBusAuthObserver or %NULL. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously sets up a D-Bus connection for exchanging D-Bus messages + * with the end represented by @stream. + * + * If @observer is not %NULL it may be used to control the + * authentication process. + * + * This is a synchronous failable constructor. See + * g_dbus_connection_new() for the asynchronous version. + * + * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusConnection * +g_dbus_connection_new_sync (GIOStream *stream, + const gchar *guid, + GDBusConnectionFlags flags, + GDBusAuthObserver *observer, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + return g_initable_new (G_TYPE_DBUS_CONNECTION, + cancellable, + error, + "stream", stream, + "guid", guid, + "flags", flags, + "authentication-observer", observer, + NULL); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_connection_new_for_address: + * @address: A D-Bus address. + * @flags: Flags describing how to make the connection. + * @observer: A #GDBusAuthObserver or %NULL. + * @cancellable: A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied. + * @user_data: The data to pass to @callback. + * + * Asynchronously connects and sets up a D-Bus client connection for + * exchanging D-Bus messages with an endpoint specified by @address + * which must be in the D-Bus address format. + * + * This constructor can only be used to initiate client-side + * connections - use g_dbus_connection_new() if you need to act as the + * server. In particular, @flags cannot contain the + * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER or + * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS flags. + * + * When the operation is finished, @callback will be invoked. You can + * then call g_dbus_connection_new_finish() to get the result of the + * operation. + * + * If @observer is not %NULL it may be used to control the + * authentication process. + * + * This is a asynchronous failable constructor. See + * g_dbus_connection_new_for_address_sync() for the synchronous + * version. + * + * Since: 2.26 + */ +void +g_dbus_connection_new_for_address (const gchar *address, + GDBusConnectionFlags flags, + GDBusAuthObserver *observer, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (address != NULL); + g_async_initable_new_async (G_TYPE_DBUS_CONNECTION, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + "address", address, + "flags", flags, + "authentication-observer", observer, + NULL); +} + +/** + * g_dbus_connection_new_for_address_finish: + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_new(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with g_dbus_connection_new_for_address(). + * + * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusConnection * +g_dbus_connection_new_for_address_finish (GAsyncResult *res, + GError **error) +{ + GObject *object; + GObject *source_object; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + source_object = g_async_result_get_source_object (res); + g_assert (source_object != NULL); + object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), + res, + error); + g_object_unref (source_object); + if (object != NULL) + return G_DBUS_CONNECTION (object); + else + return NULL; +} + +/** + * g_dbus_connection_new_for_address_sync: + * @address: A D-Bus address. + * @flags: Flags describing how to make the connection. + * @observer: A #GDBusAuthObserver or %NULL. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously connects and sets up a D-Bus client connection for + * exchanging D-Bus messages with an endpoint specified by @address + * which must be in the D-Bus address format. + * + * This constructor can only be used to initiate client-side + * connections - use g_dbus_connection_new_sync() if you need to act + * as the server. In particular, @flags cannot contain the + * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER or + * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS flags. + * + * This is a synchronous failable constructor. See + * g_dbus_connection_new_for_address() for the asynchronous version. + * + * If @observer is not %NULL it may be used to control the + * authentication process. + * + * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusConnection * +g_dbus_connection_new_for_address_sync (const gchar *address, + GDBusConnectionFlags flags, + GDBusAuthObserver *observer, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (address != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + return g_initable_new (G_TYPE_DBUS_CONNECTION, + cancellable, + error, + "address", address, + "flags", flags, + "authentication-observer", observer, + NULL); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_connection_set_exit_on_close: + * @connection: A #GDBusConnection. + * @exit_on_close: Whether the process should be terminated + * when @connection is closed by the remote peer. + * + * Sets whether the process should be terminated when @connection is + * closed by the remote peer. See #GDBusConnection:exit-on-close for + * more details. + * + * Since: 2.26 + */ +void +g_dbus_connection_set_exit_on_close (GDBusConnection *connection, + gboolean exit_on_close) +{ + g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); + connection->priv->exit_on_close = exit_on_close; +} + +/** + * g_dbus_connection_get_exit_on_close: + * @connection: A #GDBusConnection. + * + * Gets whether the process is terminated when @connection is + * closed by the remote peer. See + * #GDBusConnection:exit-on-close for more details. + * + * Returns: Whether the process is terminated when @connection is + * closed by the remote peer. + * + * Since: 2.26 + */ +gboolean +g_dbus_connection_get_exit_on_close (GDBusConnection *connection) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); + return connection->priv->exit_on_close; +} + +/** + * g_dbus_connection_get_guid: + * @connection: A #GDBusConnection. + * + * The GUID of the peer performing the role of server when + * authenticating. See #GDBusConnection:guid for more details. + * + * Returns: The GUID. Do not free this string, it is owned by + * @connection. + * + * Since: 2.26 + */ +const gchar * +g_dbus_connection_get_guid (GDBusConnection *connection) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + return connection->priv->guid; +} + +/** + * g_dbus_connection_get_unique_name: + * @connection: A #GDBusConnection. + * + * Gets the unique name of @connection as assigned by the message + * bus. This can also be used to figure out if @connection is a + * message bus connection. + * + * Returns: The unique name or %NULL if @connection is not a message + * bus connection. Do not free this string, it is owned by + * @connection. + * + * Since: 2.26 + */ +const gchar * +g_dbus_connection_get_unique_name (GDBusConnection *connection) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + return connection->priv->bus_unique_name; +} + +/** + * g_dbus_connection_get_peer_credentials: + * @connection: A #GDBusConnection. + * + * Gets the credentials of the authenticated peer. This will always + * return %NULL unless @connection acted as a server + * (e.g. %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER was passed) + * when set up and the client passed credentials as part of the + * authentication process. + * + * In a message bus setup, the message bus is always the server and + * each application is a client. So this method will always return + * %NULL for message bus clients. + * + * Returns: A #GCredentials or %NULL if not available. Do not free + * this object, it is owned by @connection. + * + * Since: 2.26 + */ +GCredentials * +g_dbus_connection_get_peer_credentials (GDBusConnection *connection) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + return connection->priv->crendentials; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static guint _global_filter_id = 1; + +/** + * g_dbus_connection_add_filter: + * @connection: A #GDBusConnection. + * @filter_function: A filter function. + * @user_data: User data to pass to @filter_function. + * @user_data_free_func: Function to free @user_data with when filter + * is removed or %NULL. + * + * Adds a message filter. Filters are handlers that are run on all + * incoming messages, prior to standard dispatch. 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. + * + * Note that filters are run in a dedicated message handling thread so + * they can't block and, generally, can't do anything but signal a + * worker thread. Also note that filters are rarely needed - use API + * such as g_dbus_connection_send_message_with_reply(), + * g_dbus_connection_signal_subscribe() or + * g_dbus_connection_call() instead. + * + * Returns: A filter identifier that can be used with + * g_dbus_connection_remove_filter(). + * + * Since: 2.26 + */ +guint +g_dbus_connection_add_filter (GDBusConnection *connection, + GDBusMessageFilterFunction filter_function, + gpointer user_data, + GDestroyNotify user_data_free_func) +{ + FilterData *data; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); + g_return_val_if_fail (filter_function != NULL, 0); + + CONNECTION_LOCK (connection); + data = g_new0 (FilterData, 1); + data->id = _global_filter_id++; /* TODO: overflow etc. */ + data->filter_function = filter_function; + data->user_data = user_data; + data->user_data_free_func = user_data_free_func; + g_ptr_array_add (connection->priv->filters, data); + CONNECTION_UNLOCK (connection); + + return data->id; +} + +/* only called from finalize(), removes all filters */ +static void +purge_all_filters (GDBusConnection *connection) +{ + guint n; + for (n = 0; n < connection->priv->filters->len; n++) + { + FilterData *data = connection->priv->filters->pdata[n]; + if (data->user_data_free_func != NULL) + data->user_data_free_func (data->user_data); + g_free (data); + } +} + +/** + * g_dbus_connection_remove_filter: + * @connection: a #GDBusConnection + * @filter_id: an identifier obtained from g_dbus_connection_add_filter() + * + * Removes a filter. + * + * Since: 2.26 + */ +void +g_dbus_connection_remove_filter (GDBusConnection *connection, + guint filter_id) +{ + guint n; + FilterData *to_destroy; + + g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); + + CONNECTION_LOCK (connection); + to_destroy = NULL; + for (n = 0; n < connection->priv->filters->len; n++) + { + FilterData *data = connection->priv->filters->pdata[n]; + if (data->id == filter_id) + { + g_ptr_array_remove_index (connection->priv->filters, n); + to_destroy = data; + break; + } + } + CONNECTION_UNLOCK (connection); + + /* do free without holding lock */ + if (to_destroy != NULL) + { + if (to_destroy->user_data_free_func != NULL) + to_destroy->user_data_free_func (to_destroy->user_data); + g_free (to_destroy); + } + else + { + g_warning ("g_dbus_connection_remove_filter: No filter found for filter_id %d", filter_id); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + gchar *rule; + gchar *sender; + gchar *interface_name; + gchar *member; + gchar *object_path; + gchar *arg0; + GArray *subscribers; +} SignalData; + +typedef struct +{ + GDBusSignalCallback callback; + gpointer user_data; + GDestroyNotify user_data_free_func; + guint id; + GMainContext *context; +} SignalSubscriber; + +static void +signal_data_free (SignalData *data) +{ + g_free (data->rule); + g_free (data->sender); + g_free (data->interface_name); + g_free (data->member); + g_free (data->object_path); + g_free (data->arg0); + g_array_free (data->subscribers, TRUE); + g_free (data); +} + +static gchar * +args_to_rule (const gchar *sender, + const gchar *interface_name, + const gchar *member, + const gchar *object_path, + const gchar *arg0) +{ + GString *rule; + + rule = g_string_new ("type='signal'"); + if (sender != NULL) + g_string_append_printf (rule, ",sender='%s'", sender); + if (interface_name != NULL) + g_string_append_printf (rule, ",interface='%s'", interface_name); + if (member != NULL) + g_string_append_printf (rule, ",member='%s'", member); + if (object_path != NULL) + g_string_append_printf (rule, ",path='%s'", object_path); + if (arg0 != NULL) + g_string_append_printf (rule, ",arg0='%s'", arg0); + + return g_string_free (rule, FALSE); +} + +static guint _global_subscriber_id = 1; +static guint _global_registration_id = 1; +static guint _global_subtree_registration_id = 1; + +/* ---------------------------------------------------------------------------------------------------- */ + +/* must hold lock when calling */ +static void +add_match_rule (GDBusConnection *connection, + const gchar *match_rule) +{ + GError *error; + GDBusMessage *message; + + message = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */ + "/org/freedesktop/DBus", /* path */ + "org.freedesktop.DBus", /* interface */ + "AddMatch"); + g_dbus_message_set_body (message, g_variant_new ("(s)", match_rule)); + + error = NULL; + if (!g_dbus_connection_send_message_unlocked (connection, + message, + NULL, + &error)) + { + g_critical ("Error while sending AddMatch() message: %s", error->message); + g_error_free (error); + } + g_object_unref (message); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* must hold lock when calling */ +static void +remove_match_rule (GDBusConnection *connection, + const gchar *match_rule) +{ + GError *error; + GDBusMessage *message; + + message = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */ + "/org/freedesktop/DBus", /* path */ + "org.freedesktop.DBus", /* interface */ + "RemoveMatch"); + g_dbus_message_set_body (message, g_variant_new ("(s)", match_rule)); + + error = NULL; + if (!g_dbus_connection_send_message_unlocked (connection, + message, + NULL, + &error)) + { + g_critical ("Error while sending RemoveMatch() message: %s", error->message); + g_error_free (error); + } + g_object_unref (message); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +is_signal_data_for_name_lost_or_acquired (SignalData *signal_data) +{ + return g_strcmp0 (signal_data->sender, "org.freedesktop.DBus") == 0 && + g_strcmp0 (signal_data->interface_name, "org.freedesktop.DBus") == 0 && + g_strcmp0 (signal_data->object_path, "/org/freedesktop/DBus") == 0 && + (g_strcmp0 (signal_data->member, "NameLost") == 0 || + g_strcmp0 (signal_data->member, "NameAcquired") == 0); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_connection_signal_subscribe: + * @connection: A #GDBusConnection. + * @sender: Sender name to match on. Must be either org.freedesktop.DBus (for listening to signals from the message bus daemon) or a unique name or %NULL to listen from all senders. + * @interface_name: D-Bus interface name to match on or %NULL to match on all interfaces. + * @member: D-Bus signal name to match on or %NULL to match on all signals. + * @object_path: Object path to match on or %NULL to match on all object paths. + * @arg0: Contents of first string argument to match on or %NULL to match on all kinds of arguments. + * @callback: Callback to invoke when there is a signal matching the requested data. + * @user_data: User data to pass to @callback. + * @user_data_free_func: Function to free @user_data with when subscription is removed or %NULL. + * + * Subscribes to signals on @connection and invokes @callback with a + * whenever the signal is received. Note that @callback + * will be invoked in the thread-default main + * loop of the thread you are calling this method from. + * + * It is considered a programming error to use this function if @connection is closed. + * + * Note that if @sender is not org.freedesktop.DBus (for listening to signals from the + * message bus daemon), then it needs to be a unique bus name or %NULL (for listening to signals from any + * name) - you cannot pass a name like com.example.MyApp. + * Use e.g. g_bus_watch_name() to find the unique name for the owner of the name you are interested in. Also note + * that this function does not remove a subscription if @sender vanishes from the bus. You have to manually + * call g_dbus_connection_signal_unsubscribe() to remove a subscription. + * + * Returns: A subscription identifier that can be used with g_dbus_connection_signal_unsubscribe(). + * + * Since: 2.26 + */ +guint +g_dbus_connection_signal_subscribe (GDBusConnection *connection, + const gchar *sender, + const gchar *interface_name, + const gchar *member, + const gchar *object_path, + const gchar *arg0, + GDBusSignalCallback callback, + gpointer user_data, + GDestroyNotify user_data_free_func) +{ + gchar *rule; + SignalData *signal_data; + SignalSubscriber subscriber; + GPtrArray *signal_data_array; + + /* Right now we abort if AddMatch() fails since it can only fail with the bus being in + * an OOM condition. We might want to change that but that would involve making + * g_dbus_connection_signal_subscribe() asynchronous and having the call sites + * handle that. And there's really no sensible way of handling this short of retrying + * to add the match rule... and then there's the little thing that, hey, maybe there's + * a reason the bus in an OOM condition. + * + * Doable, but not really sure it's worth it... + */ + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); + g_return_val_if_fail (!g_dbus_connection_is_closed (connection), 0); + g_return_val_if_fail (sender == NULL || ((strcmp (sender, "org.freedesktop.DBus") == 0 || sender[0] == ':') && + (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)), 0); + g_return_val_if_fail (interface_name == NULL || g_dbus_is_interface_name (interface_name), 0); + g_return_val_if_fail (member == NULL || g_dbus_is_member_name (member), 0); + g_return_val_if_fail (object_path == NULL || g_variant_is_object_path (object_path), 0); + g_return_val_if_fail (callback != NULL, 0); + + CONNECTION_LOCK (connection); + + rule = args_to_rule (sender, interface_name, member, object_path, arg0); + + if (sender == NULL) + sender = ""; + + subscriber.callback = callback; + subscriber.user_data = user_data; + subscriber.user_data_free_func = user_data_free_func; + subscriber.id = _global_subscriber_id++; /* TODO: overflow etc. */ + subscriber.context = g_main_context_get_thread_default (); + if (subscriber.context != NULL) + g_main_context_ref (subscriber.context); + + /* see if we've already have this rule */ + signal_data = g_hash_table_lookup (connection->priv->map_rule_to_signal_data, rule); + if (signal_data != NULL) + { + g_array_append_val (signal_data->subscribers, subscriber); + g_free (rule); + goto out; + } + + signal_data = g_new0 (SignalData, 1); + signal_data->rule = rule; + signal_data->sender = g_strdup (sender); + signal_data->interface_name = g_strdup (interface_name); + signal_data->member = g_strdup (member); + signal_data->object_path = g_strdup (object_path); + signal_data->arg0 = g_strdup (arg0); + signal_data->subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber)); + g_array_append_val (signal_data->subscribers, subscriber); + + g_hash_table_insert (connection->priv->map_rule_to_signal_data, + signal_data->rule, + signal_data); + + /* Add the match rule to the bus... + * + * Avoid adding match rules for NameLost and NameAcquired messages - the bus will + * always send such messages to us. + */ + if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION) + { + if (!is_signal_data_for_name_lost_or_acquired (signal_data)) + add_match_rule (connection, signal_data->rule); + } + + out: + g_hash_table_insert (connection->priv->map_id_to_signal_data, + GUINT_TO_POINTER (subscriber.id), + signal_data); + + signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array, + signal_data->sender); + if (signal_data_array == NULL) + { + signal_data_array = g_ptr_array_new (); + g_hash_table_insert (connection->priv->map_sender_to_signal_data_array, + g_strdup (signal_data->sender), + signal_data_array); + } + g_ptr_array_add (signal_data_array, signal_data); + + CONNECTION_UNLOCK (connection); + + return subscriber.id; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* must hold lock when calling this */ +static void +unsubscribe_id_internal (GDBusConnection *connection, + guint subscription_id, + GArray *out_removed_subscribers) +{ + SignalData *signal_data; + GPtrArray *signal_data_array; + guint n; + + signal_data = g_hash_table_lookup (connection->priv->map_id_to_signal_data, + GUINT_TO_POINTER (subscription_id)); + if (signal_data == NULL) + { + /* Don't warn here, we may have thrown all subscriptions out when the connection was closed */ + goto out; + } + + for (n = 0; n < signal_data->subscribers->len; n++) + { + SignalSubscriber *subscriber; + + subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, n)); + if (subscriber->id != subscription_id) + continue; + + g_warn_if_fail (g_hash_table_remove (connection->priv->map_id_to_signal_data, + GUINT_TO_POINTER (subscription_id))); + g_array_append_val (out_removed_subscribers, *subscriber); + g_array_remove_index (signal_data->subscribers, n); + + if (signal_data->subscribers->len == 0) + g_warn_if_fail (g_hash_table_remove (connection->priv->map_rule_to_signal_data, signal_data->rule)); + + signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array, + signal_data->sender); + g_warn_if_fail (signal_data_array != NULL); + g_warn_if_fail (g_ptr_array_remove (signal_data_array, signal_data)); + + if (signal_data_array->len == 0) + { + g_warn_if_fail (g_hash_table_remove (connection->priv->map_sender_to_signal_data_array, signal_data->sender)); + + /* remove the match rule from the bus unless NameLost or NameAcquired (see subscribe()) */ + if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION) + { + if (!is_signal_data_for_name_lost_or_acquired (signal_data)) + remove_match_rule (connection, signal_data->rule); + } + + signal_data_free (signal_data); + } + + goto out; + } + + g_assert_not_reached (); + + out: + ; +} + +/** + * g_dbus_connection_signal_unsubscribe: + * @connection: A #GDBusConnection. + * @subscription_id: A subscription id obtained from g_dbus_connection_signal_subscribe(). + * + * Unsubscribes from signals. + * + * Since: 2.26 + */ +void +g_dbus_connection_signal_unsubscribe (GDBusConnection *connection, + guint subscription_id) +{ + GArray *subscribers; + guint n; + + g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); + + subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber)); + + CONNECTION_LOCK (connection); + unsubscribe_id_internal (connection, + subscription_id, + subscribers); + CONNECTION_UNLOCK (connection); + + /* invariant */ + g_assert (subscribers->len == 0 || subscribers->len == 1); + + /* call GDestroyNotify without lock held */ + for (n = 0; n < subscribers->len; n++) + { + SignalSubscriber *subscriber; + subscriber = &(g_array_index (subscribers, SignalSubscriber, n)); + if (subscriber->user_data_free_func != NULL) + subscriber->user_data_free_func (subscriber->user_data); + if (subscriber->context != NULL) + g_main_context_unref (subscriber->context); + } + + g_array_free (subscribers, TRUE); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + guint subscription_id; + GDBusSignalCallback callback; + gpointer user_data; + GDBusMessage *message; + GDBusConnection *connection; + const gchar *sender; + const gchar *path; + const gchar *interface; + const gchar *member; +} SignalInstance; + +/* called on delivery thread (e.g. where g_dbus_connection_signal_subscribe() was called) with + * no locks held + */ +static gboolean +emit_signal_instance_in_idle_cb (gpointer data) +{ + SignalInstance *signal_instance = data; + GVariant *parameters; + gboolean has_subscription; + + parameters = g_dbus_message_get_body (signal_instance->message); + if (parameters == NULL) + { + parameters = g_variant_new ("()"); + g_variant_ref_sink (parameters); + } + else + { + g_variant_ref_sink (parameters); + } + +#if 0 + g_debug ("in emit_signal_instance_in_idle_cb (sender=%s path=%s interface=%s member=%s params=%s)", + signal_instance->sender, + signal_instance->path, + signal_instance->interface, + signal_instance->member, + g_variant_print (parameters, TRUE)); +#endif + + /* Careful here, don't do the callback if we no longer has the subscription */ + CONNECTION_LOCK (signal_instance->connection); + has_subscription = FALSE; + if (g_hash_table_lookup (signal_instance->connection->priv->map_id_to_signal_data, + GUINT_TO_POINTER (signal_instance->subscription_id)) != NULL) + has_subscription = TRUE; + CONNECTION_UNLOCK (signal_instance->connection); + + if (has_subscription) + signal_instance->callback (signal_instance->connection, + signal_instance->sender, + signal_instance->path, + signal_instance->interface, + signal_instance->member, + parameters, + signal_instance->user_data); + + if (parameters != NULL) + g_variant_unref (parameters); + + return FALSE; +} + +static void +signal_instance_free (SignalInstance *signal_instance) +{ + g_object_unref (signal_instance->message); + g_object_unref (signal_instance->connection); + g_free (signal_instance); +} + +/* called in message handler thread WITH lock held */ +static void +schedule_callbacks (GDBusConnection *connection, + GPtrArray *signal_data_array, + GDBusMessage *message, + const gchar *sender) +{ + guint n, m; + const gchar *interface; + const gchar *member; + const gchar *path; + const gchar *arg0; + + interface = NULL; + member = NULL; + path = NULL; + arg0 = NULL; + + interface = g_dbus_message_get_interface (message); + member = g_dbus_message_get_member (message); + path = g_dbus_message_get_path (message); + arg0 = g_dbus_message_get_arg0 (message); + +#if 0 + g_debug ("sender = `%s'", sender); + g_debug ("interface = `%s'", interface); + g_debug ("member = `%s'", member); + g_debug ("path = `%s'", path); + g_debug ("arg0 = `%s'", arg0); +#endif + + /* TODO: if this is slow, then we can change signal_data_array into + * map_object_path_to_signal_data_array or something. + */ + for (n = 0; n < signal_data_array->len; n++) + { + SignalData *signal_data = signal_data_array->pdata[n]; + + if (signal_data->interface_name != NULL && g_strcmp0 (signal_data->interface_name, interface) != 0) + continue; + + if (signal_data->member != NULL && g_strcmp0 (signal_data->member, member) != 0) + continue; + + if (signal_data->object_path != NULL && g_strcmp0 (signal_data->object_path, path) != 0) + continue; + + if (signal_data->arg0 != NULL && g_strcmp0 (signal_data->arg0, arg0) != 0) + continue; + + for (m = 0; m < signal_data->subscribers->len; m++) + { + SignalSubscriber *subscriber; + GSource *idle_source; + SignalInstance *signal_instance; + + subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, m)); + + signal_instance = g_new0 (SignalInstance, 1); + signal_instance->subscription_id = subscriber->id; + signal_instance->callback = subscriber->callback; + signal_instance->user_data = subscriber->user_data; + signal_instance->message = g_object_ref (message); + signal_instance->connection = g_object_ref (connection); + signal_instance->sender = sender; + signal_instance->path = path; + signal_instance->interface = interface; + signal_instance->member = member; + + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); + g_source_set_callback (idle_source, + emit_signal_instance_in_idle_cb, + signal_instance, + (GDestroyNotify) signal_instance_free); + g_source_attach (idle_source, subscriber->context); + g_source_unref (idle_source); + } + } +} + +/* called in message handler thread with lock held */ +static void +distribute_signals (GDBusConnection *connection, + GDBusMessage *message) +{ + GPtrArray *signal_data_array; + const gchar *sender; + + sender = g_dbus_message_get_sender (message); + + /* collect subscribers that match on sender */ + if (sender != NULL) + { + signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array, sender); + if (signal_data_array != NULL) + schedule_callbacks (connection, signal_data_array, message, sender); + } + + /* collect subscribers not matching on sender */ + signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array, ""); + if (signal_data_array != NULL) + schedule_callbacks (connection, signal_data_array, message, sender); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* only called from finalize(), removes all subscriptions */ +static void +purge_all_signal_subscriptions (GDBusConnection *connection) +{ + GHashTableIter iter; + gpointer key; + GArray *ids; + GArray *subscribers; + guint n; + + ids = g_array_new (FALSE, FALSE, sizeof (guint)); + g_hash_table_iter_init (&iter, connection->priv->map_id_to_signal_data); + while (g_hash_table_iter_next (&iter, &key, NULL)) + { + guint subscription_id = GPOINTER_TO_UINT (key); + g_array_append_val (ids, subscription_id); + } + + subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber)); + for (n = 0; n < ids->len; n++) + { + guint subscription_id = g_array_index (ids, guint, n); + unsubscribe_id_internal (connection, + subscription_id, + subscribers); + } + g_array_free (ids, TRUE); + + /* call GDestroyNotify without lock held */ + for (n = 0; n < subscribers->len; n++) + { + SignalSubscriber *subscriber; + subscriber = &(g_array_index (subscribers, SignalSubscriber, n)); + if (subscriber->user_data_free_func != NULL) + subscriber->user_data_free_func (subscriber->user_data); + if (subscriber->context != NULL) + g_main_context_unref (subscriber->context); + } + + g_array_free (subscribers, TRUE); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +struct ExportedObject +{ + gchar *object_path; + GDBusConnection *connection; + + /* maps gchar* -> ExportedInterface* */ + GHashTable *map_if_name_to_ei; +}; + +/* only called with lock held */ +static void +exported_object_free (ExportedObject *eo) +{ + g_free (eo->object_path); + g_hash_table_unref (eo->map_if_name_to_ei); + g_free (eo); +} + +typedef struct +{ + ExportedObject *eo; + + guint id; + gchar *interface_name; + const GDBusInterfaceVTable *vtable; + const GDBusInterfaceInfo *introspection_data; + + GMainContext *context; + gpointer user_data; + GDestroyNotify user_data_free_func; +} ExportedInterface; + +/* called with lock held */ +static void +exported_interface_free (ExportedInterface *ei) +{ + if (ei->user_data_free_func != NULL) + /* TODO: push to thread-default mainloop */ + ei->user_data_free_func (ei->user_data); + + if (ei->context != NULL) + g_main_context_unref (ei->context); + + g_free (ei->interface_name); + g_free (ei); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GDBusConnection *connection; + GDBusMessage *message; + gpointer user_data; + const char *property_name; + const GDBusInterfaceVTable *vtable; + const GDBusInterfaceInfo *interface_info; + const GDBusPropertyInfo *property_info; +} PropertyData; + +static void +property_data_free (PropertyData *data) +{ + g_object_unref (data->connection); + g_object_unref (data->message); + g_free (data); +} + +/* called in thread where object was registered - no locks held */ +static gboolean +invoke_get_property_in_idle_cb (gpointer _data) +{ + PropertyData *data = _data; + GVariant *value; + GError *error; + GDBusMessage *reply; + + error = NULL; + value = data->vtable->get_property (data->connection, + g_dbus_message_get_sender (data->message), + g_dbus_message_get_path (data->message), + data->interface_info->name, + data->property_name, + &error, + data->user_data); + + + if (value != NULL) + { + g_assert_no_error (error); + + g_variant_ref_sink (value); + reply = g_dbus_message_new_method_reply (data->message); + g_dbus_message_set_body (reply, g_variant_new ("(v)", value)); + g_dbus_connection_send_message (data->connection, reply, NULL, NULL); + g_variant_unref (value); + g_object_unref (reply); + } + else + { + gchar *dbus_error_name; + + g_assert (error != NULL); + + dbus_error_name = g_dbus_error_encode_gerror (error); + reply = g_dbus_message_new_method_error_literal (data->message, + dbus_error_name, + error->message); + g_dbus_connection_send_message (data->connection, reply, NULL, NULL); + g_free (dbus_error_name); + g_error_free (error); + g_object_unref (reply); + } + + return FALSE; +} + +/* called in thread where object was registered - no locks held */ +static gboolean +invoke_set_property_in_idle_cb (gpointer _data) +{ + PropertyData *data = _data; + GError *error; + GDBusMessage *reply; + GVariant *value; + + error = NULL; + value = NULL; + + g_variant_get (g_dbus_message_get_body (data->message), + "(ssv)", + NULL, + NULL, + &value); + + /* Fail with org.freedesktop.DBus.Error.InvalidArgs if the type + * of the given value is wrong + */ + if (g_strcmp0 (g_variant_get_type_string (value), data->property_info->signature) != 0) + { + reply = g_dbus_message_new_method_error (data->message, + "org.freedesktop.DBus.Error.InvalidArgs", + _("Error setting property `%s': Expected type `%s' but got `%s'"), + data->property_info->name, + data->property_info->signature, + g_variant_get_type_string (value)); + goto out; + } + + if (!data->vtable->set_property (data->connection, + g_dbus_message_get_sender (data->message), + g_dbus_message_get_path (data->message), + data->interface_info->name, + data->property_name, + value, + &error, + data->user_data)) + { + gchar *dbus_error_name; + g_assert (error != NULL); + dbus_error_name = g_dbus_error_encode_gerror (error); + reply = g_dbus_message_new_method_error_literal (data->message, + dbus_error_name, + error->message); + g_free (dbus_error_name); + g_error_free (error); + } + else + { + reply = g_dbus_message_new_method_reply (data->message); + } + + out: + g_assert (reply != NULL); + g_dbus_connection_send_message (data->connection, reply, NULL, NULL); + g_object_unref (reply); + + return FALSE; +} + +/* called with lock held */ +static gboolean +validate_and_maybe_schedule_property_getset (GDBusConnection *connection, + GDBusMessage *message, + gboolean is_get, + const GDBusInterfaceInfo *introspection_data, + const GDBusInterfaceVTable *vtable, + GMainContext *main_context, + gpointer user_data) +{ + gboolean handled; + const char *interface_name; + const char *property_name; + const GDBusPropertyInfo *property_info; + GSource *idle_source; + PropertyData *property_data; + GDBusMessage *reply; + + handled = FALSE; + + if (is_get) + g_variant_get (g_dbus_message_get_body (message), + "(&s&s)", + &interface_name, + &property_name); + else + g_variant_get (g_dbus_message_get_body (message), + "(&s&sv)", + &interface_name, + &property_name, + NULL); + + + if (is_get) + { + if (vtable == NULL || vtable->get_property == NULL) + goto out; + } + else + { + if (vtable == NULL || vtable->set_property == NULL) + goto out; + } + + /* Check that the property exists - if not fail with org.freedesktop.DBus.Error.InvalidArgs + */ + property_info = NULL; + + /* TODO: the cost of this is O(n) - it might be worth caching the result */ + property_info = g_dbus_interface_info_lookup_property (introspection_data, property_name); + if (property_info == NULL) + { + reply = g_dbus_message_new_method_error (message, + "org.freedesktop.DBus.Error.InvalidArgs", + _("No such property `%s'"), + property_name); + g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL); + g_object_unref (reply); + handled = TRUE; + goto out; + } + + if (is_get && !(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE)) + { + reply = g_dbus_message_new_method_error (message, + "org.freedesktop.DBus.Error.InvalidArgs", + _("Property `%s' is not readable"), + property_name); + g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL); + g_object_unref (reply); + handled = TRUE; + goto out; + } + else if (!is_get && !(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)) + { + reply = g_dbus_message_new_method_error (message, + "org.freedesktop.DBus.Error.InvalidArgs", + _("Property `%s' is not writable"), + property_name); + g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL); + g_object_unref (reply); + handled = TRUE; + goto out; + } + + /* ok, got the property info - call user code in an idle handler */ + property_data = g_new0 (PropertyData, 1); + property_data->connection = g_object_ref (connection); + property_data->message = g_object_ref (message); + property_data->user_data = user_data; + property_data->property_name = property_name; + property_data->vtable = vtable; + property_data->interface_info = introspection_data; + property_data->property_info = property_info; + + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); + g_source_set_callback (idle_source, + is_get ? invoke_get_property_in_idle_cb : invoke_set_property_in_idle_cb, + property_data, + (GDestroyNotify) property_data_free); + g_source_attach (idle_source, main_context); + g_source_unref (idle_source); + + handled = TRUE; + + out: + return handled; +} + +/* called with lock held */ +static gboolean +handle_getset_property (GDBusConnection *connection, + ExportedObject *eo, + GDBusMessage *message, + gboolean is_get) +{ + ExportedInterface *ei; + gboolean handled; + const char *interface_name; + const char *property_name; + + handled = FALSE; + + if (is_get) + g_variant_get (g_dbus_message_get_body (message), + "(&s&s)", + &interface_name, + &property_name); + else + g_variant_get (g_dbus_message_get_body (message), + "(&s&sv)", + &interface_name, + &property_name, + NULL); + + /* Fail with org.freedesktop.DBus.Error.InvalidArgs if there is + * no such interface registered + */ + ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name); + if (ei == NULL) + { + GDBusMessage *reply; + reply = g_dbus_message_new_method_error (message, + "org.freedesktop.DBus.Error.InvalidArgs", + _("No such interface `%s'"), + interface_name); + g_dbus_connection_send_message_unlocked (eo->connection, reply, NULL, NULL); + g_object_unref (reply); + handled = TRUE; + goto out; + } + + handled = validate_and_maybe_schedule_property_getset (eo->connection, + message, + is_get, + ei->introspection_data, + ei->vtable, + ei->context, + ei->user_data); + out: + return handled; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GDBusConnection *connection; + GDBusMessage *message; + gpointer user_data; + const GDBusInterfaceVTable *vtable; + const GDBusInterfaceInfo *interface_info; +} PropertyGetAllData; + +static void +property_get_all_data_free (PropertyData *data) +{ + g_object_unref (data->connection); + g_object_unref (data->message); + g_free (data); +} + +/* called in thread where object was registered - no locks held */ +static gboolean +invoke_get_all_properties_in_idle_cb (gpointer _data) +{ + PropertyGetAllData *data = _data; + GVariantBuilder *builder; + GVariant *packed; + GVariant *result; + GError *error; + GDBusMessage *reply; + guint n; + + error = NULL; + + /* TODO: Right now we never fail this call - we just omit values if + * a get_property() call is failing. + * + * We could fail the whole call if just a single get_property() call + * returns an error. We need clarification in the D-Bus spec about this. + */ + builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); + for (n = 0; data->interface_info->properties != NULL && data->interface_info->properties[n] != NULL; n++) + { + const GDBusPropertyInfo *property_info = data->interface_info->properties[n]; + GVariant *value; + + if (!(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE)) + continue; + + value = data->vtable->get_property (data->connection, + g_dbus_message_get_sender (data->message), + g_dbus_message_get_path (data->message), + data->interface_info->name, + property_info->name, + NULL, + data->user_data); + + if (value == NULL) + continue; + + g_variant_ref_sink (value); + g_variant_builder_add (builder, + "{sv}", + property_info->name, + value); + g_variant_unref (value); + } + result = g_variant_builder_end (builder); + + builder = g_variant_builder_new (G_VARIANT_TYPE_TUPLE); + g_variant_builder_add_value (builder, result); /* steals result since result is floating */ + packed = g_variant_builder_end (builder); + + reply = g_dbus_message_new_method_reply (data->message); + g_dbus_message_set_body (reply, packed); + g_dbus_connection_send_message (data->connection, reply, NULL, NULL); + g_object_unref (reply); + + return FALSE; +} + +/* called with lock held */ +static gboolean +validate_and_maybe_schedule_property_get_all (GDBusConnection *connection, + GDBusMessage *message, + const GDBusInterfaceInfo *introspection_data, + const GDBusInterfaceVTable *vtable, + GMainContext *main_context, + gpointer user_data) +{ + gboolean handled; + const char *interface_name; + GSource *idle_source; + PropertyGetAllData *property_get_all_data; + + handled = FALSE; + + g_variant_get (g_dbus_message_get_body (message), + "(&s)", + &interface_name); + + if (vtable == NULL || vtable->get_property == NULL) + goto out; + + /* ok, got the property info - call user in an idle handler */ + property_get_all_data = g_new0 (PropertyGetAllData, 1); + property_get_all_data->connection = g_object_ref (connection); + property_get_all_data->message = g_object_ref (message); + property_get_all_data->user_data = user_data; + property_get_all_data->vtable = vtable; + property_get_all_data->interface_info = introspection_data; + + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); + g_source_set_callback (idle_source, + invoke_get_all_properties_in_idle_cb, + property_get_all_data, + (GDestroyNotify) property_get_all_data_free); + g_source_attach (idle_source, main_context); + g_source_unref (idle_source); + + handled = TRUE; + + out: + return handled; +} + +/* called with lock held */ +static gboolean +handle_get_all_properties (GDBusConnection *connection, + ExportedObject *eo, + GDBusMessage *message) +{ + ExportedInterface *ei; + gboolean handled; + const char *interface_name; + + handled = FALSE; + + g_variant_get (g_dbus_message_get_body (message), + "(&s)", + &interface_name); + + /* Fail with org.freedesktop.DBus.Error.InvalidArgs if there is + * no such interface registered + */ + ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name); + if (ei == NULL) + { + GDBusMessage *reply; + reply = g_dbus_message_new_method_error (message, + "org.freedesktop.DBus.Error.InvalidArgs", + _("No such interface"), + interface_name); + g_dbus_connection_send_message_unlocked (eo->connection, reply, NULL, NULL); + g_object_unref (reply); + handled = TRUE; + goto out; + } + + handled = validate_and_maybe_schedule_property_get_all (eo->connection, + message, + ei->introspection_data, + ei->vtable, + ei->context, + ei->user_data); + out: + return handled; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static const gchar introspect_header[] = + "\n" + "\n" + "\n"; + +static const gchar introspect_tail[] = + "\n"; + +static const gchar introspect_standard_interfaces[] = + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"; + +static void +introspect_append_header (GString *s) +{ + g_string_append (s, introspect_header); +} + +static void +introspect_append_standard_interfaces (GString *s) +{ + g_string_append (s, introspect_standard_interfaces); +} + +static void +maybe_add_path (const gchar *path, gsize path_len, const gchar *object_path, GHashTable *set) +{ + if (g_str_has_prefix (object_path, path) && strlen (object_path) >= path_len) + { + const gchar *begin; + const gchar *end; + gchar *s; + + begin = object_path + path_len; + end = strchr (begin, '/'); + + if (end != NULL) + s = g_strndup (begin, end - begin); + else + s = g_strdup (begin); + + if (g_hash_table_lookup (set, s) == NULL) + g_hash_table_insert (set, s, GUINT_TO_POINTER (1)); + else + g_free (s); + } +} + +/* TODO: we want a nicer public interface for this */ +static gchar ** +g_dbus_connection_list_registered_unlocked (GDBusConnection *connection, + const gchar *path) +{ + GPtrArray *p; + gchar **ret; + GHashTableIter hash_iter; + const gchar *object_path; + gsize path_len; + GHashTable *set; + GList *keys; + GList *l; + + CONNECTION_ENSURE_LOCK (connection); + + path_len = strlen (path); + if (path_len > 1) + path_len++; + + set = g_hash_table_new (g_str_hash, g_str_equal); + + g_hash_table_iter_init (&hash_iter, connection->priv->map_object_path_to_eo); + while (g_hash_table_iter_next (&hash_iter, (gpointer) &object_path, NULL)) + maybe_add_path (path, path_len, object_path, set); + + g_hash_table_iter_init (&hash_iter, connection->priv->map_object_path_to_es); + while (g_hash_table_iter_next (&hash_iter, (gpointer) &object_path, NULL)) + maybe_add_path (path, path_len, object_path, set); + + p = g_ptr_array_new (); + keys = g_hash_table_get_keys (set); + for (l = keys; l != NULL; l = l->next) + g_ptr_array_add (p, l->data); + g_hash_table_unref (set); + g_list_free (keys); + + g_ptr_array_add (p, NULL); + ret = (gchar **) g_ptr_array_free (p, FALSE); + return ret; +} + +static gchar ** +g_dbus_connection_list_registered (GDBusConnection *connection, + const gchar *path) +{ + gchar **ret; + CONNECTION_LOCK (connection); + ret = g_dbus_connection_list_registered_unlocked (connection, path); + CONNECTION_UNLOCK (connection); + return ret; +} + +/* called in message handler thread with lock held */ +static gboolean +handle_introspect (GDBusConnection *connection, + ExportedObject *eo, + GDBusMessage *message) +{ + guint n; + GString *s; + GDBusMessage *reply; + GHashTableIter hash_iter; + ExportedInterface *ei; + gchar **registered; + + /* first the header with the standard interfaces */ + s = g_string_sized_new (sizeof (introspect_header) + + sizeof (introspect_standard_interfaces) + + sizeof (introspect_tail)); + introspect_append_header (s); + introspect_append_standard_interfaces (s); + + /* then include the registered interfaces */ + g_hash_table_iter_init (&hash_iter, eo->map_if_name_to_ei); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &ei)) + g_dbus_interface_info_generate_xml (ei->introspection_data, 2, s); + + /* finally include nodes registered below us */ + registered = g_dbus_connection_list_registered_unlocked (connection, eo->object_path); + for (n = 0; registered != NULL && registered[n] != NULL; n++) + g_string_append_printf (s, " \n", registered[n]); + g_strfreev (registered); + g_string_append (s, introspect_tail); + + reply = g_dbus_message_new_method_reply (message); + g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str)); + g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL); + g_object_unref (reply); + g_string_free (s, TRUE); + + return TRUE; +} + +/* called in thread where object was registered - no locks held */ +static gboolean +call_in_idle_cb (gpointer user_data) +{ + GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (user_data); + GDBusInterfaceVTable *vtable; + + vtable = g_object_get_data (G_OBJECT (invocation), "g-dbus-interface-vtable"); + g_assert (vtable != NULL && vtable->method_call != NULL); + + vtable->method_call (g_dbus_method_invocation_get_connection (invocation), + g_dbus_method_invocation_get_sender (invocation), + g_dbus_method_invocation_get_object_path (invocation), + g_dbus_method_invocation_get_interface_name (invocation), + g_dbus_method_invocation_get_method_name (invocation), + g_dbus_method_invocation_get_parameters (invocation), + g_object_ref (invocation), + g_dbus_method_invocation_get_user_data (invocation)); + + return FALSE; +} + +/* called in message handler thread with lock held */ +static gboolean +validate_and_maybe_schedule_method_call (GDBusConnection *connection, + GDBusMessage *message, + const GDBusInterfaceInfo *introspection_data, + const GDBusInterfaceVTable *vtable, + GMainContext *main_context, + gpointer user_data) +{ + GDBusMethodInvocation *invocation; + const GDBusMethodInfo *method_info; + GDBusMessage *reply; + GVariant *parameters; + GSource *idle_source; + gboolean handled; + gchar *in_signature; + + handled = FALSE; + + /* TODO: the cost of this is O(n) - it might be worth caching the result */ + method_info = g_dbus_interface_info_lookup_method (introspection_data, g_dbus_message_get_member (message)); + + /* if the method doesn't exist, return the org.freedesktop.DBus.Error.UnknownMethod + * error to the caller + */ + if (method_info == NULL) + { + reply = g_dbus_message_new_method_error (message, + "org.freedesktop.DBus.Error.UnknownMethod", + _("No such method `%s'"), + g_dbus_message_get_member (message)); + g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL); + g_object_unref (reply); + handled = TRUE; + goto out; + } + + /* Check that the incoming args are of the right type - if they are not, return + * the org.freedesktop.DBus.Error.InvalidArgs error to the caller + * + * TODO: might also be worth caching the combined signature. + */ + in_signature = _g_dbus_compute_complete_signature (method_info->in_args, FALSE); + if (g_strcmp0 (g_dbus_message_get_signature (message), in_signature) != 0) + { + reply = g_dbus_message_new_method_error (message, + "org.freedesktop.DBus.Error.InvalidArgs", + _("Signature of message, `%s', does not match expected signature `%s'"), + g_dbus_message_get_signature (message), + in_signature); + g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL); + g_object_unref (reply); + g_free (in_signature); + handled = TRUE; + goto out; + } + g_free (in_signature); + + parameters = g_dbus_message_get_body (message); + if (parameters == NULL) + { + parameters = g_variant_new ("()"); + g_variant_ref_sink (parameters); + } + else + { + g_variant_ref (parameters); + } + + /* schedule the call in idle */ + invocation = g_dbus_method_invocation_new (g_dbus_message_get_sender (message), + g_dbus_message_get_path (message), + g_dbus_message_get_interface (message), + g_dbus_message_get_member (message), + method_info, + connection, + message, + parameters, + user_data); + g_variant_unref (parameters); + g_object_set_data (G_OBJECT (invocation), + "g-dbus-interface-vtable", + (gpointer) vtable); + + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); + g_source_set_callback (idle_source, + call_in_idle_cb, + invocation, + g_object_unref); + g_source_attach (idle_source, main_context); + g_source_unref (idle_source); + + handled = TRUE; + + out: + return handled; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* called in message handler thread with lock held */ +static gboolean +obj_message_func (GDBusConnection *connection, + ExportedObject *eo, + GDBusMessage *message) +{ + const gchar *interface_name; + const gchar *member; + const gchar *signature; + gboolean handled; + + handled = FALSE; + + interface_name = g_dbus_message_get_interface (message); + member = g_dbus_message_get_member (message); + signature = g_dbus_message_get_signature (message); + + /* see if we have an interface for handling this call */ + if (interface_name != NULL) + { + ExportedInterface *ei; + ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name); + if (ei != NULL) + { + /* we do - invoke the handler in idle in the right thread */ + + /* handle no vtable or handler being present */ + if (ei->vtable == NULL || ei->vtable->method_call == NULL) + goto out; + + handled = validate_and_maybe_schedule_method_call (connection, + message, + ei->introspection_data, + ei->vtable, + ei->context, + ei->user_data); + goto out; + } + } + + if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Introspectable") == 0 && + g_strcmp0 (member, "Introspect") == 0 && + g_strcmp0 (signature, "") == 0) + { + handled = handle_introspect (connection, eo, message); + goto out; + } + else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 && + g_strcmp0 (member, "Get") == 0 && + g_strcmp0 (signature, "ss") == 0) + { + handled = handle_getset_property (connection, eo, message, TRUE); + goto out; + } + else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 && + g_strcmp0 (member, "Set") == 0 && + g_strcmp0 (signature, "ssv") == 0) + { + handled = handle_getset_property (connection, eo, message, FALSE); + goto out; + } + else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 && + g_strcmp0 (member, "GetAll") == 0 && + g_strcmp0 (signature, "s") == 0) + { + handled = handle_get_all_properties (connection, eo, message); + goto out; + } + + out: + return handled; +} + +/** + * g_dbus_connection_register_object: + * @connection: A #GDBusConnection. + * @object_path: The object path to register at. + * @introspection_data: Introspection data for the interface. + * @vtable: A #GDBusInterfaceVTable to call into or %NULL. + * @user_data: Data to pass to functions in @vtable. + * @user_data_free_func: Function to call when the object path is unregistered. + * @error: Return location for error or %NULL. + * + * Registers callbacks for exported objects at @object_path with the + * D-Bus interface that is described in @introspection_data. + * + * Calls to functions in @vtable (and @user_data_free_func) will + * happen in the thread-default main + * loop of the thread you are calling this method from. + * + * Note that all #GVariant values passed to functions in @vtable will match + * the signature given in @introspection_data - if a remote caller passes + * incorrect values, the org.freedesktop.DBus.Error.InvalidArgs + * is returned to the remote caller. + * + * Additionally, if the remote caller attempts to invoke methods or + * access properties not mentioned in @introspection_data the + * org.freedesktop.DBus.Error.UnknownMethod resp. + * org.freedesktop.DBus.Error.InvalidArgs errors + * are returned to the caller. + * + * It is considered a programming error if the + * #GDBusInterfaceGetPropertyFunc function in @vtable returns a + * #GVariant of incorrect type. + * + * If an existing callback is already registered at @object_path and + * @interface_name, then @error is set to #G_IO_ERROR_EXISTS. + * + * GDBus automatically implements the standard D-Bus interfaces + * org.freedesktop.DBus.Properties, org.freedesktop.DBus.Introspectable + * and org.freedesktop.Peer, so you don't have to implement those for + * the objects you export. You can implement + * org.freedesktop.DBus.Properties yourself, e.g. to handle getting + * and setting of properties asynchronously. + * + * See for an example of how to use this method. + * + * Returns: 0 if @error is set, otherwise a registration id (never 0) + * that can be used with g_dbus_connection_unregister_object() . + * + * Since: 2.26 + */ +guint +g_dbus_connection_register_object (GDBusConnection *connection, + const gchar *object_path, + const GDBusInterfaceInfo *introspection_data, + const GDBusInterfaceVTable *vtable, + gpointer user_data, + GDestroyNotify user_data_free_func, + GError **error) +{ + ExportedObject *eo; + ExportedInterface *ei; + guint ret; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); + g_return_val_if_fail (!g_dbus_connection_is_closed (connection), 0); + g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), 0); + g_return_val_if_fail (introspection_data != NULL, 0); + g_return_val_if_fail (g_dbus_is_interface_name (introspection_data->name), 0); + g_return_val_if_fail (error == NULL || *error == NULL, 0); + + ret = 0; + + CONNECTION_LOCK (connection); + + eo = g_hash_table_lookup (connection->priv->map_object_path_to_eo, object_path); + if (eo == NULL) + { + eo = g_new0 (ExportedObject, 1); + eo->object_path = g_strdup (object_path); + eo->connection = connection; + eo->map_if_name_to_ei = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) exported_interface_free); + g_hash_table_insert (connection->priv->map_object_path_to_eo, eo->object_path, eo); + } + + ei = g_hash_table_lookup (eo->map_if_name_to_ei, introspection_data->name); + if (ei != NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("An object is already exported for the interface %s at %s"), + introspection_data->name, + object_path); + goto out; + } + + ei = g_new0 (ExportedInterface, 1); + ei->id = _global_registration_id++; /* TODO: overflow etc. */ + ei->eo = eo; + ei->user_data = user_data; + ei->user_data_free_func = user_data_free_func; + ei->vtable = vtable; + ei->introspection_data = introspection_data; + ei->interface_name = g_strdup (introspection_data->name); + ei->context = g_main_context_get_thread_default (); + if (ei->context != NULL) + g_main_context_ref (ei->context); + + g_hash_table_insert (eo->map_if_name_to_ei, + (gpointer) ei->interface_name, + ei); + g_hash_table_insert (connection->priv->map_id_to_ei, + GUINT_TO_POINTER (ei->id), + ei); + + ret = ei->id; + + out: + CONNECTION_UNLOCK (connection); + + return ret; +} + +/** + * g_dbus_connection_unregister_object: + * @connection: A #GDBusConnection. + * @registration_id: A registration id obtained from g_dbus_connection_register_object(). + * + * Unregisters an object. + * + * Returns: %TRUE if the object was unregistered, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_connection_unregister_object (GDBusConnection *connection, + guint registration_id) +{ + ExportedInterface *ei; + ExportedObject *eo; + gboolean ret; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); + + ret = FALSE; + + CONNECTION_LOCK (connection); + + ei = g_hash_table_lookup (connection->priv->map_id_to_ei, + GUINT_TO_POINTER (registration_id)); + if (ei == NULL) + goto out; + + eo = ei->eo; + + g_warn_if_fail (g_hash_table_remove (connection->priv->map_id_to_ei, GUINT_TO_POINTER (ei->id))); + g_warn_if_fail (g_hash_table_remove (eo->map_if_name_to_ei, ei->interface_name)); + /* unregister object path if we have no more exported interfaces */ + if (g_hash_table_size (eo->map_if_name_to_ei) == 0) + g_warn_if_fail (g_hash_table_remove (connection->priv->map_object_path_to_eo, + eo->object_path)); + + ret = TRUE; + + out: + CONNECTION_UNLOCK (connection); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_connection_emit_signal: + * @connection: A #GDBusConnection. + * @destination_bus_name: The unique bus name for the destination for the signal or %NULL to emit to all listeners. + * @object_path: Path of remote object. + * @interface_name: D-Bus interface to emit a signal on. + * @signal_name: The name of the signal to emit. + * @parameters: A #GVariant tuple with parameters for the signal or %NULL if not passing parameters. + * @error: Return location for error or %NULL. + * + * Emits a signal. + * + * If the parameters GVariant is floating, it is consumed. + * + * This can only fail if @parameters is not compatible with the D-Bus protocol. + * + * Returns: %TRUE unless @error is set. + * + * Since: 2.26 + */ +gboolean +g_dbus_connection_emit_signal (GDBusConnection *connection, + const gchar *destination_bus_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + GError **error) +{ + GDBusMessage *message; + gboolean ret; + + message = NULL; + ret = FALSE; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); + g_return_val_if_fail (destination_bus_name == NULL || g_dbus_is_name (destination_bus_name), FALSE); + g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), FALSE); + g_return_val_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name), FALSE); + g_return_val_if_fail (signal_name != NULL && g_dbus_is_member_name (signal_name), FALSE); + g_return_val_if_fail (parameters == NULL || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), FALSE); + + message = g_dbus_message_new_signal (object_path, + interface_name, + signal_name); + + if (destination_bus_name != NULL) + g_dbus_message_set_header (message, + G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION, + g_variant_new_string (destination_bus_name)); + + if (parameters != NULL) + g_dbus_message_set_body (message, parameters); + + ret = g_dbus_connection_send_message (connection, message, NULL, error); + g_object_unref (message); + + return ret; +} + +static void +add_call_flags (GDBusMessage *message, + GDBusCallFlags flags) +{ + if (flags & G_DBUS_CALL_FLAGS_NO_AUTO_START) + g_dbus_message_set_flags (message, G_DBUS_MESSAGE_FLAGS_NO_AUTO_START); +} + +/** + * g_dbus_connection_call: + * @connection: A #GDBusConnection. + * @bus_name: A unique or well-known bus name or %NULL if @connection is not a message bus connection. + * @object_path: Path of remote object. + * @interface_name: D-Bus interface to invoke method on. + * @method_name: The name of the method to invoke. + * @parameters: A #GVariant tuple with parameters for the method or %NULL if not passing parameters. + * @flags: Flags from the #GDBusCallFlags enumeration. + * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout. + * @cancellable: A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't + * care about the result of the method invocation. + * @user_data: The data to pass to @callback. + * + * Asynchronously invokes the @method_name method on the + * @interface_name D-Bus interface on the remote object at + * @object_path owned by @bus_name. + * + * If @connection is closed then the operation will fail with + * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will + * fail with %G_IO_ERROR_CANCELLED. If @parameters contains a value + * not compatible with the D-Bus protocol, the operation fails with + * %G_IO_ERROR_INVALID_ARGUMENT. + * + * If the @parameters #GVariant is floating, it is consumed. This allows + * convenient 'inline' use of g_variant_new(), e.g.: + * |[ + * g_dbus_connection_call (connection, + * "org.freedesktop.StringThings", + * "/org/freedesktop/StringThings", + * "org.freedesktop.StringThings", + * "TwoStrings", + * g_variant_new ("(ss)", + * "Thing One", + * "Thing Two"), + * G_DBUS_CALL_FLAGS_NONE, + * -1, + * NULL, + * (GAsyncReadyCallback) two_strings_done, + * NULL); + * ]| + * + * This is an asynchronous method. When the operation is finished, @callback will be invoked + * in the thread-default main loop + * of the thread you are calling this method from. You can then call + * g_dbus_connection_call_finish() to get the result of the operation. + * See g_dbus_connection_call_sync() for the synchronous version of this + * function. + * + * Since: 2.26 + */ +void +g_dbus_connection_call (GDBusConnection *connection, + const gchar *bus_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusCallFlags flags, + gint timeout_msec, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GDBusMessage *message; + + g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); + g_return_if_fail (bus_name == NULL || g_dbus_is_name (bus_name)); + g_return_if_fail (object_path != NULL && g_variant_is_object_path (object_path)); + g_return_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name)); + g_return_if_fail (method_name != NULL && g_dbus_is_member_name (method_name)); + g_return_if_fail (timeout_msec >= 0 || timeout_msec == -1); + g_return_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE)); + + message = g_dbus_message_new_method_call (bus_name, + object_path, + interface_name, + method_name); + add_call_flags (message, flags); + if (parameters != NULL) + g_dbus_message_set_body (message, parameters); + + g_dbus_connection_send_message_with_reply (connection, + message, + timeout_msec, + NULL, /* volatile guint32 *out_serial */ + cancellable, + callback, + user_data); + + if (message != NULL) + g_object_unref (message); +} + +static GVariant * +decode_method_reply (GDBusMessage *reply, + GError **error) +{ + GVariant *result; + + result = NULL; + switch (g_dbus_message_get_message_type (reply)) + { + case G_DBUS_MESSAGE_TYPE_METHOD_RETURN: + result = g_dbus_message_get_body (reply); + if (result == NULL) + { + result = g_variant_new ("()"); + g_variant_ref_sink (result); + } + else + { + g_variant_ref (result); + } + break; + + case G_DBUS_MESSAGE_TYPE_ERROR: + g_dbus_message_to_gerror (reply, error); + break; + + default: + g_assert_not_reached (); + break; + } + + return result; +} + +/** + * g_dbus_connection_call_finish: + * @connection: A #GDBusConnection. + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_call(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with g_dbus_connection_call(). + * + * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with + * return values. Free with g_variant_unref(). + * + * Since: 2.26 + */ +GVariant * +g_dbus_connection_call_finish (GDBusConnection *connection, + GAsyncResult *res, + GError **error) +{ + GDBusMessage *reply; + GVariant *result; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + result = NULL; + + reply = g_dbus_connection_send_message_with_reply_finish (connection, res, error); + if (reply == NULL) + goto out; + + result = decode_method_reply (reply, error); + + g_object_unref (reply); + + out: + return result; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_connection_call_sync: + * @connection: A #GDBusConnection. + * @bus_name: A unique or well-known bus name. + * @object_path: Path of remote object. + * @interface_name: D-Bus interface to invoke method on. + * @method_name: The name of the method to invoke. + * @parameters: A #GVariant tuple with parameters for the method or %NULL if not passing parameters. + * @flags: Flags from the #GDBusCallFlags enumeration. + * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the @method_name method on the + * @interface_name D-Bus interface on the remote object at + * @object_path owned by @bus_name. + * + * If @connection is closed then the operation will fail with + * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the + * operation will fail with %G_IO_ERROR_CANCELLED. If @parameters + * contains a value not compatible with the D-Bus protocol, the operation + * fails with %G_IO_ERROR_INVALID_ARGUMENT. + * + * If the @parameters #GVariant is floating, it is consumed. + * This allows convenient 'inline' use of g_variant_new(), e.g.: + * |[ + * g_dbus_connection_call_sync (connection, + * "org.freedesktop.StringThings", + * "/org/freedesktop/StringThings", + * "org.freedesktop.StringThings", + * "TwoStrings", + * g_variant_new ("(ss)", + * "Thing One", + * "Thing Two"), + * G_DBUS_CALL_FLAGS_NONE, + * -1, + * NULL, + * &error); + * ]| + * + * The calling thread is blocked until a reply is received. See + * g_dbus_connection_call() for the asynchronous version of + * this method. + * + * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with + * return values. Free with g_variant_unref(). + * + * Since: 2.26 + */ +GVariant * +g_dbus_connection_call_sync (GDBusConnection *connection, + const gchar *bus_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusCallFlags flags, + gint timeout_msec, + GCancellable *cancellable, + GError **error) +{ + GDBusMessage *message; + GDBusMessage *reply; + GVariant *result; + + message = NULL; + reply = NULL; + result = NULL; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (bus_name == NULL || g_dbus_is_name (bus_name), NULL); + g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), NULL); + g_return_val_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name), NULL); + g_return_val_if_fail (method_name != NULL && g_dbus_is_member_name (method_name), NULL); + g_return_val_if_fail (timeout_msec >= 0 || timeout_msec == -1, NULL); + g_return_val_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), NULL); + + message = g_dbus_message_new_method_call (bus_name, + object_path, + interface_name, + method_name); + add_call_flags (message, flags); + if (parameters != NULL) + g_dbus_message_set_body (message, parameters); + + reply = g_dbus_connection_send_message_with_reply_sync (connection, + message, + timeout_msec, + NULL, /* volatile guint32 *out_serial */ + cancellable, + error); + + if (reply == NULL) + goto out; + + result = decode_method_reply (reply, error); + + out: + if (message != NULL) + g_object_unref (message); + if (reply != NULL) + g_object_unref (reply); + + return result; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +struct ExportedSubtree +{ + guint id; + gchar *object_path; + GDBusConnection *connection; + const GDBusSubtreeVTable *vtable; + GDBusSubtreeFlags flags; + + GMainContext *context; + gpointer user_data; + GDestroyNotify user_data_free_func; +}; + +static void +exported_subtree_free (ExportedSubtree *es) +{ + if (es->user_data_free_func != NULL) + /* TODO: push to thread-default mainloop */ + es->user_data_free_func (es->user_data); + + if (es->context != NULL) + g_main_context_unref (es->context); + + g_free (es->object_path); + g_free (es); +} + +/* called without lock held */ +static gboolean +handle_subtree_introspect (GDBusConnection *connection, + ExportedSubtree *es, + GDBusMessage *message) +{ + GString *s; + gboolean handled; + GDBusMessage *reply; + gchar **children; + gboolean is_root; + const gchar *sender; + const gchar *requested_object_path; + const gchar *requested_node; + GPtrArray *interfaces; + guint n; + gchar **subnode_paths; + + handled = FALSE; + + requested_object_path = g_dbus_message_get_path (message); + sender = g_dbus_message_get_sender (message); + is_root = (g_strcmp0 (requested_object_path, es->object_path) == 0); + + s = g_string_new (NULL); + introspect_append_header (s); + + /* Strictly we don't need the children in dynamic mode, but we avoid the + * conditionals to preserve code clarity + */ + children = es->vtable->enumerate (es->connection, + sender, + es->object_path, + es->user_data); + + if (!is_root) + { + requested_node = strrchr (requested_object_path, '/') + 1; + + /* Assert existence of object if we are not dynamic */ + if (!(es->flags & G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES) && + !_g_strv_has_string ((const gchar * const *) children, requested_node)) + goto out; + } + else + { + requested_node = "/"; + } + + interfaces = es->vtable->introspect (es->connection, + sender, + es->object_path, + requested_node, + es->user_data); + if (interfaces != NULL) + { + if (interfaces->len > 0) + { + /* we're in business */ + introspect_append_standard_interfaces (s); + + for (n = 0; n < interfaces->len; n++) + { + const GDBusInterfaceInfo *interface_info = interfaces->pdata[n]; + g_dbus_interface_info_generate_xml (interface_info, 2, s); + } + } + g_ptr_array_unref (interfaces); + } + + /* then include entries from the Subtree for the root */ + if (is_root) + { + for (n = 0; children != NULL && children[n] != NULL; n++) + g_string_append_printf (s, " \n", children[n]); + } + + /* finally include nodes registered below us */ + subnode_paths = g_dbus_connection_list_registered (es->connection, requested_object_path); + for (n = 0; subnode_paths != NULL && subnode_paths[n] != NULL; n++) + g_string_append_printf (s, " \n", subnode_paths[n]); + g_strfreev (subnode_paths); + + g_string_append (s, "\n"); + + reply = g_dbus_message_new_method_reply (message); + g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str)); + g_dbus_connection_send_message (connection, reply, NULL, NULL); + g_object_unref (reply); + + handled = TRUE; + + out: + g_string_free (s, TRUE); + g_strfreev (children); + return handled; +} + +/* called without lock held */ +static gboolean +handle_subtree_method_invocation (GDBusConnection *connection, + ExportedSubtree *es, + GDBusMessage *message) +{ + gboolean handled;; + const gchar *sender; + const gchar *interface_name; + const gchar *member; + const gchar *signature; + const gchar *requested_object_path; + const gchar *requested_node; + gboolean is_root; + gchar **children; + const GDBusInterfaceInfo *introspection_data; + const GDBusInterfaceVTable *interface_vtable; + gpointer interface_user_data; + guint n; + GPtrArray *interfaces; + gboolean is_property_get; + gboolean is_property_set; + gboolean is_property_get_all; + + handled = FALSE; + interfaces = NULL; + + requested_object_path = g_dbus_message_get_path (message); + sender = g_dbus_message_get_sender (message); + interface_name = g_dbus_message_get_interface (message); + member = g_dbus_message_get_member (message); + signature = g_dbus_message_get_signature (message); + is_root = (g_strcmp0 (requested_object_path, es->object_path) == 0); + + is_property_get = FALSE; + is_property_set = FALSE; + is_property_get_all = FALSE; + if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0) + { + if (g_strcmp0 (member, "Get") == 0 && g_strcmp0 (signature, "ss") == 0) + is_property_get = TRUE; + else if (g_strcmp0 (member, "Set") == 0 && g_strcmp0 (signature, "ssv") == 0) + is_property_set = TRUE; + else if (g_strcmp0 (member, "GetAll") == 0 && g_strcmp0 (signature, "s") == 0) + is_property_get_all = TRUE; + } + + children = es->vtable->enumerate (es->connection, + sender, + es->object_path, + es->user_data); + + if (!is_root) + { + requested_node = strrchr (requested_object_path, '/') + 1; + + /* If not dynamic, skip if requested node is not part of children */ + if (!(es->flags & G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES) && + !_g_strv_has_string ((const gchar * const *) children, requested_node)) + goto out; + } + else + { + requested_node = "/"; + } + + /* get introspection data for the node */ + interfaces = es->vtable->introspect (es->connection, + sender, + requested_object_path, + requested_node, + es->user_data); + g_assert (interfaces != NULL); + introspection_data = NULL; + for (n = 0; n < interfaces->len; n++) + { + const GDBusInterfaceInfo *id_n = (const GDBusInterfaceInfo *) interfaces->pdata[n]; + if (g_strcmp0 (id_n->name, interface_name) == 0) + introspection_data = id_n; + } + + /* dispatch the call if the user wants to handle it */ + if (introspection_data != NULL) + { + /* figure out where to dispatch the method call */ + interface_user_data = NULL; + interface_vtable = es->vtable->dispatch (es->connection, + sender, + es->object_path, + interface_name, + requested_node, + &interface_user_data, + es->user_data); + if (interface_vtable == NULL) + goto out; + + CONNECTION_LOCK (connection); + handled = validate_and_maybe_schedule_method_call (es->connection, + message, + introspection_data, + interface_vtable, + es->context, + interface_user_data); + CONNECTION_UNLOCK (connection); + } + /* handle org.freedesktop.DBus.Properties interface if not explicitly handled */ + else if (is_property_get || is_property_set || is_property_get_all) + { + if (is_property_get) + g_variant_get (g_dbus_message_get_body (message), "(&s&s)", &interface_name, NULL); + else if (is_property_set) + g_variant_get (g_dbus_message_get_body (message), "(&s&sv)", &interface_name, NULL, NULL); + else if (is_property_get_all) + g_variant_get (g_dbus_message_get_body (message), "(&s)", &interface_name, NULL, NULL); + else + g_assert_not_reached (); + + /* see if the object supports this interface at all */ + for (n = 0; n < interfaces->len; n++) + { + const GDBusInterfaceInfo *id_n = (const GDBusInterfaceInfo *) interfaces->pdata[n]; + if (g_strcmp0 (id_n->name, interface_name) == 0) + introspection_data = id_n; + } + + /* Fail with org.freedesktop.DBus.Error.InvalidArgs if the user-code + * claims it won't support the interface + */ + if (introspection_data == NULL) + { + GDBusMessage *reply; + reply = g_dbus_message_new_method_error (message, + "org.freedesktop.DBus.Error.InvalidArgs", + _("No such interface `%s'"), + interface_name); + g_dbus_connection_send_message (es->connection, reply, NULL, NULL); + g_object_unref (reply); + handled = TRUE; + goto out; + } + + /* figure out where to dispatch the property get/set/getall calls */ + interface_user_data = NULL; + interface_vtable = es->vtable->dispatch (es->connection, + sender, + es->object_path, + interface_name, + requested_node, + &interface_user_data, + es->user_data); + if (interface_vtable == NULL) + goto out; + + if (is_property_get || is_property_set) + { + CONNECTION_LOCK (connection); + handled = validate_and_maybe_schedule_property_getset (es->connection, + message, + is_property_get, + introspection_data, + interface_vtable, + es->context, + interface_user_data); + CONNECTION_UNLOCK (connection); + } + else if (is_property_get_all) + { + CONNECTION_LOCK (connection); + handled = validate_and_maybe_schedule_property_get_all (es->connection, + message, + introspection_data, + interface_vtable, + es->context, + interface_user_data); + CONNECTION_UNLOCK (connection); + } + } + + out: + if (interfaces != NULL) + g_ptr_array_unref (interfaces); + g_strfreev (children); + return handled; +} + +typedef struct +{ + GDBusMessage *message; + ExportedSubtree *es; +} SubtreeDeferredData; + +static void +subtree_deferred_data_free (SubtreeDeferredData *data) +{ + g_object_unref (data->message); + g_free (data); +} + +/* called without lock held in the thread where the caller registered the subtree */ +static gboolean +process_subtree_vtable_message_in_idle_cb (gpointer _data) +{ + SubtreeDeferredData *data = _data; + gboolean handled; + + handled = FALSE; + + if (g_strcmp0 (g_dbus_message_get_interface (data->message), "org.freedesktop.DBus.Introspectable") == 0 && + g_strcmp0 (g_dbus_message_get_member (data->message), "Introspect") == 0 && + g_strcmp0 (g_dbus_message_get_signature (data->message), "") == 0) + handled = handle_subtree_introspect (data->es->connection, + data->es, + data->message); + else + handled = handle_subtree_method_invocation (data->es->connection, + data->es, + data->message); + + if (!handled) + { + CONNECTION_LOCK (data->es->connection); + handled = handle_generic_unlocked (data->es->connection, data->message); + CONNECTION_UNLOCK (data->es->connection); + } + + /* if we couldn't handle the request, just bail with the UnknownMethod error */ + if (!handled) + { + GDBusMessage *reply; + reply = g_dbus_message_new_method_error (data->message, + "org.freedesktop.DBus.Error.UnknownMethod", + _("Method `%s' on interface `%s' with signature `%s' does not exist"), + g_dbus_message_get_member (data->message), + g_dbus_message_get_interface (data->message), + g_dbus_message_get_signature (data->message)); + g_dbus_connection_send_message (data->es->connection, reply, NULL, NULL); + g_object_unref (reply); + } + + return FALSE; +} + +/* called in message handler thread with lock held */ +static gboolean +subtree_message_func (GDBusConnection *connection, + ExportedSubtree *es, + GDBusMessage *message) +{ + GSource *idle_source; + SubtreeDeferredData *data; + + data = g_new0 (SubtreeDeferredData, 1); + data->message = g_object_ref (message); + data->es = es; + + /* defer this call to an idle handler in the right thread */ + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_HIGH); + g_source_set_callback (idle_source, + process_subtree_vtable_message_in_idle_cb, + data, + (GDestroyNotify) subtree_deferred_data_free); + g_source_attach (idle_source, es->context); + g_source_unref (idle_source); + + /* since we own the entire subtree, handlers for objects not in the subtree have been + * tried already by libdbus-1 - so we just need to ensure that we're always going + * to reply to the message + */ + return TRUE; +} + +/** + * g_dbus_connection_register_subtree: + * @connection: A #GDBusConnection. + * @object_path: The object path to register the subtree at. + * @vtable: A #GDBusSubtreeVTable to enumerate, introspect and dispatch nodes in the subtree. + * @flags: Flags used to fine tune the behavior of the subtree. + * @user_data: Data to pass to functions in @vtable. + * @user_data_free_func: Function to call when the subtree is unregistered. + * @error: Return location for error or %NULL. + * + * Registers a whole subtree of dynamic objects. + * + * The @enumerate and @introspection functions in @vtable are used to + * convey, to remote callers, what nodes exist in the subtree rooted + * by @object_path. + * + * When handling remote calls into any node in the subtree, first the + * @enumerate function is used to check if the node exists. If the node exists + * or the #G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES flag is set + * the @introspection function is used to check if the node supports the + * requested method. If so, the @dispatch function is used to determine + * where to dispatch the call. The collected #GDBusInterfaceVTable and + * #gpointer will be used to call into the interface vtable for processing + * the request. + * + * All calls into user-provided code will be invoked in the thread-default main + * loop of the thread you are calling this method from. + * + * If an existing subtree is already registered at @object_path or + * then @error is set to #G_IO_ERROR_EXISTS. + * + * Note that it is valid to register regular objects (using + * g_dbus_connection_register_object()) in a subtree registered with + * g_dbus_connection_register_subtree() - if so, the subtree handler + * is tried as the last resort. One way to think about a subtree + * handler is to consider it a fallback handler + * for object paths not registered via g_dbus_connection_register_object() + * or other bindings. + * + * See for an example of how to use this method. + * + * Returns: 0 if @error is set, otherwise a subtree registration id (never 0) + * that can be used with g_dbus_connection_unregister_subtree() . + * + * Since: 2.26 + */ +guint +g_dbus_connection_register_subtree (GDBusConnection *connection, + const gchar *object_path, + const GDBusSubtreeVTable *vtable, + GDBusSubtreeFlags flags, + gpointer user_data, + GDestroyNotify user_data_free_func, + GError **error) +{ + guint ret; + ExportedSubtree *es; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); + g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), 0); + g_return_val_if_fail (vtable != NULL, 0); + g_return_val_if_fail (error == NULL || *error == NULL, 0); + + ret = 0; + + CONNECTION_LOCK (connection); + + es = g_hash_table_lookup (connection->priv->map_object_path_to_es, object_path); + if (es != NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("A subtree is already exported for %s"), + object_path); + goto out; + } + + es = g_new0 (ExportedSubtree, 1); + es->object_path = g_strdup (object_path); + es->connection = connection; + + es->vtable = vtable; + es->flags = flags; + es->id = _global_subtree_registration_id++; /* TODO: overflow etc. */ + es->user_data = user_data; + es->user_data_free_func = user_data_free_func; + es->context = g_main_context_get_thread_default (); + if (es->context != NULL) + g_main_context_ref (es->context); + + g_hash_table_insert (connection->priv->map_object_path_to_es, es->object_path, es); + g_hash_table_insert (connection->priv->map_id_to_es, + GUINT_TO_POINTER (es->id), + es); + + ret = es->id; + + out: + CONNECTION_UNLOCK (connection); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_connection_unregister_subtree: + * @connection: A #GDBusConnection. + * @registration_id: A subtree registration id obtained from g_dbus_connection_register_subtree(). + * + * Unregisters a subtree. + * + * Returns: %TRUE if the subtree was unregistered, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_connection_unregister_subtree (GDBusConnection *connection, + guint registration_id) +{ + ExportedSubtree *es; + gboolean ret; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); + + ret = FALSE; + + CONNECTION_LOCK (connection); + + es = g_hash_table_lookup (connection->priv->map_id_to_es, + GUINT_TO_POINTER (registration_id)); + if (es == NULL) + goto out; + + g_warn_if_fail (g_hash_table_remove (connection->priv->map_id_to_es, GUINT_TO_POINTER (es->id))); + g_warn_if_fail (g_hash_table_remove (connection->priv->map_object_path_to_es, es->object_path)); + + ret = TRUE; + + out: + CONNECTION_UNLOCK (connection); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* must be called with lock held */ +static void +handle_generic_ping_unlocked (GDBusConnection *connection, + const gchar *object_path, + GDBusMessage *message) +{ + GDBusMessage *reply; + reply = g_dbus_message_new_method_reply (message); + g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL); + g_object_unref (reply); +} + +/* must be called with lock held */ +static void +handle_generic_get_machine_id_unlocked (GDBusConnection *connection, + const gchar *object_path, + GDBusMessage *message) +{ + GDBusMessage *reply; + + reply = NULL; + if (connection->priv->machine_id == NULL) + { + GError *error; + error = NULL; + /* TODO: use PACKAGE_LOCALSTATEDIR ? */ + if (!g_file_get_contents ("/var/lib/dbus/machine-id", + &connection->priv->machine_id, + NULL, + &error)) + { + reply = g_dbus_message_new_method_error (message, + "org.freedesktop.DBus.Error.Failed", + _("Unable to load /var/lib/dbus/machine-id: %s"), + error->message); + g_error_free (error); + } + else + { + g_strstrip (connection->priv->machine_id); + /* TODO: validate value */ + } + } + + if (reply == NULL) + { + reply = g_dbus_message_new_method_reply (message); + g_dbus_message_set_body (reply, g_variant_new ("(s)", connection->priv->machine_id)); + } + g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL); + g_object_unref (reply); +} + +/* must be called with lock held */ +static void +handle_generic_introspect_unlocked (GDBusConnection *connection, + const gchar *object_path, + GDBusMessage *message) +{ + guint n; + GString *s; + gchar **registered; + GDBusMessage *reply; + + /* first the header */ + s = g_string_new (NULL); + introspect_append_header (s); + + registered = g_dbus_connection_list_registered_unlocked (connection, object_path); + for (n = 0; registered != NULL && registered[n] != NULL; n++) + g_string_append_printf (s, " \n", registered[n]); + g_strfreev (registered); + g_string_append (s, "\n"); + + reply = g_dbus_message_new_method_reply (message); + g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str)); + g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL); + g_object_unref (reply); + g_string_free (s, TRUE); +} + +/* must be called with lock held */ +static gboolean +handle_generic_unlocked (GDBusConnection *connection, + GDBusMessage *message) +{ + gboolean handled; + const gchar *interface_name; + const gchar *member; + const gchar *signature; + const gchar *path; + + CONNECTION_ENSURE_LOCK (connection); + + handled = FALSE; + + interface_name = g_dbus_message_get_interface (message); + member = g_dbus_message_get_member (message); + signature = g_dbus_message_get_signature (message); + path = g_dbus_message_get_path (message); + + if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Introspectable") == 0 && + g_strcmp0 (member, "Introspect") == 0 && + g_strcmp0 (signature, "") == 0) + { + handle_generic_introspect_unlocked (connection, path, message); + handled = TRUE; + } + else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Peer") == 0 && + g_strcmp0 (member, "Ping") == 0 && + g_strcmp0 (signature, "") == 0) + { + handle_generic_ping_unlocked (connection, path, message); + handled = TRUE; + } + else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Peer") == 0 && + g_strcmp0 (member, "GetMachineId") == 0 && + g_strcmp0 (signature, "") == 0) + { + handle_generic_get_machine_id_unlocked (connection, path, message); + handled = TRUE; + } + + return handled; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* called in message handler thread with lock held */ +static void +distribute_method_call (GDBusConnection *connection, + GDBusMessage *message) +{ + ExportedObject *eo; + ExportedSubtree *es; + const gchar *object_path; + const gchar *interface_name; + const gchar *member; + const gchar *signature; + const gchar *path; + gchar *subtree_path; + gchar *needle; + + g_assert (g_dbus_message_get_message_type (message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL); + + interface_name = g_dbus_message_get_interface (message); + member = g_dbus_message_get_member (message); + signature = g_dbus_message_get_signature (message); + path = g_dbus_message_get_path (message); + subtree_path = g_strdup (path); + needle = strrchr (subtree_path, '/'); + if (needle != NULL && needle != subtree_path) + { + *needle = '\0'; + } + else + { + g_free (subtree_path); + subtree_path = NULL; + } + +#if 0 + g_debug ("interface = `%s'", interface_name); + g_debug ("member = `%s'", member); + g_debug ("signature = `%s'", signature); + g_debug ("path = `%s'", path); + g_debug ("subtree_path = `%s'", subtree_path != NULL ? subtree_path : "N/A"); +#endif + + object_path = g_dbus_message_get_path (message); + g_assert (object_path != NULL); + + eo = g_hash_table_lookup (connection->priv->map_object_path_to_eo, object_path); + if (eo != NULL) + { + if (obj_message_func (connection, eo, message)) + goto out; + } + + es = g_hash_table_lookup (connection->priv->map_object_path_to_es, object_path); + if (es != NULL) + { + if (subtree_message_func (connection, es, message)) + goto out; + } + + if (subtree_path != NULL) + { + es = g_hash_table_lookup (connection->priv->map_object_path_to_es, subtree_path); + if (es != NULL) + { + if (subtree_message_func (connection, es, message)) + goto out; + } + } + + if (handle_generic_unlocked (connection, message)) + goto out; + + /* if we end up here, the message has not been not handled */ + + out: + g_free (subtree_path); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusConnection ** +message_bus_get_singleton (GBusType bus_type, + GError **error) +{ + GDBusConnection **ret; + const gchar *starter_bus; + + ret = NULL; + + switch (bus_type) + { + case G_BUS_TYPE_SESSION: + ret = &the_session_bus; + break; + + case G_BUS_TYPE_SYSTEM: + ret = &the_system_bus; + break; + + case G_BUS_TYPE_STARTER: + starter_bus = g_getenv ("DBUS_STARTER_BUS_TYPE"); + if (g_strcmp0 (starter_bus, "session") == 0) + { + ret = message_bus_get_singleton (G_BUS_TYPE_SESSION, error); + goto out; + } + else if (g_strcmp0 (starter_bus, "system") == 0) + { + ret = message_bus_get_singleton (G_BUS_TYPE_SYSTEM, error); + goto out; + } + else + { + if (starter_bus != NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Cannot determine bus address from DBUS_STARTER_BUS_TYPE environment variable" + " - unknown value `%s'"), + starter_bus); + } + else + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Cannot determine bus address because the DBUS_STARTER_BUS_TYPE environment " + "variable is not set")); + } + } + break; + + default: + g_assert_not_reached (); + break; + } + + out: + return ret; +} + +static GDBusConnection * +get_uninitialized_connection (GBusType bus_type, + GCancellable *cancellable, + GError **error) +{ + GDBusConnection **singleton; + GDBusConnection *ret; + + ret = NULL; + + G_LOCK (message_bus_lock); + singleton = message_bus_get_singleton (bus_type, error); + if (singleton == NULL) + goto out; + + if (*singleton == NULL) + { + gchar *address; + address = g_dbus_address_get_for_bus_sync (bus_type, cancellable, error); + if (address == NULL) + goto out; + ret = *singleton = g_object_new (G_TYPE_DBUS_CONNECTION, + "address", address, + "flags", G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + "exit-on-close", TRUE, + NULL); + } + else + { + ret = g_object_ref (*singleton); + } + + g_assert (ret != NULL); + + out: + G_UNLOCK (message_bus_lock); + return ret; +} + +/** + * g_bus_get_sync: + * @bus_type: A #GBusType. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously connects to the message bus specified by @bus_type. + * Note that the returned object may shared with other callers, + * e.g. if two separate parts of a process calls this function with + * the same @bus_type, they will share the same object. + * + * This is a synchronous failable function. See g_bus_get() and + * g_bus_get_finish() for the asynchronous version. + * + * The returned object is a singleton, that is, shared with other + * callers of g_bus_get() and g_bus_get_sync() for @bus_type. In the + * event that you need a private message bus connection, use + * g_dbus_address_get_for_bus_sync() and + * g_dbus_connection_new_for_address(). + * + * Note that the returned #GDBusConnection object will (usually) have + * the #GDBusConnection:exit-on-close property set to %TRUE. + * + * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusConnection * +g_bus_get_sync (GBusType bus_type, + GCancellable *cancellable, + GError **error) +{ + GDBusConnection *connection; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + connection = get_uninitialized_connection (bus_type, cancellable, error); + if (connection == NULL) + goto out; + + if (!g_initable_init (G_INITABLE (connection), cancellable, error)) + { + g_object_unref (connection); + connection = NULL; + } + + out: + return connection; +} + +static void +bus_get_async_initable_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); + GError *error; + + error = NULL; + if (!g_async_initable_init_finish (G_ASYNC_INITABLE (source_object), + res, + &error)) + { + g_assert (error != NULL); + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + g_object_unref (source_object); + } + else + { + g_simple_async_result_set_op_res_gpointer (simple, + source_object, + g_object_unref); + } + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} + +/** + * g_bus_get: + * @bus_type: A #GBusType. + * @cancellable: A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied. + * @user_data: The data to pass to @callback. + * + * Asynchronously connects to the message bus specified by @bus_type. + * + * When the operation is finished, @callback will be invoked. You can + * then call g_bus_get_finish() to get the result of the operation. + * + * This is a asynchronous failable function. See g_bus_get_sync() for + * the synchronous version. + * + * Since: 2.26 + */ +void +g_bus_get (GBusType bus_type, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GDBusConnection *connection; + GSimpleAsyncResult *simple; + GError *error; + + simple = g_simple_async_result_new (NULL, + callback, + user_data, + g_bus_get); + + error = NULL; + connection = get_uninitialized_connection (bus_type, cancellable, &error); + if (connection == NULL) + { + g_assert (error != NULL); + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + } + else + { + g_async_initable_init_async (G_ASYNC_INITABLE (connection), + G_PRIORITY_DEFAULT, + cancellable, + bus_get_async_initable_cb, + simple); + } +} + +/** + * g_bus_get_finish: + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_bus_get(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with g_bus_get(). + * + * The returned object is a singleton, that is, shared with other + * callers of g_bus_get() and g_bus_get_sync() for @bus_type. In the + * event that you need a private message bus connection, use + * g_dbus_address_get_for_bus() and + * g_dbus_connection_new_for_address(). + * + * Note that the returned #GDBusConnection object will (usually) have + * the #GDBusConnection:exit-on-close property set to %TRUE. + * + * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusConnection * +g_bus_get_finish (GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + GObject *object; + GDBusConnection *ret; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_bus_get); + + ret = NULL; + + if (g_simple_async_result_propagate_error (simple, error)) + goto out; + + object = g_simple_async_result_get_op_res_gpointer (simple); + g_assert (object != NULL); + ret = g_object_ref (G_DBUS_CONNECTION (object)); + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#define __G_DBUS_CONNECTION_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusconnection.h b/gio/gdbusconnection.h new file mode 100644 index 000000000..42ba47676 --- /dev/null +++ b/gio/gdbusconnection.h @@ -0,0 +1,492 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_CONNECTION_H__ +#define __G_DBUS_CONNECTION_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_DBUS_CONNECTION (g_dbus_connection_get_type ()) +#define G_DBUS_CONNECTION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_CONNECTION, GDBusConnection)) +#define G_DBUS_CONNECTION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_CONNECTION, GDBusConnectionClass)) +#define G_DBUS_CONNECTION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_CONNECTION, GDBusConnectionClass)) +#define G_IS_DBUS_CONNECTION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_CONNECTION)) +#define G_IS_DBUS_CONNECTION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_CONNECTION)) + +typedef struct _GDBusConnectionClass GDBusConnectionClass; +typedef struct _GDBusConnectionPrivate GDBusConnectionPrivate; + +/** + * GDBusConnection: + * + * The #GDBusConnection structure contains only private data and + * should only be accessed using the provided API. + * + * Since: 2.26 + */ +struct _GDBusConnection +{ + /*< private >*/ + GObject parent_instance; + GDBusConnectionPrivate *priv; +}; + +/** + * GDBusConnectionClass: + * @closed: Signal class handler for the #GDBusConnection::closed signal. + * + * Class structure for #GDBusConnection. + * + * Since: 2.26 + */ +struct _GDBusConnectionClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + /* Signals */ + void (*closed) (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error); + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); +}; + +GType g_dbus_connection_get_type (void) G_GNUC_CONST; + +/* ---------------------------------------------------------------------------------------------------- */ + +void g_bus_get (GBusType bus_type, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GDBusConnection *g_bus_get_finish (GAsyncResult *res, + GError **error); +GDBusConnection *g_bus_get_sync (GBusType bus_type, + GCancellable *cancellable, + GError **error); + +/* ---------------------------------------------------------------------------------------------------- */ + +void g_dbus_connection_new (GIOStream *stream, + const gchar *guid, + GDBusConnectionFlags flags, + GDBusAuthObserver *observer, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GDBusConnection *g_dbus_connection_new_finish (GAsyncResult *res, + GError **error); +GDBusConnection *g_dbus_connection_new_sync (GIOStream *stream, + const gchar *guid, + GDBusConnectionFlags flags, + GDBusAuthObserver *observer, + GCancellable *cancellable, + GError **error); + +void g_dbus_connection_new_for_address (const gchar *address, + GDBusConnectionFlags flags, + GDBusAuthObserver *observer, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GDBusConnection *g_dbus_connection_new_for_address_finish (GAsyncResult *res, + GError **error); +GDBusConnection *g_dbus_connection_new_for_address_sync (const gchar *address, + GDBusConnectionFlags flags, + GDBusAuthObserver *observer, + GCancellable *cancellable, + GError **error); + +/* ---------------------------------------------------------------------------------------------------- */ + +gboolean g_dbus_connection_is_closed (GDBusConnection *connection); +void g_dbus_connection_close (GDBusConnection *connection); +GIOStream *g_dbus_connection_get_stream (GDBusConnection *connection); +const gchar *g_dbus_connection_get_guid (GDBusConnection *connection); +const gchar *g_dbus_connection_get_unique_name (GDBusConnection *connection); +GCredentials *g_dbus_connection_get_peer_credentials (GDBusConnection *connection); +gboolean g_dbus_connection_get_exit_on_close (GDBusConnection *connection); +void g_dbus_connection_set_exit_on_close (GDBusConnection *connection, + gboolean exit_on_close); +GDBusCapabilityFlags g_dbus_connection_get_capabilities (GDBusConnection *connection); +/* ---------------------------------------------------------------------------------------------------- */ + +gboolean g_dbus_connection_send_message (GDBusConnection *connection, + GDBusMessage *message, + volatile guint32 *out_serial, + GError **error); +void g_dbus_connection_send_message_with_reply (GDBusConnection *connection, + GDBusMessage *message, + gint timeout_msec, + volatile guint32 *out_serial, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GDBusMessage *g_dbus_connection_send_message_with_reply_finish (GDBusConnection *connection, + GAsyncResult *res, + GError **error); +GDBusMessage *g_dbus_connection_send_message_with_reply_sync (GDBusConnection *connection, + GDBusMessage *message, + gint timeout_msec, + volatile guint32 *out_serial, + GCancellable *cancellable, + GError **error); + +/* ---------------------------------------------------------------------------------------------------- */ + +gboolean g_dbus_connection_emit_signal (GDBusConnection *connection, + const gchar *destination_bus_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + GError **error); +void g_dbus_connection_call (GDBusConnection *connection, + const gchar *bus_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusCallFlags flags, + gint timeout_msec, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GVariant *g_dbus_connection_call_finish (GDBusConnection *connection, + GAsyncResult *res, + GError **error); +GVariant *g_dbus_connection_call_sync (GDBusConnection *connection, + const gchar *bus_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusCallFlags flags, + gint timeout_msec, + GCancellable *cancellable, + GError **error); + +/* ---------------------------------------------------------------------------------------------------- */ + + +/** + * GDBusInterfaceMethodCallFunc: + * @connection: A #GDBusConnection. + * @sender: The unique bus name of the remote caller. + * @object_path: The object path that the method was invoked on. + * @interface_name: The D-Bus interface name the method was invoked on. + * @method_name: The name of the method that was invoked. + * @parameters: A #GVariant tuple with parameters. + * @invocation: A #GDBusMethodInvocation object that can be used to return a value or error. + * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object(). + * + * The type of the @method_call function in #GDBusInterfaceVTable. + * + * Since: 2.26 + */ +typedef void (*GDBusInterfaceMethodCallFunc) (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data); + +/** + * GDBusInterfaceGetPropertyFunc: + * @connection: A #GDBusConnection. + * @sender: The unique bus name of the remote caller. + * @object_path: The object path that the method was invoked on. + * @interface_name: The D-Bus interface name for the property. + * @property_name: The name of the property to get the value of. + * @error: Return location for error. + * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object(). + * + * The type of the @get_property function in #GDBusInterfaceVTable. + * + * Returns: A newly-allocated #GVariant with the value for @property_name or %NULL if @error is set. + * + * Since: 2.26 + */ +typedef GVariant *(*GDBusInterfaceGetPropertyFunc) (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data); + +/** + * GDBusInterfaceSetPropertyFunc: + * @connection: A #GDBusConnection. + * @sender: The unique bus name of the remote caller. + * @object_path: The object path that the method was invoked on. + * @interface_name: The D-Bus interface name for the property. + * @property_name: The name of the property to get the value of. + * @value: The value to set the property to. + * @error: Return location for error. + * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object(). + * + * The type of the @set_property function in #GDBusInterfaceVTable. + * + * Returns: %TRUE if the property was set to @value, %FALSE if @error is set. + * + * Since: 2.26 + */ +typedef gboolean (*GDBusInterfaceSetPropertyFunc) (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, + gpointer user_data); + +/** + * GDBusInterfaceVTable: + * @method_call: Function for handling incoming method calls. + * @get_property: Function for getting a property. + * @set_property: Function for setting a property. + * + * Virtual table for handling properties and method calls for a D-Bus + * interface. + * + * If you want to handle getting/setting D-Bus properties asynchronously, simply + * register an object with the org.freedesktop.DBus.Properties + * D-Bus interface using g_dbus_connection_register_object(). + * + * Since: 2.26 + */ +struct _GDBusInterfaceVTable +{ + GDBusInterfaceMethodCallFunc method_call; + GDBusInterfaceGetPropertyFunc get_property; + GDBusInterfaceSetPropertyFunc set_property; + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); +}; + +guint g_dbus_connection_register_object (GDBusConnection *connection, + const gchar *object_path, + const GDBusInterfaceInfo *introspection_data, + const GDBusInterfaceVTable *vtable, + gpointer user_data, + GDestroyNotify user_data_free_func, + GError **error); +gboolean g_dbus_connection_unregister_object (GDBusConnection *connection, + guint registration_id); + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * GDBusSubtreeEnumerateFunc: + * @connection: A #GDBusConnection. + * @sender: The unique bus name of the remote caller. + * @object_path: The object path that was registered with g_dbus_connection_register_subtree(). + * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_subtree(). + * + * The type of the @enumerate function in #GDBusSubtreeVTable. + * + * Returns: A newly allocated array of strings for node names that are children of @object_path. + * + * Since: 2.26 + */ +typedef gchar** (*GDBusSubtreeEnumerateFunc) (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + gpointer user_data); + +/** + * GDBusSubtreeIntrospectFunc: + * @connection: A #GDBusConnection. + * @sender: The unique bus name of the remote caller. + * @object_path: The object path that was registered with g_dbus_connection_register_subtree(). + * @node: A node that is a child of @object_path (relative to @object_path) or / for the root of the subtree. + * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_subtree(). + * + * The type of the @introspect function in #GDBusSubtreeVTable. + * + * Returns: A newly-allocated #GPtrArray with pointers to #GDBusInterfaceInfo describing + * the interfaces implemented by @node. + * + * Since: 2.26 + */ +typedef GPtrArray *(*GDBusSubtreeIntrospectFunc) (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *node, + gpointer user_data); + +/** + * GDBusSubtreeDispatchFunc: + * @connection: A #GDBusConnection. + * @sender: The unique bus name of the remote caller. + * @object_path: The object path that was registered with g_dbus_connection_register_subtree(). + * @interface_name: The D-Bus interface name that the method call or property access is for. + * @node: A node that is a child of @object_path (relative to @object_path) or / for the root of the subtree. + * @out_user_data: Return location for user data to pass to functions in the returned #GDBusInterfaceVTable (never %NULL). + * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_subtree(). + * + * The type of the @dispatch function in #GDBusSubtreeVTable. + * + * Returns: A #GDBusInterfaceVTable or %NULL if you don't want to handle the methods. + * + * Since: 2.26 + */ +typedef const GDBusInterfaceVTable * (*GDBusSubtreeDispatchFunc) (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *node, + gpointer *out_user_data, + gpointer user_data); + +/** + * GDBusSubtreeVTable: + * @enumerate: Function for enumerating child nodes. + * @introspect: Function for introspecting a child node. + * @dispatch: Function for dispatching a remote call on a child node. + * + * Virtual table for handling subtrees registered with g_dbus_connection_register_subtree(). + * + * Since: 2.26 + */ +struct _GDBusSubtreeVTable +{ + GDBusSubtreeEnumerateFunc enumerate; + GDBusSubtreeIntrospectFunc introspect; + GDBusSubtreeDispatchFunc dispatch; + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); +}; + +guint g_dbus_connection_register_subtree (GDBusConnection *connection, + const gchar *object_path, + const GDBusSubtreeVTable *vtable, + GDBusSubtreeFlags flags, + gpointer user_data, + GDestroyNotify user_data_free_func, + GError **error); +gboolean g_dbus_connection_unregister_subtree (GDBusConnection *connection, + guint registration_id); + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * GDBusSignalCallback: + * @connection: A #GDBusConnection. + * @sender_name: The unique bus name of the sender of the signal. + * @object_path: The object path that the signal was emitted on. + * @interface_name: The name of the signal. + * @signal_name: The name of the signal. + * @parameters: A #GVariant tuple with parameters for the signal. + * @user_data: User data passed when subscribing to the signal. + * + * Signature for callback function used in g_dbus_connection_signal_subscribe(). + * + * Since: 2.26 + */ +typedef void (*GDBusSignalCallback) (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data); + +guint g_dbus_connection_signal_subscribe (GDBusConnection *connection, + const gchar *sender, + const gchar *interface_name, + const gchar *member, + const gchar *object_path, + const gchar *arg0, + GDBusSignalCallback callback, + gpointer user_data, + GDestroyNotify user_data_free_func); +void g_dbus_connection_signal_unsubscribe (GDBusConnection *connection, + guint subscription_id); + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * GDBusMessageFilterFunction: + * @connection: A #GDBusConnection. + * @message: A #GDBusMessage. + * @user_data: User data passed when adding the filter. + * + * Signature for function used in g_dbus_connection_add_filter(). + * + * Returns: %TRUE if the filter handled @message, %FALSE to let other + * handlers run. + * + * Since: 2.26 + */ +typedef gboolean (*GDBusMessageFilterFunction) (GDBusConnection *connection, + GDBusMessage *message, + gpointer user_data); + +guint g_dbus_connection_add_filter (GDBusConnection *connection, + GDBusMessageFilterFunction filter_function, + gpointer user_data, + GDestroyNotify user_data_free_func); + +void g_dbus_connection_remove_filter (GDBusConnection *connection, + guint filter_id); + +/* ---------------------------------------------------------------------------------------------------- */ + + +G_END_DECLS + +#endif /* __G_DBUS_CONNECTION_H__ */ diff --git a/gio/gdbuserror.c b/gio/gdbuserror.c new file mode 100644 index 000000000..a6e552b0f --- /dev/null +++ b/gio/gdbuserror.c @@ -0,0 +1,873 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include +#include + +#include "gdbuserror.h" +#include "gioenums.h" +#include "gioenumtypes.h" +#include "gioerror.h" +#include "gdbusprivate.h" + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbuserror + * @title: GDBusError + * @short_description: Mapping D-Bus errors to and from #GError + * @include: gio/gio.h + * + * All facilities that return errors from remote methods (such as + * g_dbus_connection_call_sync()) use #GError to represent both D-Bus + * errors (e.g. errors returned from the other peer) and locally + * in-process generated errors. + * + * To check if a returned #GError is an error from a remote peer, use + * g_dbus_error_is_remote_error(). To get the actual D-Bus error name, + * use g_dbus_error_get_remote_error(). Before presenting an error, + * always use g_dbus_error_strip_remote_error(). + * + * In addition, facilities used to return errors to a remote peer also + * use #GError. See g_dbus_method_invocation_return_error() for + * discussion about how the D-Bus error name is set. + * + * Applications can associate a #GError error domain with a set of D-Bus errors in order to + * automatically map from D-Bus errors to #GError and back. This + * is typically done in the function returning the #GQuark for the + * error domain: + * Error Registration + * /* foo-bar-error.h: */ + * + * #define FOO_BAR_ERROR (foo_bar_error_quark ()) + * GQuark foo_bar_error_quark (void); + * + * typedef enum + * { + * FOO_BAR_ERROR_FAILED, + * FOO_BAR_ERROR_ANOTHER_ERROR, + * FOO_BAR_ERROR_SOME_THIRD_ERROR, + * } FooBarError; + * + * /* foo-bar-error.c: */ + * + * static const GDBusErrorEntry foo_bar_error_entries[] = + * { + * {FOO_BAR_ERROR_FAILED, "org.project.Foo.Bar.Error.Failed"}, + * {FOO_BAR_ERROR_ANOTHER_ERROR, "org.project.Foo.Bar.Error.AnotherError"}, + * {FOO_BAR_ERROR_SOME_THIRD_ERROR, "org.project.Foo.Bar.Error.SomeThirdError"}, + * }; + * + * GQuark + * foo_bar_error_quark (void) + * { + * static volatile gsize quark_volatile = 0; + * g_dbus_error_register_error_domain ("foo-bar-error-quark", + * &quark_volatile, + * foo_bar_error_entries, + * G_N_ELEMENTS (foo_bar_error_entries)); + * G_STATIC_ASSERT (G_N_ELEMENTS (foo_bar_error_entries) - 1 == FOO_BAR_ERROR_SOME_THIRD_ERROR); + * return (GQuark) quark_volatile; + * } + * + * With this setup, a D-Bus peer can transparently pass e.g. %FOO_BAR_ERROR_ANOTHER_ERROR and + * other peers will see the D-Bus error name org.project.Foo.Bar.Error.AnotherError. + * If the other peer is using GDBus, the peer will see also %FOO_BAR_ERROR_ANOTHER_ERROR instead + * of %G_IO_ERROR_DBUS_ERROR. Note that GDBus clients can still recover + * org.project.Foo.Bar.Error.AnotherError using g_dbus_error_get_remote_error(). + * + * Note that errors in the %G_DBUS_ERROR error domain is intended only + * for returning errors from a remote message bus process. Errors + * generated locally in-process by e.g. #GDBusConnection is from the + * %G_IO_ERROR domain. + */ + +static const GDBusErrorEntry g_dbus_error_entries[] = +{ + {G_DBUS_ERROR_FAILED, "org.freedesktop.DBus.Error.Failed"}, + {G_DBUS_ERROR_NO_MEMORY, "org.freedesktop.DBus.Error.NoMemory"}, + {G_DBUS_ERROR_SERVICE_UNKNOWN, "org.freedesktop.DBus.Error.ServiceUnknown"}, + {G_DBUS_ERROR_NAME_HAS_NO_OWNER, "org.freedesktop.DBus.Error.NameHasNoOwner"}, + {G_DBUS_ERROR_NO_REPLY, "org.freedesktop.DBus.Error.NoReply"}, + {G_DBUS_ERROR_IO_ERROR, "org.freedesktop.DBus.Error.IOError"}, + {G_DBUS_ERROR_BAD_ADDRESS, "org.freedesktop.DBus.Error.BadAddress"}, + {G_DBUS_ERROR_NOT_SUPPORTED, "org.freedesktop.DBus.Error.NotSupported"}, + {G_DBUS_ERROR_LIMITS_EXCEEDED, "org.freedesktop.DBus.Error.LimitsExceeded"}, + {G_DBUS_ERROR_ACCESS_DENIED, "org.freedesktop.DBus.Error.AccessDenied"}, + {G_DBUS_ERROR_AUTH_FAILED, "org.freedesktop.DBus.Error.AuthFailed"}, + {G_DBUS_ERROR_NO_SERVER, "org.freedesktop.DBus.Error.NoServer"}, + {G_DBUS_ERROR_TIMEOUT, "org.freedesktop.DBus.Error.Timeout"}, + {G_DBUS_ERROR_NO_NETWORK, "org.freedesktop.DBus.Error.NoNetwork"}, + {G_DBUS_ERROR_ADDRESS_IN_USE, "org.freedesktop.DBus.Error.AddressInUse"}, + {G_DBUS_ERROR_DISCONNECTED, "org.freedesktop.DBus.Error.Disconnected"}, + {G_DBUS_ERROR_INVALID_ARGS, "org.freedesktop.DBus.Error.InvalidArgs"}, + {G_DBUS_ERROR_FILE_NOT_FOUND, "org.freedesktop.DBus.Error.FileNotFound"}, + {G_DBUS_ERROR_FILE_EXISTS, "org.freedesktop.DBus.Error.FileExists"}, + {G_DBUS_ERROR_UNKNOWN_METHOD, "org.freedesktop.DBus.Error.UnknownMethod"}, + {G_DBUS_ERROR_TIMED_OUT, "org.freedesktop.DBus.Error.TimedOut"}, + {G_DBUS_ERROR_MATCH_RULE_NOT_FOUND, "org.freedesktop.DBus.Error.MatchRuleNotFound"}, + {G_DBUS_ERROR_MATCH_RULE_INVALID, "org.freedesktop.DBus.Error.MatchRuleInvalid"}, + {G_DBUS_ERROR_SPAWN_EXEC_FAILED, "org.freedesktop.DBus.Error.Spawn.ExecFailed"}, + {G_DBUS_ERROR_SPAWN_FORK_FAILED, "org.freedesktop.DBus.Error.Spawn.ForkFailed"}, + {G_DBUS_ERROR_SPAWN_CHILD_EXITED, "org.freedesktop.DBus.Error.Spawn.ChildExited"}, + {G_DBUS_ERROR_SPAWN_CHILD_SIGNALED, "org.freedesktop.DBus.Error.Spawn.ChildSignaled"}, + {G_DBUS_ERROR_SPAWN_FAILED, "org.freedesktop.DBus.Error.Spawn.Failed"}, + {G_DBUS_ERROR_SPAWN_SETUP_FAILED, "org.freedesktop.DBus.Error.Spawn.FailedToSetup"}, + {G_DBUS_ERROR_SPAWN_CONFIG_INVALID, "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"}, + {G_DBUS_ERROR_SPAWN_SERVICE_INVALID, "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"}, + {G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"}, + {G_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"}, + {G_DBUS_ERROR_SPAWN_FILE_INVALID, "org.freedesktop.DBus.Error.Spawn.FileInvalid"}, + {G_DBUS_ERROR_SPAWN_NO_MEMORY, "org.freedesktop.DBus.Error.Spawn.NoMemory"}, + {G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, "org.freedesktop.DBus.Error.UnixProcessIdUnknown"}, + {G_DBUS_ERROR_INVALID_SIGNATURE, "org.freedesktop.DBus.Error.InvalidSignature"}, + {G_DBUS_ERROR_INVALID_FILE_CONTENT, "org.freedesktop.DBus.Error.InvalidFileContent"}, + {G_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"}, + {G_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN, "org.freedesktop.DBus.Error.AdtAuditDataUnknown"}, + {G_DBUS_ERROR_OBJECT_PATH_IN_USE, "org.freedesktop.DBus.Error.ObjectPathInUse"}, +}; + +GQuark +g_dbus_error_quark (void) +{ + static volatile gsize quark_volatile = 0; + g_dbus_error_register_error_domain ("g-dbus-error-quark", + &quark_volatile, + g_dbus_error_entries, + G_N_ELEMENTS (g_dbus_error_entries)); + G_STATIC_ASSERT (G_N_ELEMENTS (g_dbus_error_entries) - 1 == G_DBUS_ERROR_OBJECT_PATH_IN_USE); + return (GQuark) quark_volatile; +} + +/** + * g_dbus_error_register_error_domain: + * @error_domain_quark_name: The error domain name. + * @quark_volatile: A pointer where to store the #GQuark. + * @entries: A pointer to @num_entries #GDBusErrorEntry struct items. + * @num_entries: Number of items to register. + * + * Helper function for associating a #GError error domain with D-Bus error names. + * + * Since: 2.26 + */ +void +g_dbus_error_register_error_domain (const gchar *error_domain_quark_name, + volatile gsize *quark_volatile, + const GDBusErrorEntry *entries, + guint num_entries) +{ + g_return_if_fail (error_domain_quark_name != NULL); + g_return_if_fail (quark_volatile != NULL); + g_return_if_fail (entries != NULL); + g_return_if_fail (num_entries > 0); + + if (g_once_init_enter (quark_volatile)) + { + guint n; + GQuark quark; + + quark = g_quark_from_static_string (error_domain_quark_name); + + for (n = 0; n < num_entries; n++) + { + g_warn_if_fail (g_dbus_error_register_error (quark, + entries[n].error_code, + entries[n].dbus_error_name)); + } + g_once_init_leave (quark_volatile, quark); + } +} + +static gboolean +_g_dbus_error_decode_gerror (const gchar *dbus_name, + GQuark *out_error_domain, + gint *out_error_code) +{ + gboolean ret; + guint n; + GString *s; + gchar *domain_quark_string; + + ret = FALSE; + s = NULL; + + if (g_str_has_prefix (dbus_name, "org.gtk.GDBus.UnmappedGError.Quark._")) + { + s = g_string_new (NULL); + + for (n = sizeof "org.gtk.GDBus.UnmappedGError.Quark._" - 1; + dbus_name[n] != '.' && dbus_name[n] != '\0'; + n++) + { + if (g_ascii_isalnum (dbus_name[n])) + { + g_string_append_c (s, dbus_name[n]); + } + else if (dbus_name[n] == '_') + { + guint nibble_top; + guint nibble_bottom; + + n++; + + nibble_top = dbus_name[n]; + if (nibble_top >= '0' && nibble_top <= '9') + nibble_top -= '0'; + else if (nibble_top >= 'a' && nibble_top <= 'f') + nibble_top -= ('a' - 10); + else + goto not_mapped; + + n++; + + nibble_bottom = dbus_name[n]; + if (nibble_bottom >= '0' && nibble_bottom <= '9') + nibble_bottom -= '0'; + else if (nibble_bottom >= 'a' && nibble_bottom <= 'f') + nibble_bottom -= ('a' - 10); + else + goto not_mapped; + + g_string_append_c (s, (nibble_top<<4) | nibble_bottom); + } + else + { + goto not_mapped; + } + } + + if (!g_str_has_prefix (dbus_name + n, ".Code")) + goto not_mapped; + + domain_quark_string = g_string_free (s, FALSE); + s = NULL; + + if (out_error_domain != NULL) + *out_error_domain = g_quark_from_string (domain_quark_string); + g_free (domain_quark_string); + + if (out_error_code != NULL) + *out_error_code = atoi (dbus_name + n + sizeof ".Code" - 1); + + ret = TRUE; + } + + not_mapped: + + if (s != NULL) + g_string_free (s, TRUE); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GQuark error_domain; + gint error_code; +} QuarkCodePair; + +static guint +quark_code_pair_hash_func (const QuarkCodePair *pair) +{ + gint val; + val = pair->error_domain + pair->error_code; + return g_int_hash (&val); +} + +static gboolean +quark_code_pair_equal_func (const QuarkCodePair *a, + const QuarkCodePair *b) +{ + return (a->error_domain == b->error_domain) && (a->error_code == b->error_code); +} + +typedef struct +{ + QuarkCodePair pair; + gchar *dbus_error_name; +} RegisteredError; + +static void +registered_error_free (RegisteredError *re) +{ + g_free (re->dbus_error_name); + g_free (re); +} + +G_LOCK_DEFINE_STATIC (error_lock); + +/* maps from QuarkCodePair* -> RegisteredError* */ +static GHashTable *quark_code_pair_to_re = NULL; + +/* maps from gchar* -> RegisteredError* */ +static GHashTable *dbus_error_name_to_re = NULL; + +/** + * g_dbus_error_register_error: + * @error_domain: A #GQuark for a error domain. + * @error_code: An error code. + * @dbus_error_name: A D-Bus error name. + * + * Creates an association to map between @dbus_error_name and + * #GErrors specified by @error_domain and @error_code. + * + * This is typically done in the routine that returns the #GQuark for + * an error domain. + * + * Returns: %TRUE if the association was created, %FALSE if it already + * exists. + * + * Since: 2.26 + */ +gboolean +g_dbus_error_register_error (GQuark error_domain, + gint error_code, + const gchar *dbus_error_name) +{ + gboolean ret; + QuarkCodePair pair; + RegisteredError *re; + + g_return_val_if_fail (dbus_error_name != NULL, FALSE); + + ret = FALSE; + + G_LOCK (error_lock); + + if (quark_code_pair_to_re == NULL) + { + g_assert (dbus_error_name_to_re == NULL); /* check invariant */ + quark_code_pair_to_re = g_hash_table_new ((GHashFunc) quark_code_pair_hash_func, + (GEqualFunc) quark_code_pair_equal_func); + dbus_error_name_to_re = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) registered_error_free); + } + + if (g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name) != NULL) + goto out; + + pair.error_domain = error_domain; + pair.error_code = error_code; + + if (g_hash_table_lookup (quark_code_pair_to_re, &pair) != NULL) + goto out; + + re = g_new0 (RegisteredError, 1); + re->pair = pair; + re->dbus_error_name = g_strdup (dbus_error_name); + + g_hash_table_insert (quark_code_pair_to_re, &(re->pair), re); + g_hash_table_insert (dbus_error_name_to_re, re->dbus_error_name, re); + + ret = TRUE; + + out: + G_UNLOCK (error_lock); + return ret; +} + +/** + * g_dbus_error_unregister_error: + * @error_domain: A #GQuark for a error domain. + * @error_code: An error code. + * @dbus_error_name: A D-Bus error name. + * + * Destroys an association previously set up with g_dbus_error_register_error(). + * + * Returns: %TRUE if the association was destroyed, %FALSE if it wasn't found. + * + * Since: 2.26 + */ +gboolean +g_dbus_error_unregister_error (GQuark error_domain, + gint error_code, + const gchar *dbus_error_name) +{ + gboolean ret; + RegisteredError *re; + guint hash_size; + + g_return_val_if_fail (dbus_error_name != NULL, FALSE); + + ret = FALSE; + + G_LOCK (error_lock); + + if (dbus_error_name_to_re == NULL) + { + g_assert (quark_code_pair_to_re == NULL); /* check invariant */ + goto out; + } + + re = g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name); + if (re == NULL) + { + QuarkCodePair pair; + pair.error_domain = error_domain; + pair.error_code = error_code; + g_warn_if_fail (g_hash_table_lookup (quark_code_pair_to_re, &pair) == NULL); /* check invariant */ + goto out; + } + g_warn_if_fail (g_hash_table_lookup (quark_code_pair_to_re, &(re->pair)) == re); /* check invariant */ + + g_warn_if_fail (g_hash_table_remove (quark_code_pair_to_re, &(re->pair))); + g_warn_if_fail (g_hash_table_remove (dbus_error_name_to_re, re)); + + /* destroy hashes if empty */ + hash_size = g_hash_table_size (dbus_error_name_to_re); + if (hash_size == 0) + { + g_warn_if_fail (g_hash_table_size (quark_code_pair_to_re) == 0); /* check invariant */ + + g_hash_table_unref (dbus_error_name_to_re); + dbus_error_name_to_re = NULL; + g_hash_table_unref (quark_code_pair_to_re); + quark_code_pair_to_re = NULL; + } + else + { + g_warn_if_fail (g_hash_table_size (quark_code_pair_to_re) == hash_size); /* check invariant */ + } + + out: + G_UNLOCK (error_lock); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_error_is_remote_error: + * @error: A #GError. + * + * Checks if @error represents an error received via D-Bus from a remote peer. If so, + * use g_dbus_error_get_remote_error() to get the name of the error. + * + * Returns: %TRUE if @error represents an error from a remote peer, + * %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_error_is_remote_error (const GError *error) +{ + g_return_val_if_fail (error != NULL, FALSE); + return g_str_has_prefix (error->message, "GDBus.Error:"); +} + + +/** + * g_dbus_error_get_remote_error: + * @error: A #GError. + * + * Gets the D-Bus error name used for @error, if any. + * + * This function is guaranteed to return a D-Bus error name for all + * #GErrors returned from functions handling remote method + * calls (e.g. g_dbus_connection_call_finish()) unless + * g_dbus_error_strip_remote_error() has been used on @error. + * + * Returns: An allocated string or %NULL if the D-Bus error name could not be found. Free with g_free(). + * + * Since: 2.26 + */ +gchar * +g_dbus_error_get_remote_error (const GError *error) +{ + RegisteredError *re; + gchar *ret; + + g_return_val_if_fail (error != NULL, NULL); + + /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */ + _g_dbus_initialize (); + + ret = NULL; + + G_LOCK (error_lock); + + re = NULL; + if (quark_code_pair_to_re != NULL) + { + QuarkCodePair pair; + pair.error_domain = error->domain; + pair.error_code = error->code; + g_assert (dbus_error_name_to_re != NULL); /* check invariant */ + re = g_hash_table_lookup (quark_code_pair_to_re, &pair); + } + + if (re != NULL) + { + ret = g_strdup (re->dbus_error_name); + } + else + { + if (g_str_has_prefix (error->message, "GDBus.Error:")) + { + const gchar *begin; + const gchar *end; + begin = error->message + sizeof ("GDBus.Error:") -1; + end = strstr (begin, ":"); + if (end != NULL && end[1] == ' ') + { + ret = g_strndup (begin, end - begin); + } + } + } + + G_UNLOCK (error_lock); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_error_new_for_dbus_error: + * @dbus_error_name: D-Bus error name. + * @dbus_error_message: D-Bus error message. + * + * Creates a #GError based on the contents of @dbus_error_name and + * @dbus_error_message. + * + * Errors registered with g_dbus_error_register_error() will be looked + * up using @dbus_error_name and if a match is found, the error domain + * and code is used. Applications can use g_dbus_error_get_remote_error() + * to recover @dbus_error_name. + * + * If a match against a registered error is not found and the D-Bus + * error name is in a form as returned by g_dbus_error_encode_gerror() + * the error domain and code encoded in the name is used to + * create the #GError. Also, @dbus_error_name is added to the error message + * such that it can be recovered with g_dbus_error_get_remote_error(). + * + * Otherwise, a #GError with the error code %G_IO_ERROR_DBUS_ERROR + * in the #G_IO_ERROR error domain is returned. Also, @dbus_error_name is + * added to the error message such that it can be recovered with + * g_dbus_error_get_remote_error(). + * + * In all three cases, @dbus_error_name can always be recovered from the + * returned #GError using the g_dbus_error_get_remote_error() function + * (unless g_dbus_error_strip_remote_error() hasn't been used on the returned error). + * + * This function is typically only used in object mappings to prepare + * #GError instances for applications. Regular applications should not use + * it. + * + * Returns: An allocated #GError. Free with g_error_free(). + * + * Since: 2.26 + */ +GError * +g_dbus_error_new_for_dbus_error (const gchar *dbus_error_name, + const gchar *dbus_error_message) +{ + GError *error; + RegisteredError *re; + + g_return_val_if_fail (dbus_error_name != NULL, NULL); + g_return_val_if_fail (dbus_error_message != NULL, NULL); + + /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */ + _g_dbus_initialize (); + + G_LOCK (error_lock); + + re = NULL; + if (dbus_error_name_to_re != NULL) + { + g_assert (quark_code_pair_to_re != NULL); /* check invariant */ + re = g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name); + } + + if (re != NULL) + { + error = g_error_new (re->pair.error_domain, + re->pair.error_code, + "GDBus.Error:%s: %s", + dbus_error_name, + dbus_error_message); + } + else + { + GQuark error_domain = 0; + gint error_code = 0; + + if (_g_dbus_error_decode_gerror (dbus_error_name, + &error_domain, + &error_code)) + { + error = g_error_new (error_domain, + error_code, + "GDBus.Error:%s: %s", + dbus_error_name, + dbus_error_message); + } + else + { + error = g_error_new (G_IO_ERROR, + G_IO_ERROR_DBUS_ERROR, + "GDBus.Error:%s: %s", + dbus_error_name, + dbus_error_message); + } + } + + G_UNLOCK (error_lock); + return error; +} + +/** + * g_dbus_error_set_dbus_error: + * @error: A pointer to a #GError or %NULL. + * @dbus_error_name: D-Bus error name. + * @dbus_error_message: D-Bus error message. + * @format: printf()-style format to prepend to @dbus_error_message or %NULL. + * @...: Arguments for @format. + * + * Does nothing if @error is %NULL. Otherwise sets *@error to + * a new #GError created with g_dbus_error_new_for_dbus_error() + * with @dbus_error_message prepend with @format (unless %NULL). + * + * Since: 2.26 + */ +void +g_dbus_error_set_dbus_error (GError **error, + const gchar *dbus_error_name, + const gchar *dbus_error_message, + const gchar *format, + ...) +{ + g_return_if_fail (error == NULL || *error == NULL); + g_return_if_fail (dbus_error_name != NULL); + g_return_if_fail (dbus_error_message != NULL); + + if (error == NULL) + return; + + if (format == NULL) + { + *error = g_dbus_error_new_for_dbus_error (dbus_error_name, dbus_error_message); + } + else + { + va_list var_args; + va_start (var_args, format); + g_dbus_error_set_dbus_error_valist (error, + dbus_error_name, + dbus_error_message, + format, + var_args); + va_end (var_args); + } +} + +/** + * g_dbus_error_set_dbus_error_valist: + * @error: A pointer to a #GError or %NULL. + * @dbus_error_name: D-Bus error name. + * @dbus_error_message: D-Bus error message. + * @format: printf()-style format to prepend to @dbus_error_message or %NULL. + * @var_args: Arguments for @format. + * + * Like g_dbus_error_set_dbus_error() but intended for language bindings. + * + * Since: 2.26 + */ +void +g_dbus_error_set_dbus_error_valist (GError **error, + const gchar *dbus_error_name, + const gchar *dbus_error_message, + const gchar *format, + va_list var_args) +{ + g_return_if_fail (error == NULL || *error == NULL); + g_return_if_fail (dbus_error_name != NULL); + g_return_if_fail (dbus_error_message != NULL); + + if (error == NULL) + return; + + if (format != NULL) + { + gchar *message; + gchar *s; + message = g_strdup_vprintf (format, var_args); + s = g_strdup_printf ("%s: %s", message, dbus_error_message); + *error = g_dbus_error_new_for_dbus_error (dbus_error_name, s); + g_free (s); + g_free (message); + } + else + { + *error = g_dbus_error_new_for_dbus_error (dbus_error_name, dbus_error_message); + } +} + +/** + * g_dbus_error_strip_remote_error: + * @error: A #GError. + * + * Looks for extra information in the error message used to recover + * the D-Bus error name and strips it if found. If stripped, the + * message field in @error will correspond exactly to what was + * received on the wire. + * + * This is typically used when presenting errors to the end user. + * + * Returns: %TRUE if information was stripped, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_error_strip_remote_error (GError *error) +{ + gboolean ret; + + g_return_val_if_fail (error != NULL, FALSE); + + ret = FALSE; + + if (g_str_has_prefix (error->message, "GDBus.Error:")) + { + const gchar *begin; + const gchar *end; + gchar *new_message; + + begin = error->message + sizeof ("GDBus.Error:") -1; + end = strstr (begin, ":"); + if (end != NULL && end[1] == ' ') + { + new_message = g_strdup (end + 2); + g_free (error->message); + error->message = new_message; + ret = TRUE; + } + } + + return ret; +} + +/** + * g_dbus_error_encode_gerror: + * @error: A #GError. + * + * Creates a D-Bus error name to use for @error. If @error matches + * a registered error (cf. g_dbus_error_register_error()), the corresponding + * D-Bus error name will be returned. + * + * Otherwise the a name of the form + * org.gtk.GDBus.UnmappedGError.Quark._ESCAPED_QUARK_NAME.Code_ERROR_CODE + * will be used. This allows other GDBus applications to map the error + * on the wire back to a #GError using g_dbus_error_new_for_dbus_error(). + * + * This function is typically only used in object mappings to put a + * #GError on the wire. Regular applications should not use it. + * + * Returns: A D-Bus error name (never %NULL). Free with g_free(). + * + * Since: 2.26 + */ +gchar * +g_dbus_error_encode_gerror (const GError *error) +{ + RegisteredError *re; + gchar *error_name; + + g_return_val_if_fail (error != NULL, NULL); + + /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */ + _g_dbus_initialize (); + + error_name = NULL; + + G_LOCK (error_lock); + re = NULL; + if (quark_code_pair_to_re != NULL) + { + QuarkCodePair pair; + pair.error_domain = error->domain; + pair.error_code = error->code; + g_assert (dbus_error_name_to_re != NULL); /* check invariant */ + re = g_hash_table_lookup (quark_code_pair_to_re, &pair); + } + if (re != NULL) + { + error_name = g_strdup (re->dbus_error_name); + G_UNLOCK (error_lock); + } + else + { + const gchar *domain_as_string; + GString *s; + guint n; + + G_UNLOCK (error_lock); + + /* We can't make a lot of assumptions about what domain_as_string + * looks like and D-Bus is extremely picky about error names so + * hex-encode it for transport across the wire. + */ + domain_as_string = g_quark_to_string (error->domain); + s = g_string_new ("org.gtk.GDBus.UnmappedGError.Quark._"); + for (n = 0; domain_as_string[n] != 0; n++) + { + gint c = domain_as_string[n]; + if (g_ascii_isalnum (c)) + { + g_string_append_c (s, c); + } + else + { + guint nibble_top; + guint nibble_bottom; + g_string_append_c (s, '_'); + nibble_top = ((int) domain_as_string[n]) >> 4; + nibble_bottom = ((int) domain_as_string[n]) & 0x0f; + if (nibble_top < 10) + nibble_top += '0'; + else + nibble_top += 'a' - 10; + if (nibble_bottom < 10) + nibble_bottom += '0'; + else + nibble_bottom += 'a' - 10; + g_string_append_c (s, nibble_top); + g_string_append_c (s, nibble_bottom); + } + } + g_string_append_printf (s, ".Code%d", error->code); + error_name = g_string_free (s, FALSE); + } + + return error_name; +} + +#define __G_DBUS_ERROR_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbuserror.h b/gio/gdbuserror.h new file mode 100644 index 000000000..145137917 --- /dev/null +++ b/gio/gdbuserror.h @@ -0,0 +1,96 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_ERROR_H__ +#define __G_DBUS_ERROR_H__ + +#include + +G_BEGIN_DECLS + +/** + * G_DBUS_ERROR: + * + * Error domain for errors generated by a remote message bus. Errors + * in this domain will be from the #GDBusError enumeration. See + * #GError for more information on error domains. + * + * Note that errors in this error domain is intended only for + * returning errors from a remote message bus process. Errors + * generated locally in-process by e.g. #GDBusConnection is from the + * %G_IO_ERROR domain. + * + * Since: 2.26 + */ +#define G_DBUS_ERROR g_dbus_error_quark() + +GQuark g_dbus_error_quark (void); + +/* Used by applications to check, get and strip the D-Bus error name */ +gboolean g_dbus_error_is_remote_error (const GError *error); +gchar *g_dbus_error_get_remote_error (const GError *error); +gboolean g_dbus_error_strip_remote_error (GError *error); + +/** + * GDBusErrorEntry: + * @error_code: An error code. + * @dbus_error_name: The D-Bus error name to associate with @error_code. + * + * Struct used in g_dbus_error_register_error_domain(). + * + * Since: 2.26 + */ +struct _GDBusErrorEntry +{ + gint error_code; + const gchar *dbus_error_name; +}; + +gboolean g_dbus_error_register_error (GQuark error_domain, + gint error_code, + const gchar *dbus_error_name); +gboolean g_dbus_error_unregister_error (GQuark error_domain, + gint error_code, + const gchar *dbus_error_name); +void g_dbus_error_register_error_domain (const gchar *error_domain_quark_name, + volatile gsize *quark_volatile, + const GDBusErrorEntry *entries, + guint num_entries); + +/* Only used by object mappings to map back and forth to GError */ +GError *g_dbus_error_new_for_dbus_error (const gchar *dbus_error_name, + const gchar *dbus_error_message); +void g_dbus_error_set_dbus_error (GError **error, + const gchar *dbus_error_name, + const gchar *dbus_error_message, + const gchar *format, + ...); +void g_dbus_error_set_dbus_error_valist (GError **error, + const gchar *dbus_error_name, + const gchar *dbus_error_message, + const gchar *format, + va_list var_args); +gchar *g_dbus_error_encode_gerror (const GError *error); + +G_END_DECLS + +#endif /* __G_DBUS_ERROR_H__ */ diff --git a/gio/gdbusintrospection.c b/gio/gdbusintrospection.c new file mode 100644 index 000000000..c1c4d35c2 --- /dev/null +++ b/gio/gdbusintrospection.c @@ -0,0 +1,2079 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include +#include + +#include "gdbusintrospection.h" + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusintrospection + * @title: Introspection XML + * @short_description: Parse and Generate Introspection XML + * @include: gio/gio.h + * + * Various data structures and convenience routines to parse and + * generate D-Bus introspection XML. Introspection information is + * used when registering objects with g_dbus_connection_register_object(). + * + * The format of D-BUs introspection XML is specified in the + * D-Bus specification. + */ + +/* ---------------------------------------------------------------------------------------------------- */ + +/* See also https://bugzilla.gnome.org/show_bug.cgi?id=449565 ... */ +#define _MY_DEFINE_BOXED_TYPE(TypeName, type_name) \ + GType \ + type_name##_get_type (void) \ + { \ + static volatile gsize type_volatile = 0; \ + if (g_once_init_enter (&type_volatile)) \ + { \ + GType type = g_boxed_type_register_static (g_intern_static_string (#TypeName), \ + (GBoxedCopyFunc) type_name##_ref, \ + (GBoxedFreeFunc) type_name##_unref); \ + g_once_init_leave (&type_volatile, type); \ + } \ + return (GType) type_volatile; \ + } + +_MY_DEFINE_BOXED_TYPE (GDBusNodeInfo, g_dbus_node_info); +_MY_DEFINE_BOXED_TYPE (GDBusInterfaceInfo, g_dbus_interface_info); +_MY_DEFINE_BOXED_TYPE (GDBusMethodInfo, g_dbus_method_info); +_MY_DEFINE_BOXED_TYPE (GDBusSignalInfo, g_dbus_signal_info); +_MY_DEFINE_BOXED_TYPE (GDBusPropertyInfo, g_dbus_property_info); +_MY_DEFINE_BOXED_TYPE (GDBusArgInfo, g_dbus_arg_info); +_MY_DEFINE_BOXED_TYPE (GDBusAnnotationInfo, g_dbus_annotation_info); + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + /* stuff we are currently collecting */ + GPtrArray *args; + GPtrArray *out_args; + GPtrArray *methods; + GPtrArray *signals; + GPtrArray *properties; + GPtrArray *interfaces; + GPtrArray *nodes; + GPtrArray *annotations; + + /* A list of GPtrArray's containing annotations */ + GSList *annotations_stack; + + /* A list of GPtrArray's containing interfaces */ + GSList *interfaces_stack; + + /* A list of GPtrArray's containing nodes */ + GSList *nodes_stack; + + /* Whether the direction was "in" for last parsed arg */ + gboolean last_arg_was_in; + + /* Number of args currently being collected; used for assigning + * names to args without a "name" attribute + */ + guint num_args; + +} ParseData; + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_node_info_ref: + * @info: A #GDBusNodeInfo + * + * If @info is statically allocated does nothing. Otherwise increases + * the reference count. + * + * Returns: The same @info. + * + * Since: 2.26 + */ +GDBusNodeInfo * +g_dbus_node_info_ref (GDBusNodeInfo *info) +{ + if (info->ref_count == -1) + return info; + g_atomic_int_inc (&info->ref_count); + return info; +} + +/** + * g_dbus_interface_info_ref: + * @info: A #GDBusInterfaceInfo + * + * If @info is statically allocated does nothing. Otherwise increases + * the reference count. + * + * Returns: The same @info. + * + * Since: 2.26 + */ +GDBusInterfaceInfo * +g_dbus_interface_info_ref (GDBusInterfaceInfo *info) +{ + if (info->ref_count == -1) + return info; + g_atomic_int_inc (&info->ref_count); + return info; +} + +/** + * g_dbus_method_info_ref: + * @info: A #GDBusMethodInfo + * + * If @info is statically allocated does nothing. Otherwise increases + * the reference count. + * + * Returns: The same @info. + * + * Since: 2.26 + */ +GDBusMethodInfo * +g_dbus_method_info_ref (GDBusMethodInfo *info) +{ + if (info->ref_count == -1) + return info; + g_atomic_int_inc (&info->ref_count); + return info; +} + +/** + * g_dbus_signal_info_ref: + * @info: A #GDBusSignalInfo + * + * If @info is statically allocated does nothing. Otherwise increases + * the reference count. + * + * Returns: The same @info. + * + * Since: 2.26 + */ +GDBusSignalInfo * +g_dbus_signal_info_ref (GDBusSignalInfo *info) +{ + if (info->ref_count == -1) + return info; + g_atomic_int_inc (&info->ref_count); + return info; +} + +/** + * g_dbus_property_info_ref: + * @info: A #GDBusPropertyInfo + * + * If @info is statically allocated does nothing. Otherwise increases + * the reference count. + * + * Returns: The same @info. + * + * Since: 2.26 + */ +GDBusPropertyInfo * +g_dbus_property_info_ref (GDBusPropertyInfo *info) +{ + if (info->ref_count == -1) + return info; + g_atomic_int_inc (&info->ref_count); + return info; +} + +/** + * g_dbus_arg_info_ref: + * @info: A #GDBusArgInfo + * + * If @info is statically allocated does nothing. Otherwise increases + * the reference count. + * + * Returns: The same @info. + * + * Since: 2.26 + */ +GDBusArgInfo * +g_dbus_arg_info_ref (GDBusArgInfo *info) +{ + if (info->ref_count == -1) + return info; + g_atomic_int_inc (&info->ref_count); + return info; +} + +/** + * g_dbus_annotation_info_ref: + * @info: A #GDBusNodeInfo + * + * If @info is statically allocated does nothing. Otherwise increases + * the reference count. + * + * Returns: The same @info. + * + * Since: 2.26 + */ +GDBusAnnotationInfo * +g_dbus_annotation_info_ref (GDBusAnnotationInfo *info) +{ + if (info->ref_count == -1) + return info; + g_atomic_int_inc (&info->ref_count); + return info; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +free_null_terminated_array (gpointer array, GDestroyNotify unref_func) +{ + guint n; + gpointer *p = array; + if (p == NULL) + return; + for (n = 0; p[n] != NULL; n++) + unref_func (p[n]); + g_free (p); +} + +/** + * g_dbus_annotation_info_unref: + * @info: A #GDBusAnnotationInfo. + * + * If @info is statically allocated, does nothing. Otherwise decreases + * the reference count of @info. When its reference count drops to 0, + * the memory used is freed. + * + * Since: 2.26 + */ +void +g_dbus_annotation_info_unref (GDBusAnnotationInfo *info) +{ + if (info->ref_count == -1) + return; + if (g_atomic_int_dec_and_test (&info->ref_count)) + { + g_free (info->key); + g_free (info->value); + free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref); + g_free (info); + } +} + +/** + * g_dbus_arg_info_unref: + * @info: A #GDBusArgInfo. + * + * If @info is statically allocated, does nothing. Otherwise decreases + * the reference count of @info. When its reference count drops to 0, + * the memory used is freed. + * + * Since: 2.26 + */ +void +g_dbus_arg_info_unref (GDBusArgInfo *info) +{ + if (info->ref_count == -1) + return; + if (g_atomic_int_dec_and_test (&info->ref_count)) + { + g_free (info->name); + g_free (info->signature); + free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref); + g_free (info); + } +} + +/** + * g_dbus_method_info_unref: + * @info: A #GDBusMethodInfo. + * + * If @info is statically allocated, does nothing. Otherwise decreases + * the reference count of @info. When its reference count drops to 0, + * the memory used is freed. + * + * Since: 2.26 + */ +void +g_dbus_method_info_unref (GDBusMethodInfo *info) +{ + if (info->ref_count == -1) + return; + if (g_atomic_int_dec_and_test (&info->ref_count)) + { + g_free (info->name); + free_null_terminated_array (info->in_args, (GDestroyNotify) g_dbus_arg_info_unref); + free_null_terminated_array (info->out_args, (GDestroyNotify) g_dbus_arg_info_unref); + free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref); + g_free (info); + } +} + +/** + * g_dbus_signal_info_unref: + * @info: A #GDBusSignalInfo. + * + * If @info is statically allocated, does nothing. Otherwise decreases + * the reference count of @info. When its reference count drops to 0, + * the memory used is freed. + * + * Since: 2.26 + */ +void +g_dbus_signal_info_unref (GDBusSignalInfo *info) +{ + if (info->ref_count == -1) + return; + if (g_atomic_int_dec_and_test (&info->ref_count)) + { + g_free (info->name); + free_null_terminated_array (info->args, (GDestroyNotify) g_dbus_arg_info_unref); + free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref); + g_free (info); + } +} + +/** + * g_dbus_property_info_unref: + * @info: A #GDBusPropertyInfo. + * + * If @info is statically allocated, does nothing. Otherwise decreases + * the reference count of @info. When its reference count drops to 0, + * the memory used is freed. + * + * Since: 2.26 + */ +void +g_dbus_property_info_unref (GDBusPropertyInfo *info) +{ + if (info->ref_count == -1) + return; + if (g_atomic_int_dec_and_test (&info->ref_count)) + { + g_free (info->name); + g_free (info->signature); + free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref); + g_free (info); + } +} + +/** + * g_dbus_interface_info_unref: + * @info: A #GDBusInterfaceInfo. + * + * If @info is statically allocated, does nothing. Otherwise decreases + * the reference count of @info. When its reference count drops to 0, + * the memory used is freed. + * + * Since: 2.26 + */ +void +g_dbus_interface_info_unref (GDBusInterfaceInfo *info) +{ + if (info->ref_count == -1) + return; + if (g_atomic_int_dec_and_test (&info->ref_count)) + { + g_free (info->name); + free_null_terminated_array (info->methods, (GDestroyNotify) g_dbus_method_info_unref); + free_null_terminated_array (info->signals, (GDestroyNotify) g_dbus_signal_info_unref); + free_null_terminated_array (info->properties, (GDestroyNotify) g_dbus_property_info_unref); + free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref); + g_free (info); + } +} + +/** + * g_dbus_node_info_unref: + * @info: A #GDBusNodeInfo. + * + * If @info is statically allocated, does nothing. Otherwise decreases + * the reference count of @info. When its reference count drops to 0, + * the memory used is freed. + * + * Since: 2.26 + */ +void +g_dbus_node_info_unref (GDBusNodeInfo *info) +{ + if (info->ref_count == -1) + return; + if (g_atomic_int_dec_and_test (&info->ref_count)) + { + g_free (info->path); + free_null_terminated_array (info->interfaces, (GDestroyNotify) g_dbus_interface_info_unref); + free_null_terminated_array (info->nodes, (GDestroyNotify) g_dbus_node_info_unref); + free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref); + g_free (info); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +g_dbus_annotation_info_set (ParseData *data, + GDBusAnnotationInfo *info, + const gchar *key, + const gchar *value, + GDBusAnnotationInfo **embedded_annotations) +{ + info->ref_count = 1; + + if (key != NULL) + info->key = g_strdup (key); + + if (value != NULL) + info->value = g_strdup (value); + + if (embedded_annotations != NULL) + info->annotations = embedded_annotations; +} + +static void +g_dbus_arg_info_set (ParseData *data, + GDBusArgInfo *info, + const gchar *name, + const gchar *signature, + GDBusAnnotationInfo **annotations) +{ + info->ref_count = 1; + + /* name may be NULL - TODO: compute name? */ + if (name != NULL) + info->name = g_strdup (name); + + if (signature != NULL) + info->signature = g_strdup (signature); + + if (annotations != NULL) + info->annotations = annotations; +} + +static void +g_dbus_method_info_set (ParseData *data, + GDBusMethodInfo *info, + const gchar *name, + guint in_num_args, + GDBusArgInfo **in_args, + guint out_num_args, + GDBusArgInfo **out_args, + GDBusAnnotationInfo **annotations) +{ + info->ref_count = 1; + + if (name != NULL) + info->name = g_strdup (name); + + if (in_num_args != 0) + { + //info->in_num_args = in_num_args; + info->in_args = in_args; + } + + if (out_num_args != 0) + { + //info->out_num_args = out_num_args; + info->out_args = out_args; + } + + if (annotations != NULL) + info->annotations = annotations; +} + +static void +g_dbus_signal_info_set (ParseData *data, + GDBusSignalInfo *info, + const gchar *name, + guint num_args, + GDBusArgInfo **args, + GDBusAnnotationInfo **annotations) +{ + info->ref_count = 1; + + if (name != NULL) + info->name = g_strdup (name); + + if (num_args != 0) + { + //info->num_args = num_args; + info->args = args; + } + + if (annotations != NULL) + { + info->annotations = annotations; + } +} + +static void +g_dbus_property_info_set (ParseData *data, + GDBusPropertyInfo *info, + const gchar *name, + const gchar *signature, + GDBusPropertyInfoFlags flags, + GDBusAnnotationInfo **annotations) +{ + info->ref_count = 1; + + if (name != NULL) + info->name = g_strdup (name); + + if (flags != G_DBUS_PROPERTY_INFO_FLAGS_NONE) + info->flags = flags; + + if (signature != NULL) + { + info->signature = g_strdup (signature); + } + + if (annotations != NULL) + { + info->annotations = annotations; + } +} + +static void +g_dbus_interface_info_set (ParseData *data, + GDBusInterfaceInfo *info, + const gchar *name, + guint num_methods, + GDBusMethodInfo **methods, + guint num_signals, + GDBusSignalInfo **signals, + guint num_properties, + GDBusPropertyInfo **properties, + GDBusAnnotationInfo **annotations) +{ + info->ref_count = 1; + + if (name != NULL) + { + info->name = g_strdup (name); + } + + if (num_methods != 0) + { + //info->num_methods = num_methods; + info->methods = methods; + } + + if (num_signals != 0) + { + //info->num_signals = num_signals; + info->signals = signals; + } + + if (num_properties != 0) + { + //info->num_properties = num_properties; + info->properties = properties; + } + + if (annotations != NULL) + { + info->annotations = annotations; + } +} + +static void +g_dbus_node_info_set (ParseData *data, + GDBusNodeInfo *info, + const gchar *path, + guint num_interfaces, + GDBusInterfaceInfo **interfaces, + guint num_nodes, + GDBusNodeInfo **nodes, + GDBusAnnotationInfo **annotations) +{ + info->ref_count = 1; + + if (path != NULL) + { + info->path = g_strdup (path); + /* TODO: relative / absolute path snafu */ + } + + if (num_interfaces != 0) + { + //info->num_interfaces = num_interfaces; + info->interfaces = interfaces; + } + + if (num_nodes != 0) + { + //info->num_nodes = num_nodes; + info->nodes = nodes; + } + + if (annotations != NULL) + { + info->annotations = annotations; + } + +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +g_dbus_annotation_info_generate_xml (const GDBusAnnotationInfo *info, + guint indent, + GString *string_builder) +{ + guint n; + + g_string_append_printf (string_builder, "%*skey, + info->value); + + if (info->annotations == NULL) + { + g_string_append (string_builder, "/>\n"); + } + else + { + g_string_append (string_builder, ">\n"); + + for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++) + g_dbus_annotation_info_generate_xml (info->annotations[n], + indent + 2, + string_builder); + + g_string_append_printf (string_builder, "%*s\n", + indent, ""); + } + +} + +static void +g_dbus_arg_info_generate_xml (const GDBusArgInfo *info, + guint indent, + const gchar *extra_attributes, + GString *string_builder) +{ + guint n; + + g_string_append_printf (string_builder, "%*ssignature); + + if (info->name != NULL) + g_string_append_printf (string_builder, " name=\"%s\"", info->name); + + if (extra_attributes != NULL) + g_string_append_printf (string_builder, " %s", extra_attributes); + + if (info->annotations == NULL) + { + g_string_append (string_builder, "/>\n"); + } + else + { + g_string_append (string_builder, ">\n"); + + for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++) + g_dbus_annotation_info_generate_xml (info->annotations[n], + indent + 2, + string_builder); + + g_string_append_printf (string_builder, "%*s\n", indent, ""); + } + +} + +static void +g_dbus_method_info_generate_xml (const GDBusMethodInfo *info, + guint indent, + GString *string_builder) +{ + guint n; + + g_string_append_printf (string_builder, "%*sname); + + if (info->annotations == NULL && info->in_args == NULL && info->out_args == NULL) + { + g_string_append (string_builder, "/>\n"); + } + else + { + g_string_append (string_builder, ">\n"); + + for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++) + g_dbus_annotation_info_generate_xml (info->annotations[n], + indent + 2, + string_builder); + + for (n = 0; info->in_args != NULL && info->in_args[n] != NULL; n++) + g_dbus_arg_info_generate_xml (info->in_args[n], + indent + 2, + "direction=\"in\"", + string_builder); + + for (n = 0; info->out_args != NULL && info->out_args[n] != NULL; n++) + g_dbus_arg_info_generate_xml (info->out_args[n], + indent + 2, + "direction=\"out\"", + string_builder); + + g_string_append_printf (string_builder, "%*s\n", indent, ""); + } +} + +static void +g_dbus_signal_info_generate_xml (const GDBusSignalInfo *info, + guint indent, + GString *string_builder) +{ + guint n; + + g_string_append_printf (string_builder, "%*sname); + + if (info->annotations == NULL && info->args == NULL) + { + g_string_append (string_builder, "/>\n"); + } + else + { + g_string_append (string_builder, ">\n"); + + for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++) + g_dbus_annotation_info_generate_xml (info->annotations[n], + indent + 2, + string_builder); + + for (n = 0; info->args != NULL && info->args[n] != NULL; n++) + g_dbus_arg_info_generate_xml (info->args[n], + indent + 2, + NULL, + string_builder); + + g_string_append_printf (string_builder, "%*s\n", indent, ""); + } +} + +static void +g_dbus_property_info_generate_xml (const GDBusPropertyInfo *info, + guint indent, + GString *string_builder) +{ + guint n; + const gchar *access_string; + + if ((info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE) && + (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)) + { + access_string = "readwrite"; + } + else if (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE) + { + access_string = "read"; + } + else if (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE) + { + access_string = "write"; + } + else + { + g_assert_not_reached (); + } + + g_string_append_printf (string_builder, "%*ssignature, + info->name, + access_string); + + if (info->annotations == NULL) + { + g_string_append (string_builder, "/>\n"); + } + else + { + g_string_append (string_builder, ">\n"); + + for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++) + g_dbus_annotation_info_generate_xml (info->annotations[n], + indent + 2, + string_builder); + + g_string_append_printf (string_builder, "%*s\n", indent, ""); + } + +} + +/** + * g_dbus_interface_info_generate_xml: + * @info: A #GDBusNodeInfo + * @indent: Indentation level. + * @string_builder: A #GString to to append XML data to. + * + * Appends an XML representation of @info (and its children) to @string_builder. + * + * This function is typically used for generating introspection XML + * documents at run-time for handling the + * org.freedesktop.DBus.Introspectable.Introspect + * method. + * + * Since: 2.26 + */ +void +g_dbus_interface_info_generate_xml (const GDBusInterfaceInfo *info, + guint indent, + GString *string_builder) +{ + guint n; + + g_string_append_printf (string_builder, "%*s\n", + indent, "", + info->name); + + for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++) + g_dbus_annotation_info_generate_xml (info->annotations[n], + indent + 2, + string_builder); + + for (n = 0; info->methods != NULL && info->methods[n] != NULL; n++) + g_dbus_method_info_generate_xml (info->methods[n], + indent + 2, + string_builder); + + for (n = 0; info->signals != NULL && info->signals[n] != NULL; n++) + g_dbus_signal_info_generate_xml (info->signals[n], + indent + 2, + string_builder); + + for (n = 0; info->properties != NULL && info->properties[n] != NULL; n++) + g_dbus_property_info_generate_xml (info->properties[n], + indent + 2, + string_builder); + + g_string_append_printf (string_builder, "%*s\n", indent, ""); +} + +/** + * g_dbus_node_info_generate_xml: + * @info: A #GDBusNodeInfo. + * @indent: Indentation level. + * @string_builder: A #GString to to append XML data to. + * + * Appends an XML representation of @info (and its children) to @string_builder. + * + * This function is typically used for generating introspection XML documents at run-time for + * handling the org.freedesktop.DBus.Introspectable.Introspect method. + * + * Since: 2.26 + */ +void +g_dbus_node_info_generate_xml (const GDBusNodeInfo *info, + guint indent, + GString *string_builder) +{ + guint n; + + g_string_append_printf (string_builder, "%*spath != NULL) + g_string_append_printf (string_builder, " name=\"%s\"", info->path); + + if (info->interfaces == NULL && info->nodes == NULL && info->annotations == NULL) + { + g_string_append (string_builder, "/>\n"); + } + else + { + g_string_append (string_builder, ">\n"); + + for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++) + g_dbus_annotation_info_generate_xml (info->annotations[n], + indent + 2, + string_builder); + + for (n = 0; info->interfaces != NULL && info->interfaces[n] != NULL; n++) + g_dbus_interface_info_generate_xml (info->interfaces[n], + indent + 2, + string_builder); + + for (n = 0; info->nodes != NULL && info->nodes[n] != NULL; n++) + g_dbus_node_info_generate_xml (info->nodes[n], + indent + 2, + string_builder); + + g_string_append_printf (string_builder, "%*s\n", indent, ""); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusAnnotationInfo ** +parse_data_steal_annotations (ParseData *data, + guint *out_num_elements) +{ + GDBusAnnotationInfo **ret; + if (out_num_elements != NULL) + *out_num_elements = data->annotations->len; + if (data->annotations == NULL) + ret = NULL; + else + { + g_ptr_array_add (data->annotations, NULL); + ret = (GDBusAnnotationInfo **) g_ptr_array_free (data->annotations, FALSE); + } + data->annotations = g_ptr_array_new (); + return ret; +} + +static GDBusArgInfo ** +parse_data_steal_args (ParseData *data, + guint *out_num_elements) +{ + GDBusArgInfo **ret; + if (out_num_elements != NULL) + *out_num_elements = data->args->len; + if (data->args == NULL) + ret = NULL; + else + { + g_ptr_array_add (data->args, NULL); + ret = (GDBusArgInfo **) g_ptr_array_free (data->args, FALSE); + } + data->args = g_ptr_array_new (); + return ret; +} + +static GDBusArgInfo ** +parse_data_steal_out_args (ParseData *data, + guint *out_num_elements) +{ + GDBusArgInfo **ret; + if (out_num_elements != NULL) + *out_num_elements = data->out_args->len; + if (data->out_args == NULL) + ret = NULL; + else + { + g_ptr_array_add (data->out_args, NULL); + ret = (GDBusArgInfo **) g_ptr_array_free (data->out_args, FALSE); + } + data->out_args = g_ptr_array_new (); + return ret; +} + +static GDBusMethodInfo ** +parse_data_steal_methods (ParseData *data, + guint *out_num_elements) +{ + GDBusMethodInfo **ret; + if (out_num_elements != NULL) + *out_num_elements = data->methods->len; + if (data->methods == NULL) + ret = NULL; + else + { + g_ptr_array_add (data->methods, NULL); + ret = (GDBusMethodInfo **) g_ptr_array_free (data->methods, FALSE); + } + data->methods = g_ptr_array_new (); + return ret; +} + +static GDBusSignalInfo ** +parse_data_steal_signals (ParseData *data, + guint *out_num_elements) +{ + GDBusSignalInfo **ret; + if (out_num_elements != NULL) + *out_num_elements = data->signals->len; + if (data->signals == NULL) + ret = NULL; + else + { + g_ptr_array_add (data->signals, NULL); + ret = (GDBusSignalInfo **) g_ptr_array_free (data->signals, FALSE); + } + data->signals = g_ptr_array_new (); + return ret; +} + +static GDBusPropertyInfo ** +parse_data_steal_properties (ParseData *data, + guint *out_num_elements) +{ + GDBusPropertyInfo **ret; + if (out_num_elements != NULL) + *out_num_elements = data->properties->len; + if (data->properties == NULL) + ret = NULL; + else + { + g_ptr_array_add (data->properties, NULL); + ret = (GDBusPropertyInfo **) g_ptr_array_free (data->properties, FALSE); + } + data->properties = g_ptr_array_new (); + return ret; +} + +static GDBusInterfaceInfo ** +parse_data_steal_interfaces (ParseData *data, + guint *out_num_elements) +{ + GDBusInterfaceInfo **ret; + if (out_num_elements != NULL) + *out_num_elements = data->interfaces->len; + if (data->interfaces == NULL) + ret = NULL; + else + { + g_ptr_array_add (data->interfaces, NULL); + ret = (GDBusInterfaceInfo **) g_ptr_array_free (data->interfaces, FALSE); + } + data->interfaces = g_ptr_array_new (); + return ret; +} + +static GDBusNodeInfo ** +parse_data_steal_nodes (ParseData *data, + guint *out_num_elements) +{ + GDBusNodeInfo **ret; + if (out_num_elements != NULL) + *out_num_elements = data->nodes->len; + if (data->nodes == NULL) + ret = NULL; + else + { + g_ptr_array_add (data->nodes, NULL); + ret = (GDBusNodeInfo **) g_ptr_array_free (data->nodes, FALSE); + } + data->nodes = g_ptr_array_new (); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +parse_data_free_annotations (ParseData *data) +{ + if (data->annotations == NULL) + return; + g_ptr_array_foreach (data->annotations, (GFunc) g_dbus_annotation_info_unref, NULL); + g_ptr_array_free (data->annotations, TRUE); + data->annotations = NULL; +} + +static void +parse_data_free_args (ParseData *data) +{ + if (data->args == NULL) + return; + g_ptr_array_foreach (data->args, (GFunc) g_dbus_arg_info_unref, NULL); + g_ptr_array_free (data->args, TRUE); + data->args = NULL; +} + +static void +parse_data_free_out_args (ParseData *data) +{ + if (data->out_args == NULL) + return; + g_ptr_array_foreach (data->out_args, (GFunc) g_dbus_arg_info_unref, NULL); + g_ptr_array_free (data->out_args, TRUE); + data->out_args = NULL; +} + +static void +parse_data_free_methods (ParseData *data) +{ + if (data->methods == NULL) + return; + g_ptr_array_foreach (data->methods, (GFunc) g_dbus_method_info_unref, NULL); + g_ptr_array_free (data->methods, TRUE); + data->methods = NULL; +} + +static void +parse_data_free_signals (ParseData *data) +{ + if (data->signals == NULL) + return; + g_ptr_array_foreach (data->signals, (GFunc) g_dbus_signal_info_unref, NULL); + g_ptr_array_free (data->signals, TRUE); + data->signals = NULL; +} + +static void +parse_data_free_properties (ParseData *data) +{ + if (data->properties == NULL) + return; + g_ptr_array_foreach (data->properties, (GFunc) g_dbus_property_info_unref, NULL); + g_ptr_array_free (data->properties, TRUE); + data->properties = NULL; +} + +static void +parse_data_free_interfaces (ParseData *data) +{ + if (data->interfaces == NULL) + return; + g_ptr_array_foreach (data->interfaces, (GFunc) g_dbus_interface_info_unref, NULL); + g_ptr_array_free (data->interfaces, TRUE); + data->interfaces = NULL; +} + +static void +parse_data_free_nodes (ParseData *data) +{ + if (data->nodes == NULL) + return; + g_ptr_array_foreach (data->nodes, (GFunc) g_dbus_node_info_unref, NULL); + g_ptr_array_free (data->nodes, TRUE); + data->nodes = NULL; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusAnnotationInfo * +parse_data_get_annotation (ParseData *data, + gboolean create_new) +{ + if (create_new) + g_ptr_array_add (data->annotations, g_new0 (GDBusAnnotationInfo, 1)); + return data->annotations->pdata[data->annotations->len - 1]; +} + +static GDBusArgInfo * +parse_data_get_arg (ParseData *data, + gboolean create_new) +{ + if (create_new) + g_ptr_array_add (data->args, g_new0 (GDBusArgInfo, 1)); + return data->args->pdata[data->args->len - 1]; +} + +static GDBusArgInfo * +parse_data_get_out_arg (ParseData *data, + gboolean create_new) +{ + if (create_new) + g_ptr_array_add (data->out_args, g_new0 (GDBusArgInfo, 1)); + return data->out_args->pdata[data->out_args->len - 1]; +} + +static GDBusMethodInfo * +parse_data_get_method (ParseData *data, + gboolean create_new) +{ + if (create_new) + g_ptr_array_add (data->methods, g_new0 (GDBusMethodInfo, 1)); + return data->methods->pdata[data->methods->len - 1]; +} + +static GDBusSignalInfo * +parse_data_get_signal (ParseData *data, + gboolean create_new) +{ + if (create_new) + g_ptr_array_add (data->signals, g_new0 (GDBusSignalInfo, 1)); + return data->signals->pdata[data->signals->len - 1]; +} + +static GDBusPropertyInfo * +parse_data_get_property (ParseData *data, + gboolean create_new) +{ + if (create_new) + g_ptr_array_add (data->properties, g_new0 (GDBusPropertyInfo, 1)); + return data->properties->pdata[data->properties->len - 1]; +} + +static GDBusInterfaceInfo * +parse_data_get_interface (ParseData *data, + gboolean create_new) +{ + if (create_new) + g_ptr_array_add (data->interfaces, g_new0 (GDBusInterfaceInfo, 1)); + return data->interfaces->pdata[data->interfaces->len - 1]; +} + +static GDBusNodeInfo * +parse_data_get_node (ParseData *data, + gboolean create_new) +{ + if (create_new) + g_ptr_array_add (data->nodes, g_new0 (GDBusNodeInfo, 1)); + return data->nodes->pdata[data->nodes->len - 1]; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static ParseData * +parse_data_new (void) +{ + ParseData *data; + + data = g_new0 (ParseData, 1); + + /* initialize arrays */ + parse_data_steal_annotations (data, NULL); + parse_data_steal_args (data, NULL); + parse_data_steal_out_args (data, NULL); + parse_data_steal_methods (data, NULL); + parse_data_steal_signals (data, NULL); + parse_data_steal_properties (data, NULL); + parse_data_steal_interfaces (data, NULL); + parse_data_steal_nodes (data, NULL); + + return data; +} + +static void +parse_data_free (ParseData *data) +{ + GSList *l; + + /* free stack of annotation arrays */ + for (l = data->annotations_stack; l != NULL; l = l->next) + { + GPtrArray *annotations = l->data; + g_ptr_array_foreach (annotations, (GFunc) g_dbus_annotation_info_unref, NULL); + g_ptr_array_free (annotations, TRUE); + } + g_slist_free (data->annotations_stack); + + /* free stack of interface arrays */ + for (l = data->interfaces_stack; l != NULL; l = l->next) + { + GPtrArray *interfaces = l->data; + g_ptr_array_foreach (interfaces, (GFunc) g_dbus_interface_info_unref, NULL); + g_ptr_array_free (interfaces, TRUE); + } + g_slist_free (data->interfaces_stack); + + /* free stack of node arrays */ + for (l = data->nodes_stack; l != NULL; l = l->next) + { + GPtrArray *nodes = l->data; + g_ptr_array_foreach (nodes, (GFunc) g_dbus_node_info_unref, NULL); + g_ptr_array_free (nodes, TRUE); + } + g_slist_free (data->nodes_stack); + + /* free arrays (data->annotations, data->interfaces and data->nodes have been freed above) */ + parse_data_free_args (data); + parse_data_free_out_args (data); + parse_data_free_methods (data); + parse_data_free_signals (data); + parse_data_free_properties (data); + + g_free (data); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +parser_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + ParseData *data = user_data; + GSList *stack; + const gchar *name; + const gchar *type; + const gchar *access; + const gchar *direction; + const gchar *value; + + name = NULL; + type = NULL; + access = NULL; + direction = NULL; + value = NULL; + + stack = (GSList *) g_markup_parse_context_get_element_stack (context); + + /* ---------------------------------------------------------------------------------------------------- */ + if (strcmp (element_name, "node") == 0) + { + if (!(g_slist_length (stack) >= 1 || strcmp (stack->next->data, "node") != 0)) + { + g_set_error_literal (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " elements can only be top-level or embedded in other elements"); + goto out; + } + + if (!g_markup_collect_attributes (element_name, + attribute_names, + attribute_values, + error, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "name", &name, + /* some hand-written introspection XML documents use this */ + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "xmlns:doc", NULL, + G_MARKUP_COLLECT_INVALID)) + goto out; + + g_dbus_node_info_set (data, + parse_data_get_node (data, TRUE), + name, + 0, NULL, + 0, NULL, + NULL); + + /* push the currently retrieved interfaces and nodes on the stack and prepare new arrays */ + data->interfaces_stack = g_slist_prepend (data->interfaces_stack, data->interfaces); + data->interfaces = NULL; + parse_data_steal_interfaces (data, NULL); + + data->nodes_stack = g_slist_prepend (data->nodes_stack, data->nodes); + data->nodes = NULL; + parse_data_steal_nodes (data, NULL); + + } + /* ---------------------------------------------------------------------------------------------------- */ + else if (strcmp (element_name, "interface") == 0) + { + if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "node") != 0) + { + g_set_error_literal (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " elements can only be embedded in elements"); + goto out; + } + + if (!g_markup_collect_attributes (element_name, + attribute_names, + attribute_values, + error, + G_MARKUP_COLLECT_STRING, "name", &name, + G_MARKUP_COLLECT_INVALID)) + goto out; + + g_dbus_interface_info_set (data, + parse_data_get_interface (data, TRUE), + name, + 0, NULL, + 0, NULL, + 0, NULL, + NULL); + + } + /* ---------------------------------------------------------------------------------------------------- */ + else if (strcmp (element_name, "method") == 0) + { + if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0) + { + g_set_error_literal (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " elements can only be embedded in elements"); + goto out; + } + + if (!g_markup_collect_attributes (element_name, + attribute_names, + attribute_values, + error, + G_MARKUP_COLLECT_STRING, "name", &name, + G_MARKUP_COLLECT_INVALID)) + goto out; + + g_dbus_method_info_set (data, + parse_data_get_method (data, TRUE), + name, + 0, NULL, + 0, NULL, + NULL); + + data->num_args = 0; + + } + /* ---------------------------------------------------------------------------------------------------- */ + else if (strcmp (element_name, "signal") == 0) + { + if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0) + { + g_set_error_literal (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " elements can only be embedded in elements"); + goto out; + } + + if (!g_markup_collect_attributes (element_name, + attribute_names, + attribute_values, + error, + G_MARKUP_COLLECT_STRING, "name", &name, + G_MARKUP_COLLECT_INVALID)) + goto out; + + g_dbus_signal_info_set (data, + parse_data_get_signal (data, TRUE), + name, + 0, NULL, + NULL); + + data->num_args = 0; + + } + /* ---------------------------------------------------------------------------------------------------- */ + else if (strcmp (element_name, "property") == 0) + { + GDBusPropertyInfoFlags flags; + + if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0) + { + g_set_error_literal (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " elements can only be embedded in elements"); + goto out; + } + + if (!g_markup_collect_attributes (element_name, + attribute_names, + attribute_values, + error, + G_MARKUP_COLLECT_STRING, "name", &name, + G_MARKUP_COLLECT_STRING, "type", &type, + G_MARKUP_COLLECT_STRING, "access", &access, + G_MARKUP_COLLECT_INVALID)) + goto out; + + if (strcmp (access, "read") == 0) + flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE; + else if (strcmp (access, "write") == 0) + flags = G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE; + else if (strcmp (access, "readwrite") == 0) + flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE; + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Unknown value '%s' of access attribute for element ", + access); + goto out; + } + + g_dbus_property_info_set (data, + parse_data_get_property (data, TRUE), + name, + type, + flags, + NULL); + + } + /* ---------------------------------------------------------------------------------------------------- */ + else if (strcmp (element_name, "arg") == 0) + { + gboolean is_in; + gchar *name_to_use; + + if (g_slist_length (stack) < 2 || + (strcmp (stack->next->data, "method") != 0 && + strcmp (stack->next->data, "signal") != 0)) + { + g_set_error_literal (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " elements can only be embedded in or elements"); + goto out; + } + + if (!g_markup_collect_attributes (element_name, + attribute_names, + attribute_values, + error, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "name", &name, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "direction", &direction, + G_MARKUP_COLLECT_STRING, "type", &type, + G_MARKUP_COLLECT_INVALID)) + goto out; + + is_in = FALSE; + if (direction != NULL) + { + if (strcmp (direction, "in") == 0) + is_in = TRUE; + else if (strcmp (direction, "out") == 0) + is_in = FALSE; + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Unknown value '%s' of direction attribute", + direction); + goto out; + } + } + + if (is_in && strcmp (stack->next->data, "signal") == 0) + { + g_set_error_literal (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Only direction 'out' is allowed for elements embedded in "); + goto out; + } + + if (name == NULL) + name_to_use = g_strdup_printf ("arg_%d", data->num_args); + else + name_to_use = g_strdup (name); + data->num_args++; + + if (is_in) + { + g_dbus_arg_info_set (data, + parse_data_get_arg (data, TRUE), + name_to_use, + type, + NULL); + data->last_arg_was_in = TRUE; + } + else + { + g_dbus_arg_info_set (data, + parse_data_get_out_arg (data, TRUE), + name_to_use, + type, + NULL); + data->last_arg_was_in = FALSE; + + } + + g_free (name_to_use); + } + /* ---------------------------------------------------------------------------------------------------- */ + else if (strcmp (element_name, "annotation") == 0) + { + if (g_slist_length (stack) < 2 || + (strcmp (stack->next->data, "node") != 0 && + strcmp (stack->next->data, "interface") != 0 && + strcmp (stack->next->data, "signal") != 0 && + strcmp (stack->next->data, "method") != 0 && + strcmp (stack->next->data, "property") != 0 && + strcmp (stack->next->data, "arg") != 0 && + strcmp (stack->next->data, "annotation") != 0)) + { + g_set_error_literal (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + " elements can only be embedded in , , , , , or elements"); + goto out; + } + + if (!g_markup_collect_attributes (element_name, + attribute_names, + attribute_values, + error, + G_MARKUP_COLLECT_STRING, "name", &name, + G_MARKUP_COLLECT_STRING, "value", &value, + G_MARKUP_COLLECT_INVALID)) + goto out; + + g_dbus_annotation_info_set (data, + parse_data_get_annotation (data, TRUE), + name, + value, + NULL); + } + /* ---------------------------------------------------------------------------------------------------- */ + else + { + /* don't bail on unknown elements; just ignore them */ + } + /* ---------------------------------------------------------------------------------------------------- */ + + /* push the currently retrieved annotations on the stack and prepare a new one */ + data->annotations_stack = g_slist_prepend (data->annotations_stack, data->annotations); + data->annotations = NULL; + parse_data_steal_annotations (data, NULL); + + out: + ; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusAnnotationInfo ** +steal_annotations (ParseData *data) +{ + return parse_data_steal_annotations (data, NULL); +} + + +static void +parser_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + ParseData *data = user_data; + gboolean have_popped_annotations; + + have_popped_annotations = FALSE; + + if (strcmp (element_name, "node") == 0) + { + guint num_nodes; + guint num_interfaces; + GDBusNodeInfo **nodes; + GDBusInterfaceInfo **interfaces; + + nodes = parse_data_steal_nodes (data, &num_nodes); + interfaces = parse_data_steal_interfaces (data, &num_interfaces); + + /* destroy the nodes, interfaces for scope we're exiting and and pop the nodes, interfaces from the + * scope we're reentering + */ + parse_data_free_interfaces (data); + data->interfaces = (GPtrArray *) data->interfaces_stack->data; + data->interfaces_stack = g_slist_remove (data->interfaces_stack, data->interfaces_stack->data); + + parse_data_free_nodes (data); + data->nodes = (GPtrArray *) data->nodes_stack->data; + data->nodes_stack = g_slist_remove (data->nodes_stack, data->nodes_stack->data); + + g_dbus_node_info_set (data, + parse_data_get_node (data, FALSE), + NULL, + num_interfaces, + interfaces, + num_nodes, + nodes, + steal_annotations (data)); + + } + else if (strcmp (element_name, "interface") == 0) + { + guint num_methods; + guint num_signals; + guint num_properties; + GDBusMethodInfo **methods; + GDBusSignalInfo **signals; + GDBusPropertyInfo **properties; + + methods = parse_data_steal_methods (data, &num_methods); + signals = parse_data_steal_signals (data, &num_signals); + properties = parse_data_steal_properties (data, &num_properties); + + g_dbus_interface_info_set (data, + parse_data_get_interface (data, FALSE), + NULL, + num_methods, + methods, + num_signals, + signals, + num_properties, + properties, + steal_annotations (data)); + + } + else if (strcmp (element_name, "method") == 0) + { + guint in_num_args; + guint out_num_args; + GDBusArgInfo **in_args; + GDBusArgInfo **out_args; + + in_args = parse_data_steal_args (data, &in_num_args); + out_args = parse_data_steal_out_args (data, &out_num_args); + + g_dbus_method_info_set (data, + parse_data_get_method (data, FALSE), + NULL, + in_num_args, + in_args, + out_num_args, + out_args, + steal_annotations (data)); + } + else if (strcmp (element_name, "signal") == 0) + { + guint num_args; + GDBusArgInfo **args; + + args = parse_data_steal_out_args (data, &num_args); + + g_dbus_signal_info_set (data, + parse_data_get_signal (data, FALSE), + NULL, + num_args, + args, + steal_annotations (data)); + } + else if (strcmp (element_name, "property") == 0) + { + g_dbus_property_info_set (data, + parse_data_get_property (data, FALSE), + NULL, + NULL, + G_DBUS_PROPERTY_INFO_FLAGS_NONE, + steal_annotations (data)); + } + else if (strcmp (element_name, "arg") == 0) + { + g_dbus_arg_info_set (data, + data->last_arg_was_in ? parse_data_get_arg (data, FALSE) : parse_data_get_out_arg (data, FALSE), + NULL, + NULL, + steal_annotations (data)); + } + else if (strcmp (element_name, "annotation") == 0) + { + GDBusAnnotationInfo **embedded_annotations; + + embedded_annotations = steal_annotations (data); + + /* destroy the annotations for scope we're exiting and and pop the annotations from the scope we're reentering */ + parse_data_free_annotations (data); + data->annotations = (GPtrArray *) data->annotations_stack->data; + data->annotations_stack = g_slist_remove (data->annotations_stack, data->annotations_stack->data); + + have_popped_annotations = TRUE; + + g_dbus_annotation_info_set (data, + parse_data_get_annotation (data, FALSE), + NULL, + NULL, + embedded_annotations); + } + else + { + /* don't bail on unknown elements; just ignore them */ + } + + if (!have_popped_annotations) + { + /* destroy the annotations for scope we're exiting and and pop the annotations from the scope we're reentering */ + parse_data_free_annotations (data); + data->annotations = (GPtrArray *) data->annotations_stack->data; + data->annotations_stack = g_slist_remove (data->annotations_stack, data->annotations_stack->data); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +parser_error (GMarkupParseContext *context, + GError *error, + gpointer user_data) +{ + gint line_number; + gint char_number; + + g_markup_parse_context_get_position (context, &line_number, &char_number); + + g_prefix_error (&error, "%d:%d: ", + line_number, + char_number); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_node_info_new_for_xml: + * @xml_data: Valid D-Bus introspection XML. + * @error: Return location for error. + * + * Parses @xml_data and returns a #GDBusNodeInfo representing the data. + * + * Returns: A #GDBusNodeInfo structure or %NULL if @error is set. Free + * with g_dbus_node_info_unref(). + * + * Since: 2.26 + */ +GDBusNodeInfo * +g_dbus_node_info_new_for_xml (const gchar *xml_data, + GError **error) +{ + GDBusNodeInfo *ret; + GMarkupParseContext *context; + GMarkupParser *parser; + guint num_nodes; + ParseData *data; + + ret = NULL; + parser = NULL; + context = NULL; + + parser = g_new0 (GMarkupParser, 1); + parser->start_element = parser_start_element; + parser->end_element = parser_end_element; + parser->error = parser_error; + + data = parse_data_new (); + context = g_markup_parse_context_new (parser, + 0, + data, + (GDestroyNotify) parse_data_free); + + if (!g_markup_parse_context_parse (context, + xml_data, + strlen (xml_data), + error)) + goto out; + + GDBusNodeInfo **ughret; + ughret = parse_data_steal_nodes (data, &num_nodes); + + if (num_nodes != 1) + { + guint n; + + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "Expected a single node in introspection XML, found %d", + num_nodes); + + /* clean up */ + for (n = 0; n < num_nodes; n++) + { + for (n = 0; n < num_nodes; n++) + g_dbus_node_info_unref (&(ret[n])); + } + g_free (ret); + ret = NULL; + } + + ret = ughret[0]; + g_free (ughret); + + out: + if (parser != NULL) + g_free (parser); + if (context != NULL) + g_markup_parse_context_free (context); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_annotation_info_lookup: + * @annotations: A %NULL-terminated array of annotations or %NULL. + * @name: The name of the annotation to look up. + * + * Looks up the value of an annotation. + * + * This cost of this function is O(n) in number of annotations. + * + * Returns: The value or %NULL if not found. Do not free, it is owned by @annotations. + * + * Since: 2.26 + */ +const gchar * +g_dbus_annotation_info_lookup (const GDBusAnnotationInfo **annotations, + const gchar *name) +{ + guint n; + const gchar *ret; + + ret = NULL; + for (n = 0; annotations != NULL && annotations[n]->key != NULL; n++) + { + if (g_strcmp0 (annotations[n]->key, name) == 0) + { + ret = annotations[n]->value; + goto out; + } + } + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_interface_info_lookup_method: + * @info: A #GDBusInterfaceInfo. + * @name: A D-Bus method name (typically in CamelCase) + * + * Looks up information about a method. + * + * This cost of this function is O(n) in number of methods. + * + * Returns: A #GDBusMethodInfo or %NULL if not found. Do not free, it is owned by @info. + * + * Since: 2.26 + */ +const GDBusMethodInfo * +g_dbus_interface_info_lookup_method (const GDBusInterfaceInfo *info, + const gchar *name) +{ + guint n; + const GDBusMethodInfo *result; + + for (n = 0; info->methods != NULL && info->methods[n] != NULL; n++) + { + const GDBusMethodInfo *i = info->methods[n]; + + if (g_strcmp0 (i->name, name) == 0) + { + result = i; + goto out; + } + } + + result = NULL; + + out: + return result; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_interface_info_lookup_signal: + * @info: A #GDBusInterfaceInfo. + * @name: A D-Bus signal name (typically in CamelCase) + * + * Looks up information about a signal. + * + * This cost of this function is O(n) in number of signals. + * + * Returns: A #GDBusSignalInfo or %NULL if not found. Do not free, it is owned by @info. + * + * Since: 2.26 + */ +const GDBusSignalInfo * +g_dbus_interface_info_lookup_signal (const GDBusInterfaceInfo *info, + const gchar *name) +{ + guint n; + const GDBusSignalInfo *result; + + for (n = 0; info->signals != NULL && info->signals[n] != NULL; n++) + { + const GDBusSignalInfo *i = info->signals[n]; + + if (g_strcmp0 (i->name, name) == 0) + { + result = i; + goto out; + } + } + + result = NULL; + + out: + return result; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_interface_info_lookup_property: + * @info: A #GDBusInterfaceInfo. + * @name: A D-Bus property name (typically in CamelCase). + * + * Looks up information about a property. + * + * This cost of this function is O(n) in number of properties. + * + * Returns: A #GDBusPropertyInfo or %NULL if not found. Do not free, it is owned by @info. + * + * Since: 2.26 + */ +const GDBusPropertyInfo * +g_dbus_interface_info_lookup_property (const GDBusInterfaceInfo *info, + const gchar *name) +{ + guint n; + const GDBusPropertyInfo *result; + + for (n = 0; info->properties != NULL && info->properties[n] != NULL; n++) + { + const GDBusPropertyInfo *i = info->properties[n]; + + if (g_strcmp0 (i->name, name) == 0) + { + result = i; + goto out; + } + } + + result = NULL; + + out: + return result; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_node_info_lookup_interface: + * @info: A #GDBusNodeInfo. + * @name: A D-Bus interface name. + * + * Looks up information about an interface. + * + * This cost of this function is O(n) in number of interfaces. + * + * Returns: A #GDBusInterfaceInfo or %NULL if not found. Do not free, it is owned by @info. + * + * Since: 2.26 + */ +const GDBusInterfaceInfo * +g_dbus_node_info_lookup_interface (const GDBusNodeInfo *info, + const gchar *name) +{ + guint n; + const GDBusInterfaceInfo *result; + + for (n = 0; info->interfaces != NULL && info->interfaces[n] != NULL; n++) + { + const GDBusInterfaceInfo *i = info->interfaces[n]; + + if (g_strcmp0 (i->name, name) == 0) + { + result = i; + goto out; + } + } + + result = NULL; + + out: + return result; +} + +#define __G_DBUS_INTROSPECTION_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusintrospection.h b/gio/gdbusintrospection.h new file mode 100644 index 000000000..3560ff39f --- /dev/null +++ b/gio/gdbusintrospection.h @@ -0,0 +1,283 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_INTROSPECTION_H__ +#define __G_DBUS_INTROSPECTION_H__ + +#include + +G_BEGIN_DECLS + +/** + * GDBusAnnotationInfo: + * @ref_count: The reference count or -1 if statically allocated. + * @key: The name of the annotation, e.g. "org.freedesktop.DBus.Deprecated". + * @value: The value of the annotation. + * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations. + * + * Information about an annotation. + * + * Since: 2.26 + */ +struct _GDBusAnnotationInfo +{ + volatile gint ref_count; + gchar *key; + gchar *value; + GDBusAnnotationInfo **annotations; +}; + +/** + * GDBusArgInfo: + * @ref_count: The reference count or -1 if statically allocated. + * @name: Name of the argument, e.g. @unix_user_id. + * @signature: D-Bus signature of the argument (a single complete type). + * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations. + * + * Information about an argument for a method or a signal. + * + * Since: 2.26 + */ +struct _GDBusArgInfo +{ + volatile gint ref_count; + gchar *name; + gchar *signature; + GDBusAnnotationInfo **annotations; +}; + +/** + * GDBusMethodInfo: + * @ref_count: The reference count or -1 if statically allocated. + * @name: The name of the D-Bus method, e.g. @RequestName. + * @in_args: A pointer to a %NULL-terminated array of pointers to #GDBusArgInfo structures or %NULL if there are no in arguments. + * @out_args: A pointer to a %NULL-terminated array of pointers to #GDBusArgInfo structures or %NULL if there are no out arguments. + * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations. + * + * Information about a method on an D-Bus interface. + * + * Since: 2.26 + */ +struct _GDBusMethodInfo +{ + volatile gint ref_count; + gchar *name; + GDBusArgInfo **in_args; + GDBusArgInfo **out_args; + GDBusAnnotationInfo **annotations; +}; + +/** + * GDBusSignalInfo: + * @ref_count: The reference count or -1 if statically allocated. + * @name: The name of the D-Bus signal, e.g. "NameOwnerChanged". + * @args: A pointer to a %NULL-terminated array of pointers to #GDBusArgInfo structures or %NULL if there are no arguments. + * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations. + * + * Information about a signal on a D-Bus interface. + * + * Since: 2.26 + */ +struct _GDBusSignalInfo +{ + volatile gint ref_count; + gchar *name; + GDBusArgInfo **args; + GDBusAnnotationInfo **annotations; +}; + +/** + * GDBusPropertyInfo: + * @ref_count: The reference count or -1 if statically allocated. + * @name: The name of the D-Bus property, e.g. "SupportedFilesystems". + * @signature: The D-Bus signature of the property (a single complete type). + * @flags: Access control flags for the property. + * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations. + * + * Information about a D-Bus property on a D-Bus interface. + * + * Since: 2.26 + */ +struct _GDBusPropertyInfo +{ + volatile gint ref_count; + gchar *name; + gchar *signature; + GDBusPropertyInfoFlags flags; + GDBusAnnotationInfo **annotations; +}; + +/** + * GDBusInterfaceInfo: + * @ref_count: The reference count or -1 if statically allocated. + * @name: The name of the D-Bus interface, e.g. "org.freedesktop.DBus.Properties". + * @methods: A pointer to a %NULL-terminated array of pointers to #GDBusMethodInfo structures or %NULL if there are no methods. + * @signals: A pointer to a %NULL-terminated array of pointers to #GDBusSignalInfo structures or %NULL if there are no signals. + * @properties: A pointer to a %NULL-terminated array of pointers to #GDBusPropertyInfo structures or %NULL if there are no properties. + * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations. + * + * Information about a D-Bus interface. + * + * Since: 2.26 + */ +struct _GDBusInterfaceInfo +{ + volatile gint ref_count; + gchar *name; + GDBusMethodInfo **methods; + GDBusSignalInfo **signals; + GDBusPropertyInfo **properties; + GDBusAnnotationInfo **annotations; +}; + +/** + * GDBusNodeInfo: + * @ref_count: The reference count or -1 if statically allocated. + * @path: The path of the node or %NULL if omitted. Note that this may be a relative path. See the D-Bus specification for more details. + * @interfaces: A pointer to a %NULL-terminated array of pointers to #GDBusInterfaceInfo structures or %NULL if there are no interfaces. + * @nodes: A pointer to a %NULL-terminated array of pointers to #GDBusNodeInfo structures or %NULL if there are no nodes. + * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations. + * + * Information about nodes in a remote object hierarchy. + * + * Since: 2.26 + */ +struct _GDBusNodeInfo +{ + volatile gint ref_count; + gchar *path; + GDBusInterfaceInfo **interfaces; + GDBusNodeInfo **nodes; + GDBusAnnotationInfo **annotations; +}; + +const gchar *g_dbus_annotation_info_lookup (const GDBusAnnotationInfo **annotations, + const gchar *name); +const GDBusMethodInfo *g_dbus_interface_info_lookup_method (const GDBusInterfaceInfo *info, + const gchar *name); +const GDBusSignalInfo *g_dbus_interface_info_lookup_signal (const GDBusInterfaceInfo *info, + const gchar *name); +const GDBusPropertyInfo *g_dbus_interface_info_lookup_property (const GDBusInterfaceInfo *info, + const gchar *name); +void g_dbus_interface_info_generate_xml (const GDBusInterfaceInfo *info, + guint indent, + GString *string_builder); + +GDBusNodeInfo *g_dbus_node_info_new_for_xml (const gchar *xml_data, + GError **error); +const GDBusInterfaceInfo *g_dbus_node_info_lookup_interface (const GDBusNodeInfo *info, + const gchar *name); +void g_dbus_node_info_generate_xml (const GDBusNodeInfo *info, + guint indent, + GString *string_builder); + +GDBusNodeInfo *g_dbus_node_info_ref (GDBusNodeInfo *info); +GDBusInterfaceInfo *g_dbus_interface_info_ref (GDBusInterfaceInfo *info); +GDBusMethodInfo *g_dbus_method_info_ref (GDBusMethodInfo *info); +GDBusSignalInfo *g_dbus_signal_info_ref (GDBusSignalInfo *info); +GDBusPropertyInfo *g_dbus_property_info_ref (GDBusPropertyInfo *info); +GDBusArgInfo *g_dbus_arg_info_ref (GDBusArgInfo *info); +GDBusAnnotationInfo *g_dbus_annotation_info_ref (GDBusAnnotationInfo *info); + +void g_dbus_node_info_unref (GDBusNodeInfo *info); +void g_dbus_interface_info_unref (GDBusInterfaceInfo *info); +void g_dbus_method_info_unref (GDBusMethodInfo *info); +void g_dbus_signal_info_unref (GDBusSignalInfo *info); +void g_dbus_property_info_unref (GDBusPropertyInfo *info); +void g_dbus_arg_info_unref (GDBusArgInfo *info); +void g_dbus_annotation_info_unref (GDBusAnnotationInfo *info); + + +/** + * G_TYPE_DBUS_NODE_INFO: + * + * The #GType for a boxed type holding a #GDBusNodeInfo. + * + * Since: 2.26 + */ +#define G_TYPE_DBUS_NODE_INFO (g_dbus_node_info_get_type ()) + +/** + * G_TYPE_DBUS_INTERFACE_INFO: + * + * The #GType for a boxed type holding a #GDBusInterfaceInfo. + * + * Since: 2.26 + */ +#define G_TYPE_DBUS_INTERFACE_INFO (g_dbus_interface_info_get_type ()) + +/** + * G_TYPE_DBUS_METHOD_INFO: + * + * The #GType for a boxed type holding a #GDBusMethodInfo. + * + * Since: 2.26 + */ +#define G_TYPE_DBUS_METHOD_INFO (g_dbus_method_info_get_type ()) + +/** + * G_TYPE_DBUS_SIGNAL_INFO: + * + * The #GType for a boxed type holding a #GDBusSignalInfo. + * + * Since: 2.26 + */ +#define G_TYPE_DBUS_SIGNAL_INFO (g_dbus_signal_info_get_type ()) + +/** + * G_TYPE_DBUS_PROPERTY_INFO: + * + * The #GType for a boxed type holding a #GDBusPropertyInfo. + * + * Since: 2.26 + */ +#define G_TYPE_DBUS_PROPERTY_INFO (g_dbus_property_info_get_type ()) + +/** + * G_TYPE_DBUS_ARG_INFO: + * + * The #GType for a boxed type holding a #GDBusArgInfo. + * + * Since: 2.26 + */ +#define G_TYPE_DBUS_ARG_INFO (g_dbus_arg_info_get_type ()) + +/** + * G_TYPE_DBUS_ANNOTATION_INFO: + * + * The #GType for a boxed type holding a #GDBusAnnotationInfo. + * + * Since: 2.26 + */ +#define G_TYPE_DBUS_ANNOTATION_INFO (g_dbus_annotation_info_get_type ()) + +GType g_dbus_node_info_get_type (void) G_GNUC_CONST; +GType g_dbus_interface_info_get_type (void) G_GNUC_CONST; +GType g_dbus_method_info_get_type (void) G_GNUC_CONST; +GType g_dbus_signal_info_get_type (void) G_GNUC_CONST; +GType g_dbus_property_info_get_type (void) G_GNUC_CONST; +GType g_dbus_arg_info_get_type (void) G_GNUC_CONST; +GType g_dbus_annotation_info_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __G_DBUS_INTROSPECTION_H__ */ diff --git a/gio/gdbusmessage.c b/gio/gdbusmessage.c new file mode 100644 index 000000000..bfc46f096 --- /dev/null +++ b/gio/gdbusmessage.c @@ -0,0 +1,2522 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include + +#include "gdbusutils.h" +#include "gdbusmessage.h" +#include "gdbuserror.h" +#include "gioenumtypes.h" +#include "ginputstream.h" +#include "gdatainputstream.h" +#include "gmemoryinputstream.h" +#include "goutputstream.h" +#include "gdataoutputstream.h" +#include "gmemoryoutputstream.h" +#include "gseekable.h" +#include "gioerror.h" + +#ifdef G_OS_UNIX +#include "gunixfdlist.h" + +#include +#include +#include +#include +#endif + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusmessage + * @short_description: D-Bus Message + * @include: gio/gio.h + * + * A type for representing D-Bus messages that can be sent or received + * on a #GDBusConnection. + */ + +struct _GDBusMessagePrivate +{ + GDBusMessageType type; + GDBusMessageFlags flags; + guchar major_protocol_version; + guint32 serial; + GHashTable *headers; + GVariant *body; +#ifdef G_OS_UNIX + GUnixFDList *fd_list; +#endif +}; + +G_DEFINE_TYPE (GDBusMessage, g_dbus_message, G_TYPE_OBJECT); + +static void +g_dbus_message_finalize (GObject *object) +{ + GDBusMessage *message = G_DBUS_MESSAGE (object); + + if (message->priv->headers != NULL) + g_hash_table_unref (message->priv->headers); + if (message->priv->body != NULL) + g_variant_unref (message->priv->body); +#ifdef G_OS_UNIX + if (message->priv->fd_list != NULL) + g_object_unref (message->priv->fd_list); +#endif + + if (G_OBJECT_CLASS (g_dbus_message_parent_class)->finalize != NULL) + G_OBJECT_CLASS (g_dbus_message_parent_class)->finalize (object); +} + +static void +g_dbus_message_class_init (GDBusMessageClass *klass) +{ + GObjectClass *gobject_class; + + g_type_class_add_private (klass, sizeof (GDBusMessagePrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_dbus_message_finalize; +} + +static void +g_dbus_message_init (GDBusMessage *message) +{ + message->priv = G_TYPE_INSTANCE_GET_PRIVATE (message, G_TYPE_DBUS_MESSAGE, GDBusMessagePrivate); + + message->priv->headers = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify) g_variant_unref); +} + +/** + * g_dbus_message_new: + * + * Creates a new empty #GDBusMessage. + * + * Returns: A #GDBusMessage. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusMessage * +g_dbus_message_new (void) +{ + return g_object_new (G_TYPE_DBUS_MESSAGE, NULL); +} + +/** + * g_dbus_message_new_method_call: + * @name: A valid D-Bus name or %NULL. + * @path: A valid object path. + * @interface: A valid D-Bus interface name or %NULL. + * @method: A valid method name. + * + * Creates a new #GDBusMessage for a method call. + * + * Returns: A #GDBusMessage. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusMessage * +g_dbus_message_new_method_call (const gchar *name, + const gchar *path, + const gchar *interface, + const gchar *method) +{ + GDBusMessage *message; + + g_return_val_if_fail (name == NULL || g_dbus_is_name (name), NULL); + g_return_val_if_fail (g_variant_is_object_path (path), NULL); + g_return_val_if_fail (g_dbus_is_member_name (method), NULL); + g_return_val_if_fail (interface == NULL || g_dbus_is_interface_name (interface), NULL); + + message = g_dbus_message_new (); + message->priv->type = G_DBUS_MESSAGE_TYPE_METHOD_CALL; + + if (name != NULL) + g_dbus_message_set_destination (message, name); + g_dbus_message_set_path (message, path); + g_dbus_message_set_member (message, method); + if (interface != NULL) + g_dbus_message_set_interface (message, interface); + + return message; +} + +/** + * g_dbus_message_new_signal: + * @path: A valid object path. + * @interface: A valid D-Bus interface name or %NULL. + * @signal: A valid signal name. + * + * Creates a new #GDBusMessage for a signal emission. + * + * Returns: A #GDBusMessage. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusMessage * +g_dbus_message_new_signal (const gchar *path, + const gchar *interface, + const gchar *signal) +{ + GDBusMessage *message; + + g_return_val_if_fail (g_variant_is_object_path (path), NULL); + g_return_val_if_fail (g_dbus_is_member_name (signal), NULL); + g_return_val_if_fail (interface == NULL || g_dbus_is_interface_name (interface), NULL); + + message = g_dbus_message_new (); + message->priv->type = G_DBUS_MESSAGE_TYPE_SIGNAL; + message->priv->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; + + g_dbus_message_set_path (message, path); + g_dbus_message_set_member (message, signal); + + if (interface != NULL) + g_dbus_message_set_interface (message, interface); + + return message; +} + + +/** + * g_dbus_message_new_method_reply: + * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to + * create a reply message to. + * + * Creates a new #GDBusMessage that is a reply to @method_call_message. + * + * Returns: A #GDBusMessage. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusMessage * +g_dbus_message_new_method_reply (GDBusMessage *method_call_message) +{ + GDBusMessage *message; + const gchar *sender; + + g_return_val_if_fail (G_IS_DBUS_MESSAGE (method_call_message), NULL); + g_return_val_if_fail (g_dbus_message_get_message_type (method_call_message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL, NULL); + g_return_val_if_fail (g_dbus_message_get_serial (method_call_message) != 0, NULL); + + message = g_dbus_message_new (); + message->priv->type = G_DBUS_MESSAGE_TYPE_METHOD_RETURN; + message->priv->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; + + g_dbus_message_set_reply_serial (message, g_dbus_message_get_serial (method_call_message)); + sender = g_dbus_message_get_sender (method_call_message); + if (sender != NULL) + g_dbus_message_set_destination (message, sender); + + return message; +} + +/** + * g_dbus_message_new_method_error: + * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to + * create a reply message to. + * @error_name: A valid D-Bus error name. + * @error_message_format: The D-Bus error message in a printf() format. + * @...: Arguments for @error_message_format. + * + * Creates a new #GDBusMessage that is an error reply to @method_call_message. + * + * Returns: A #GDBusMessage. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusMessage * +g_dbus_message_new_method_error (GDBusMessage *method_call_message, + const gchar *error_name, + const gchar *error_message_format, + ...) +{ + GDBusMessage *ret; + va_list var_args; + + va_start (var_args, error_message_format); + ret = g_dbus_message_new_method_error_valist (method_call_message, + error_name, + error_message_format, + var_args); + va_end (var_args); + + return ret; +} + +/** + * g_dbus_message_new_method_error_literal: + * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to + * create a reply message to. + * @error_name: A valid D-Bus error name. + * @error_message: The D-Bus error message. + * + * Creates a new #GDBusMessage that is an error reply to @method_call_message. + * + * Returns: A #GDBusMessage. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusMessage * +g_dbus_message_new_method_error_literal (GDBusMessage *method_call_message, + const gchar *error_name, + const gchar *error_message) +{ + GDBusMessage *message; + const gchar *sender; + + g_return_val_if_fail (G_IS_DBUS_MESSAGE (method_call_message), NULL); + g_return_val_if_fail (g_dbus_message_get_message_type (method_call_message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL, NULL); + g_return_val_if_fail (g_dbus_message_get_serial (method_call_message) != 0, NULL); + g_return_val_if_fail (g_dbus_is_name (error_name), NULL); + g_return_val_if_fail (error_message != NULL, NULL); + + message = g_dbus_message_new (); + message->priv->type = G_DBUS_MESSAGE_TYPE_ERROR; + message->priv->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; + + g_dbus_message_set_reply_serial (message, g_dbus_message_get_serial (method_call_message)); + g_dbus_message_set_error_name (message, error_name); + g_dbus_message_set_body (message, g_variant_new ("(s)", error_message)); + + sender = g_dbus_message_get_sender (method_call_message); + if (sender != NULL) + g_dbus_message_set_destination (message, sender); + + return message; +} + +/** + * g_dbus_message_new_method_error_valist: + * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to + * create a reply message to. + * @error_name: A valid D-Bus error name. + * @error_message_format: The D-Bus error message in a printf() format. + * @var_args: Arguments for @error_message_format. + * + * Like g_dbus_message_new_method_error() but intended for language bindings. + * + * Returns: A #GDBusMessage. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusMessage * +g_dbus_message_new_method_error_valist (GDBusMessage *method_call_message, + const gchar *error_name, + const gchar *error_message_format, + va_list var_args) +{ + GDBusMessage *ret; + gchar *error_message; + error_message = g_strdup_vprintf (error_message_format, var_args); + ret = g_dbus_message_new_method_error_literal (method_call_message, + error_name, + error_message); + g_free (error_message); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* TODO: need GI annotations to specify that any guchar value goes for the type */ + +/** + * g_dbus_message_get_message_type: + * @message: A #GDBusMessage. + * + * Gets the type of @message. + * + * Returns: A 8-bit unsigned integer (typically a value from the #GDBusMessageType enumeration). + * + * Since: 2.26 + */ +GDBusMessageType +g_dbus_message_get_message_type (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), G_DBUS_MESSAGE_TYPE_INVALID); + return message->priv->type; +} + +/** + * g_dbus_message_set_message_type: + * @message: A #GDBusMessage. + * @type: A 8-bit unsigned integer (typically a value from the #GDBusMessageType enumeration). + * + * Sets @message to be of @type. + * + * Since: 2.26 + */ +void +g_dbus_message_set_message_type (GDBusMessage *message, + GDBusMessageType type) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (type >=0 && type < 256); + message->priv->type = type; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* TODO: need GI annotations to specify that any guchar value goes for flags */ + +/** + * g_dbus_message_get_flags: + * @message: A #GDBusMessage. + * + * Gets the flags for @message. + * + * Returns: Flags that are set (typically values from the #GDBusMessageFlags enumeration bitwise ORed together). + * + * Since: 2.26 + */ +GDBusMessageFlags +g_dbus_message_get_flags (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), G_DBUS_MESSAGE_FLAGS_NONE); + return message->priv->flags; +} + +/** + * g_dbus_message_set_flags: + * @message: A #GDBusMessage. + * @flags: Flags for @message that are set (typically values from the #GDBusMessageFlags + * enumeration bitwise ORed together). + * + * Sets the flags to set on @message. + * + * Since: 2.26 + */ +void +g_dbus_message_set_flags (GDBusMessage *message, + GDBusMessageFlags flags) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (flags >=0 && flags < 256); + message->priv->flags = flags; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_serial: + * @message: A #GDBusMessage. + * + * Gets the serial for @message. + * + * Returns: A #guint32. + * + * Since: 2.26 + */ +guint32 +g_dbus_message_get_serial (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0); + return message->priv->serial; +} + +/** + * g_dbus_message_set_serial: + * @message: A #GDBusMessage. + * @serial: A #guint32. + * + * Sets the serial for @message. + * + * Since: 2.26 + */ +void +g_dbus_message_set_serial (GDBusMessage *message, + guint32 serial) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + message->priv->serial = serial; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* TODO: need GI annotations to specify that any guchar value goes for header_field */ + +/** + * g_dbus_message_get_header: + * @message: A #GDBusMessage. + * @header_field: A 8-bit unsigned integer (typically a value from the #GDBusMessageHeaderField enumeration) + * + * Gets a header field on @message. + * + * Returns: A #GVariant with the value if the header was found, %NULL + * otherwise. Do not free, it is owned by @message. + * + * Since: 2.26 + */ +GVariant * +g_dbus_message_get_header (GDBusMessage *message, + GDBusMessageHeaderField header_field) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + g_return_val_if_fail (header_field >=0 && header_field < 256, NULL); + return g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field)); +} + +/** + * g_dbus_message_set_header: + * @message: A #GDBusMessage. + * @header_field: A 8-bit unsigned integer (typically a value from the #GDBusMessageHeaderField enumeration) + * @value: A #GVariant to set the header field or %NULL to clear the header field. + * + * Sets a header field on @message. + * + * If @value is floating, @message assumes ownership of @value. + * + * Since: 2.26 + */ +void +g_dbus_message_set_header (GDBusMessage *message, + GDBusMessageHeaderField header_field, + GVariant *value) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (header_field >=0 && header_field < 256); + if (value == NULL) + { + g_hash_table_remove (message->priv->headers, GUINT_TO_POINTER (header_field)); + } + else + { + g_hash_table_insert (message->priv->headers, GUINT_TO_POINTER (header_field), g_variant_ref_sink (value)); + } +} + +/** + * g_dbus_message_get_header_fields: + * @message: A #GDBusMessage. + * + * Gets an array of all header fields on @message that are set. + * + * Returns: An array of header fields terminated by + * %G_DBUS_MESSAGE_HEADER_FIELD_INVALID. Each element is a + * #guchar. Free with g_free(). + * + * Since: 2.26 + */ +guchar * +g_dbus_message_get_header_fields (GDBusMessage *message) +{ + GList *keys; + guchar *ret; + guint num_keys; + GList *l; + guint n; + + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + + keys = g_hash_table_get_keys (message->priv->headers); + num_keys = g_list_length (keys); + ret = g_new (guchar, num_keys + 1); + for (l = keys, n = 0; l != NULL; l = l->next, n++) + ret[n] = GPOINTER_TO_UINT (l->data); + g_assert (n == num_keys); + ret[n] = G_DBUS_MESSAGE_HEADER_FIELD_INVALID; + g_list_free (keys); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_body: + * @message: A #GDBusMessage. + * + * Gets the body of a message. + * + * Returns: A #GVariant or %NULL if the body is empty. Do not free, it is owned by @message. + * + * Since: 2.26 + */ +GVariant * +g_dbus_message_get_body (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + return message->priv->body; +} + +/** + * g_dbus_message_set_body: + * @message: A #GDBusMessage. + * @body: Either %NULL or a #GVariant that is a tuple. + * + * Sets the body @message. As a side-effect the + * %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field is set to the + * type string of @body (or cleared if @body is %NULL). + * + * If @body is floating, @message assumes ownership of @body. + * + * Since: 2.26 + */ +void +g_dbus_message_set_body (GDBusMessage *message, + GVariant *body) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail ((body == NULL) || g_variant_is_of_type (body, G_VARIANT_TYPE_TUPLE)); + + if (message->priv->body != NULL) + g_variant_unref (message->priv->body); + if (body == NULL) + { + message->priv->body = NULL; + g_dbus_message_set_signature (message, NULL); + } + else + { + const gchar *type_string; + gsize type_string_len; + gchar *signature; + + message->priv->body = g_variant_ref_sink (body); + + type_string = g_variant_get_type_string (body); + type_string_len = strlen (type_string); + g_assert (type_string_len >= 2); + signature = g_strndup (type_string + 1, type_string_len - 2); + g_dbus_message_set_signature (message, signature); + g_free (signature); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#ifdef G_OS_UNIX +/** + * g_dbus_message_get_unix_fd_list: + * @message: A #GDBusMessage. + * + * Gets the UNIX file descriptors associated with @message, if any. + * + * This method is only available on UNIX. + * + * Returns: A #GUnixFDList or %NULL if no file descriptors are + * associated. Do not free, this object is owned by @message. + * + * Since: 2.26 + */ +GUnixFDList * +g_dbus_message_get_unix_fd_list (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + return message->priv->fd_list; +} + +/** + * g_dbus_message_set_unix_fd_list: + * @message: A #GDBusMessage. + * @fd_list: A #GUnixFDList or %NULL. + * + * Sets the UNIX file descriptors associated with @message. As a + * side-effect the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header + * field is set to the number of fds in @fd_list (or cleared if + * @fd_list is %NULL). + * + * This method is only available on UNIX. + * + * Since: 2.26 + */ +void +g_dbus_message_set_unix_fd_list (GDBusMessage *message, + GUnixFDList *fd_list) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (fd_list == NULL || G_IS_UNIX_FD_LIST (fd_list)); + if (message->priv->fd_list != NULL) + g_object_unref (message->priv->fd_list); + if (fd_list != NULL) + { + message->priv->fd_list = g_object_ref (fd_list); + g_dbus_message_set_num_unix_fds (message, g_unix_fd_list_get_length (fd_list)); + } + else + { + message->priv->fd_list = NULL; + g_dbus_message_set_num_unix_fds (message, 0); + } +} +#endif + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +ensure_input_padding (GMemoryInputStream *mis, + gsize padding_size, + GError **error) +{ + gsize offset; + gsize wanted_offset; + + offset = g_seekable_tell (G_SEEKABLE (mis)); + wanted_offset = ((offset + padding_size - 1) / padding_size) * padding_size; + + return g_seekable_seek (G_SEEKABLE (mis), wanted_offset, G_SEEK_SET, NULL, error); +} + +static gchar * +read_string (GMemoryInputStream *mis, + GDataInputStream *dis, + gsize len, + GError **error) +{ + GString *s; + gchar buf[256]; + gsize remaining; + guchar nul; + GError *local_error; + + s = g_string_new (NULL); + + remaining = len; + while (remaining > 0) + { + gsize to_read; + gssize num_read; + + to_read = MIN (remaining, sizeof (buf)); + num_read = g_input_stream_read (G_INPUT_STREAM (mis), + buf, + to_read, + NULL, + error); + if (num_read < 0) + goto fail; + if (num_read == 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Wanted to read %" G_GSIZE_FORMAT " bytes but got EOF"), + to_read); + goto fail; + } + + remaining -= num_read; + g_string_append_len (s, buf, num_read); + } + + local_error = NULL; + nul = g_data_input_stream_read_byte (dis, NULL, &local_error); + if (local_error != NULL) + { + g_propagate_error (error, local_error); + goto fail; + } + if (nul != '\0') + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Expected NUL byte after the string `%s' but found `%c' (%d)"), + s->str, nul, nul); + goto fail; + } + + return g_string_free (s, FALSE); + + fail: + g_string_free (s, TRUE); + return NULL; +} + +static GVariant * +parse_value_from_blob (GMemoryInputStream *mis, + GDataInputStream *dis, + const GVariantType *type, + GError **error) +{ + GVariant *ret; + GError *local_error; + + local_error = NULL; + if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN)) + { + gboolean v; + if (!ensure_input_padding (mis, 4, &local_error)) + goto fail; + v = g_data_input_stream_read_uint32 (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + ret = g_variant_new_boolean (v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE)) + { + guchar v; + v = g_data_input_stream_read_byte (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + ret = g_variant_new_byte (v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16)) + { + gint16 v; + if (!ensure_input_padding (mis, 2, &local_error)) + goto fail; + v = g_data_input_stream_read_int16 (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + ret = g_variant_new_int16 (v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16)) + { + guint16 v; + if (!ensure_input_padding (mis, 2, &local_error)) + goto fail; + v = g_data_input_stream_read_uint16 (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + ret = g_variant_new_uint16 (v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32)) + { + gint32 v; + if (!ensure_input_padding (mis, 4, &local_error)) + goto fail; + v = g_data_input_stream_read_int32 (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + ret = g_variant_new_int32 (v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32)) + { + guint32 v; + if (!ensure_input_padding (mis, 4, &local_error)) + goto fail; + v = g_data_input_stream_read_uint32 (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + ret = g_variant_new_uint32 (v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64)) + { + gint64 v; + if (!ensure_input_padding (mis, 8, &local_error)) + goto fail; + v = g_data_input_stream_read_int64 (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + ret = g_variant_new_int64 (v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64)) + { + guint64 v; + if (!ensure_input_padding (mis, 8, &local_error)) + goto fail; + v = g_data_input_stream_read_uint64 (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + ret = g_variant_new_uint64 (v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE)) + { + guint64 v; + gdouble *encoded; + if (!ensure_input_padding (mis, 8, &local_error)) + goto fail; + v = g_data_input_stream_read_uint64 (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + /* TODO: hmm */ + encoded = (gdouble *) &v; + ret = g_variant_new_double (*encoded); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) + { + guint32 len; + gchar *v; + if (!ensure_input_padding (mis, 4, &local_error)) + goto fail; + len = g_data_input_stream_read_uint32 (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + v = read_string (mis, dis, (gsize) len, &local_error); + if (v == NULL) + goto fail; + ret = g_variant_new_string (v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH)) + { + guint32 len; + gchar *v; + if (!ensure_input_padding (mis, 4, &local_error)) + goto fail; + len = g_data_input_stream_read_uint32 (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + v = read_string (mis, dis, (gsize) len, &local_error); + if (v == NULL) + goto fail; + if (!g_variant_is_object_path (v)) + { + g_set_error (&local_error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Parsed value `%s' is not a valid D-Bus object path"), + v); + goto fail; + } + ret = g_variant_new_object_path (v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE)) + { + guchar len; + gchar *v; + len = g_data_input_stream_read_byte (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + v = read_string (mis, dis, (gsize) len, &local_error); + if (v == NULL) + goto fail; + if (!g_variant_is_signature (v)) + { + g_set_error (&local_error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Parsed value `%s' is not a valid D-Bus signature"), + v); + goto fail; + } + ret = g_variant_new_signature (v); + } + else if (g_variant_type_is_array (type)) + { + guint32 array_len; + goffset offset; + goffset target; + const GVariantType *element_type; + GVariantBuilder *builder; + + if (!ensure_input_padding (mis, 4, &local_error)) + goto fail; + array_len = g_data_input_stream_read_uint32 (dis, NULL, &local_error); + + if (array_len > (2<<26)) + { + g_set_error (&local_error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Encountered array of length %" G_GUINT32_FORMAT " bytes. Maximum length is 2<<26 bytes."), + array_len); + goto fail; + } + + builder = g_variant_builder_new (type); + element_type = g_variant_type_element (type); + + /* TODO: optimize array of primitive types */ + + offset = g_seekable_tell (G_SEEKABLE (mis)); + target = offset + array_len; + while (offset < target) + { + GVariant *item; + item = parse_value_from_blob (mis, dis, element_type, &local_error); + if (item == NULL) + { + g_variant_builder_unref (builder); + goto fail; + } + g_variant_builder_add_value (builder, item); + offset = g_seekable_tell (G_SEEKABLE (mis)); + } + + ret = g_variant_builder_end (builder); + } + else if (g_variant_type_is_dict_entry (type)) + { + const GVariantType *key_type; + const GVariantType *value_type; + GVariant *key; + GVariant *value; + + if (!ensure_input_padding (mis, 8, &local_error)) + goto fail; + + key_type = g_variant_type_key (type); + key = parse_value_from_blob (mis, dis, key_type, &local_error); + if (key == NULL) + goto fail; + + value_type = g_variant_type_value (type); + value = parse_value_from_blob (mis, dis, value_type, &local_error); + if (value == NULL) + { + g_variant_unref (key); + goto fail; + } + ret = g_variant_new_dict_entry (key, value); + } + else if (g_variant_type_is_tuple (type)) + { + const GVariantType *element_type; + GVariantBuilder *builder; + + if (!ensure_input_padding (mis, 8, &local_error)) + goto fail; + + builder = g_variant_builder_new (type); + element_type = g_variant_type_first (type); + while (element_type != NULL) + { + GVariant *item; + item = parse_value_from_blob (mis, dis, element_type, &local_error); + if (item == NULL) + { + g_variant_builder_unref (builder); + goto fail; + } + g_variant_builder_add_value (builder, item); + + element_type = g_variant_type_next (element_type); + } + ret = g_variant_builder_end (builder); + } + else if (g_variant_type_is_variant (type)) + { + guchar siglen; + gchar *sig; + GVariantType *variant_type; + GVariant *value; + + siglen = g_data_input_stream_read_byte (dis, NULL, &local_error); + if (local_error != NULL) + goto fail; + sig = read_string (mis, dis, (gsize) siglen, &local_error); + if (sig == NULL) + goto fail; + if (!g_variant_is_signature (sig)) + { + g_set_error (&local_error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Parsed value `%s' for variant is not a valid D-Bus signature"), + sig); + goto fail; + } + variant_type = g_variant_type_new (sig); + value = parse_value_from_blob (mis, dis, variant_type, &local_error); + g_variant_type_free (variant_type); + if (value == NULL) + goto fail; + ret = g_variant_new_variant (value); + } + else + { + gchar *s; + s = g_variant_type_dup_string (type); + g_set_error (&local_error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Error deserializing GVariant with type-string `%s' from the D-Bus wire format"), + s); + g_free (s); + goto fail; + } + + g_assert (ret != NULL); + return ret; + + fail: + g_propagate_error (error, local_error); + return NULL; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* message_header must be at least 16 bytes */ + +/** + * g_dbus_message_bytes_needed: + * @blob: A blob represent a binary D-Bus message. + * @blob_len: The length of @blob (must be at least 16). + * @error: Return location for error or %NULL. + * + * Utility function to calculate how many bytes are needed to + * completely deserialize the D-Bus message stored at @blob. + * + * Returns: Number of bytes needed or -1 if @error is set (e.g. if + * @blob contains invalid data or not enough data is available to + * determine the size). + * + * Since: 2.26 + */ +gssize +g_dbus_message_bytes_needed (guchar *blob, + gsize blob_len, + GError **error) +{ + gssize ret; + + ret = -1; + + g_return_val_if_fail (blob != NULL, -1); + g_return_val_if_fail (error == NULL || *error == NULL, -1); + g_return_val_if_fail (blob_len >= 16, -1); + + if (blob[0] == 'l') + { + /* core header (12 bytes) + ARRAY of STRUCT of (BYTE,VARIANT) */ + ret = 12 + 4 + GUINT32_FROM_LE (((guint32 *) blob)[3]); + /* round up so it's a multiple of 8 */ + ret = 8 * ((ret + 7)/8); + /* finally add the body size */ + ret += GUINT32_FROM_LE (((guint32 *) blob)[1]); + } + else if (blob[0] == 'B') + { + /* TODO */ + g_assert_not_reached (); + } + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Unable to determine message blob length - given blob is malformed"); + } + + if (ret > (2<<27)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Blob indicates that message exceeds maximum message length (128MiB)"); + ret = -1; + } + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_new_from_blob: + * @blob: A blob represent a binary D-Bus message. + * @blob_len: The length of @blob. + * @capabilities: A #GDBusCapabilityFlags describing what protocol features are supported. + * @error: Return location for error or %NULL. + * + * Creates a new #GDBusMessage from the data stored at @blob. + * + * Returns: A new #GDBusMessage or %NULL if @error is set. Free with + * g_object_unref(). + * + * Since: 2.26 + */ +GDBusMessage * +g_dbus_message_new_from_blob (guchar *blob, + gsize blob_len, + GDBusCapabilityFlags capabilities, + GError **error) +{ + gboolean ret; + GMemoryInputStream *mis; + GDataInputStream *dis; + GDBusMessage *message; + guchar endianness; + guchar major_protocol_version; + GDataStreamByteOrder byte_order; + guint32 message_body_len; + GVariant *headers; + GVariant *item; + GVariantIter iter; + GVariant *signature; + + /* TODO: check against @capabilities */ + + ret = FALSE; + + g_return_val_if_fail (blob != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (blob_len >= 12, NULL); + + message = g_dbus_message_new (); + + mis = G_MEMORY_INPUT_STREAM (g_memory_input_stream_new_from_data (blob, blob_len, NULL)); + dis = g_data_input_stream_new (G_INPUT_STREAM (mis)); + + endianness = g_data_input_stream_read_byte (dis, NULL, NULL); + switch (endianness) + { + case 'l': + byte_order = G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN; + break; + case 'B': + byte_order = G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN; + break; + default: + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid endianness value. Expected 'l' or 'B' but found '%c' (%d)"), + endianness, endianness); + goto out; + } + g_data_input_stream_set_byte_order (dis, byte_order); + + message->priv->type = g_data_input_stream_read_byte (dis, NULL, NULL); + message->priv->flags = g_data_input_stream_read_byte (dis, NULL, NULL); + major_protocol_version = g_data_input_stream_read_byte (dis, NULL, NULL); + if (major_protocol_version != 1) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid major protocol version. Expected 1 but found %d"), + major_protocol_version); + goto out; + } + message_body_len = g_data_input_stream_read_uint32 (dis, NULL, NULL); + message->priv->serial = g_data_input_stream_read_uint32 (dis, NULL, NULL); + + headers = parse_value_from_blob (mis, + dis, + G_VARIANT_TYPE ("a{yv}"), + error); + if (headers == NULL) + goto out; + g_variant_ref_sink (headers); + g_variant_iter_init (&iter, headers); + while ((item = g_variant_iter_next_value (&iter))) + { + guchar header_field; + GVariant *value; + g_variant_get (item, + "{yv}", + &header_field, + &value); + g_dbus_message_set_header (message, header_field, value); + } + g_variant_unref (headers); + + signature = g_dbus_message_get_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE); + if (signature != NULL) + { + const gchar *signature_str; + gsize signature_str_len; + + signature_str = g_variant_get_string (signature, NULL); + signature_str_len = strlen (signature_str); + + /* signature but no body */ + if (message_body_len == 0 && signature_str_len > 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Signature header with signature `%s' found but message body is empty"), + signature_str); + goto out; + } + else if (signature_str_len > 0) + { + GVariantType *variant_type; + gchar *tupled_signature_str; + + if (!g_variant_is_signature (signature_str)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Parsed value `%s' is not a valid D-Bus signature (for body)"), + signature_str); + goto out; + } + tupled_signature_str = g_strdup_printf ("(%s)", signature_str); + variant_type = g_variant_type_new (tupled_signature_str); + g_free (tupled_signature_str); + message->priv->body = parse_value_from_blob (mis, + dis, + variant_type, + error); + if (message->priv->body == NULL) + { + g_variant_type_free (variant_type); + goto out; + } + g_variant_ref_sink (message->priv->body); + g_variant_type_free (variant_type); + } + } + else + { + /* no signature, this is only OK if the body is empty */ + if (message_body_len != 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("No signature header in message but the message body is %" G_GUINT32_FORMAT " bytes"), + message_body_len); + goto out; + } + } + + + ret = TRUE; + + out: + g_object_unref (dis); + g_object_unref (mis); + + if (ret) + { + return message; + } + else + { + if (message != NULL) + g_object_unref (message); + return NULL; + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gsize +ensure_output_padding (GMemoryOutputStream *mos, + GDataOutputStream *dos, + gsize padding_size) +{ + gsize offset; + gsize wanted_offset; + gsize padding_needed; + guint n; + + offset = g_memory_output_stream_get_data_size (mos); + wanted_offset = ((offset + padding_size - 1) / padding_size) * padding_size; + padding_needed = wanted_offset - offset; + + for (n = 0; n < padding_needed; n++) + g_data_output_stream_put_byte (dos, '\0', NULL, NULL); + + return padding_needed; +} + +static gboolean +append_value_to_blob (GVariant *value, + GMemoryOutputStream *mos, + GDataOutputStream *dos, + gsize *out_padding_added, + GError **error) +{ + const GVariantType *type; + gsize padding_added; + + padding_added = 0; + + type = g_variant_get_type (value); + if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN)) + { + gboolean v = g_variant_get_boolean (value); + padding_added = ensure_output_padding (mos, dos, 4); + g_data_output_stream_put_uint32 (dos, v, NULL, NULL); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE)) + { + guint8 v = g_variant_get_byte (value); + g_data_output_stream_put_byte (dos, v, NULL, NULL); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16)) + { + gint16 v = g_variant_get_int16 (value); + padding_added = ensure_output_padding (mos, dos, 2); + g_data_output_stream_put_int16 (dos, v, NULL, NULL); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16)) + { + guint16 v = g_variant_get_uint16 (value); + padding_added = ensure_output_padding (mos, dos, 2); + g_data_output_stream_put_uint16 (dos, v, NULL, NULL); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32)) + { + gint32 v = g_variant_get_int32 (value); + padding_added = ensure_output_padding (mos, dos, 4); + g_data_output_stream_put_int32 (dos, v, NULL, NULL); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32)) + { + guint32 v = g_variant_get_uint32 (value); + padding_added = ensure_output_padding (mos, dos, 4); + g_data_output_stream_put_uint32 (dos, v, NULL, NULL); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64)) + { + gint64 v = g_variant_get_int64 (value); + padding_added = ensure_output_padding (mos, dos, 8); + g_data_output_stream_put_int64 (dos, v, NULL, NULL); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64)) + { + guint64 v = g_variant_get_uint64 (value); + padding_added = ensure_output_padding (mos, dos, 8); + g_data_output_stream_put_uint64 (dos, v, NULL, NULL); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE)) + { + guint64 *encoded; + gdouble v = g_variant_get_double (value); + padding_added = ensure_output_padding (mos, dos, 8); + /* TODO: hmm */ + encoded = (guint64 *) &v; + g_data_output_stream_put_uint64 (dos, *encoded, NULL, NULL); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) + { + const gchar *v = g_variant_get_string (value, NULL); + gsize len; + padding_added = ensure_output_padding (mos, dos, 4); + len = strlen (v); + g_data_output_stream_put_uint32 (dos, len, NULL, NULL); + g_data_output_stream_put_string (dos, v, NULL, NULL); + g_data_output_stream_put_byte (dos, '\0', NULL, NULL); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH)) + { + /* TODO: validate object path */ + const gchar *v = g_variant_get_string (value, NULL); + gsize len; + padding_added = ensure_output_padding (mos, dos, 4); + len = strlen (v); + g_data_output_stream_put_uint32 (dos, len, NULL, NULL); + g_data_output_stream_put_string (dos, v, NULL, NULL); + g_data_output_stream_put_byte (dos, '\0', NULL, NULL); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE)) + { + /* TODO: validate signature (including max len being 255) */ + const gchar *v = g_variant_get_string (value, NULL); + gsize len; + len = strlen (v); + g_data_output_stream_put_byte (dos, len, NULL, NULL); + g_data_output_stream_put_string (dos, v, NULL, NULL); + g_data_output_stream_put_byte (dos, '\0', NULL, NULL); + } + else if (g_variant_type_is_array (type)) + { + GVariant *item; + GVariantIter iter; + goffset array_len_offset; + goffset array_payload_begin_offset; + goffset cur_offset; + gsize array_len; + guint n; + + padding_added = ensure_output_padding (mos, dos, 4); + + /* array length - will be filled in later */ + array_len_offset = g_memory_output_stream_get_data_size (mos); + g_data_output_stream_put_uint32 (dos, 0xF00DFACE, NULL, NULL); + + /* From the D-Bus spec: + * + * "A UINT32 giving the length of the array data in bytes, + * followed by alignment padding to the alignment boundary of + * the array element type, followed by each array element. The + * array length is from the end of the alignment padding to + * the end of the last element, i.e. it does not include the + * padding after the length, or any padding after the last + * element." + * + * Thus, we need to count how much padding the first element + * contributes and subtract that from the array length. + */ + array_payload_begin_offset = g_memory_output_stream_get_data_size (mos); + + g_variant_iter_init (&iter, value); + n = 0; + while ((item = g_variant_iter_next_value (&iter))) + { + gsize padding_added_for_item; + if (!append_value_to_blob (item, mos, dos, &padding_added_for_item, error)) + goto fail; + if (n == 0) + { + array_payload_begin_offset += padding_added_for_item; + } + n++; + } + + cur_offset = g_memory_output_stream_get_data_size (mos); + + array_len = cur_offset - array_payload_begin_offset; + + if (!g_seekable_seek (G_SEEKABLE (mos), array_len_offset, G_SEEK_SET, NULL, error)) + goto fail; + + g_data_output_stream_put_uint32 (dos, array_len, NULL, NULL); + + if (!g_seekable_seek (G_SEEKABLE (mos), cur_offset, G_SEEK_SET, NULL, error)) + goto fail; + } + else if (g_variant_type_is_dict_entry (type) || g_variant_type_is_tuple (type)) + { + GVariant *item; + GVariantIter iter; + + padding_added = ensure_output_padding (mos, dos, 8); + + g_variant_iter_init (&iter, value); + + while ((item = g_variant_iter_next_value (&iter))) + { + if (!append_value_to_blob (item, mos, dos, NULL, error)) + goto fail; + } + } + else if (g_variant_type_is_variant (type)) + { + GVariant *child; + const gchar *signature; + child = g_variant_get_child_value (value, 0); + signature = g_variant_get_type_string (child); + /* TODO: validate signature (including max len being 255) */ + g_data_output_stream_put_byte (dos, strlen (signature), NULL, NULL); + g_data_output_stream_put_string (dos, signature, NULL, NULL); + g_data_output_stream_put_byte (dos, '\0', NULL, NULL); + if (!append_value_to_blob (child, mos, dos, NULL, error)) + { + g_variant_unref (child); + goto fail; + } + g_variant_unref (child); + } + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Error serializing GVariant with type-string `%s' to the D-Bus wire format"), + g_variant_get_type_string (value)); + goto fail; + } + + if (out_padding_added != NULL) + *out_padding_added = padding_added; + + return TRUE; + + fail: + return FALSE; +} + +static gboolean +append_body_to_blob (GVariant *value, + GMemoryOutputStream *mos, + GDataOutputStream *dos, + GError **error) +{ + gboolean ret; + GVariant *item; + GVariantIter iter; + + ret = FALSE; + + if (!g_variant_is_of_type (value, G_VARIANT_TYPE_TUPLE)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Expected a tuple for the body of the GDBusMessage."); + goto fail; + } + + g_variant_iter_init (&iter, value); + while ((item = g_variant_iter_next_value (&iter))) + { + if (!append_value_to_blob (item, mos, dos, NULL, error)) + goto fail; + } + return TRUE; + + fail: + return FALSE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_to_blob: + * @message: A #GDBusMessage. + * @out_size: Return location for size of generated blob. + * @capabilities: A #GDBusCapabilityFlags describing what protocol features are supported. + * @error: Return location for error. + * + * Serializes @message to a blob. + * + * Returns: A pointer to a valid binary D-Bus message of @out_size bytes + * generated by @message or %NULL if @error is set. Free with g_free(). + * + * Since: 2.26 + */ +guchar * +g_dbus_message_to_blob (GDBusMessage *message, + gsize *out_size, + GDBusCapabilityFlags capabilities, + GError **error) +{ + GMemoryOutputStream *mos; + GDataOutputStream *dos; + guchar *ret; + gsize size; + GDataStreamByteOrder byte_order; + goffset body_len_offset; + goffset body_start_offset; + gsize body_size; + GVariant *header_fields; + GVariantBuilder *builder; + GHashTableIter hash_iter; + gpointer key; + GVariant *header_value; + GVariant *signature; + const gchar *signature_str; + gint num_fds_in_message; + gint num_fds_according_to_header; + + /* TODO: check against @capabilities */ + + ret = NULL; + + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + g_return_val_if_fail (out_size != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + mos = G_MEMORY_OUTPUT_STREAM (g_memory_output_stream_new (NULL, 0, g_realloc, g_free)); + dos = g_data_output_stream_new (G_OUTPUT_STREAM (mos)); + + /* TODO: detect endianess... */ + byte_order = G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN; + g_data_output_stream_set_byte_order (dos, byte_order); + + /* Core header */ + g_data_output_stream_put_byte (dos, byte_order == G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN ? 'l' : 'B', NULL, NULL); + g_data_output_stream_put_byte (dos, message->priv->type, NULL, NULL); + g_data_output_stream_put_byte (dos, message->priv->flags, NULL, NULL); + g_data_output_stream_put_byte (dos, 1, NULL, NULL); /* major protocol version */ + body_len_offset = g_memory_output_stream_get_data_size (mos); + /* body length - will be filled in later */ + g_data_output_stream_put_uint32 (dos, 0xF00DFACE, NULL, NULL); + g_data_output_stream_put_uint32 (dos, message->priv->serial, NULL, NULL); + + num_fds_in_message = 0; +#ifdef G_OS_UNIX + if (message->priv->fd_list != NULL) + num_fds_in_message = g_unix_fd_list_get_length (message->priv->fd_list); +#endif + num_fds_according_to_header = g_dbus_message_get_num_unix_fds (message); + /* TODO: check we have all the right header fields and that they are the correct value etc etc */ + if (num_fds_in_message != num_fds_according_to_header) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Message has %d fds but the header field indicates %d fds"), + num_fds_in_message, + num_fds_according_to_header); + goto out; + } + + builder = g_variant_builder_new (G_VARIANT_TYPE ("a{yv}"));//G_VARIANT_TYPE_ARRAY); + g_hash_table_iter_init (&hash_iter, message->priv->headers); + while (g_hash_table_iter_next (&hash_iter, &key, (gpointer) &header_value)) + { + g_variant_builder_add (builder, + "{yv}", + (guchar) GPOINTER_TO_UINT (key), + header_value); + } + header_fields = g_variant_new ("a{yv}", builder); + + if (!append_value_to_blob (header_fields, mos, dos, NULL, error)) + { + g_variant_unref (header_fields); + goto out; + } + g_variant_unref (header_fields); + + /* header size must be a multiple of 8 */ + ensure_output_padding (mos, dos, 8); + + body_start_offset = g_memory_output_stream_get_data_size (mos); + + signature = g_dbus_message_get_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE); + signature_str = NULL; + if (signature != NULL) + signature_str = g_variant_get_string (signature, NULL); + if (message->priv->body != NULL) + { + gchar *tupled_signature_str; + tupled_signature_str = g_strdup_printf ("(%s)", signature_str); + if (signature == NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Message body has signature `%s' but there is no signature header"), + signature_str); + g_free (tupled_signature_str); + goto out; + } + else if (g_strcmp0 (tupled_signature_str, g_variant_get_type_string (message->priv->body)) != 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Message body has type signature `%s' but signature in the header field is `%s'"), + tupled_signature_str, g_variant_get_type_string (message->priv->body)); + g_free (tupled_signature_str); + goto out; + } + g_free (tupled_signature_str); + if (!append_body_to_blob (message->priv->body, mos, dos, error)) + goto out; + } + else + { + if (signature != NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Message body is empty but signature in the header field is `(%s)'"), + signature_str); + goto out; + } + } + + /* OK, we're done writing the message - set the body length */ + size = g_memory_output_stream_get_data_size (mos); + body_size = size - body_start_offset; + + if (!g_seekable_seek (G_SEEKABLE (mos), body_len_offset, G_SEEK_SET, NULL, error)) + goto out; + + g_data_output_stream_put_uint32 (dos, body_size, NULL, NULL); + + *out_size = size; + ret = g_memdup (g_memory_output_stream_get_data (mos), size); + + out: + g_object_unref (dos); + g_object_unref (mos); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static guint32 +get_uint32_header (GDBusMessage *message, + GDBusMessageHeaderField header_field) +{ + GVariant *value; + guint32 ret; + + ret = 0; + value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field)); + if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32)) + ret = g_variant_get_uint32 (value); + + return ret; +} + +static const gchar * +get_string_header (GDBusMessage *message, + GDBusMessageHeaderField header_field) +{ + GVariant *value; + const gchar *ret; + + ret = NULL; + value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field)); + if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + ret = g_variant_get_string (value, NULL); + + return ret; +} + +static const gchar * +get_object_path_header (GDBusMessage *message, + GDBusMessageHeaderField header_field) +{ + GVariant *value; + const gchar *ret; + + ret = NULL; + value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field)); + if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_OBJECT_PATH)) + ret = g_variant_get_string (value, NULL); + + return ret; +} + +static const gchar * +get_signature_header (GDBusMessage *message, + GDBusMessageHeaderField header_field) +{ + GVariant *value; + const gchar *ret; + + ret = NULL; + value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field)); + if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_SIGNATURE)) + ret = g_variant_get_string (value, NULL); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +set_uint32_header (GDBusMessage *message, + GDBusMessageHeaderField header_field, + guint32 value) +{ + g_dbus_message_set_header (message, + header_field, + g_variant_new_uint32 (value)); +} + +static void +set_string_header (GDBusMessage *message, + GDBusMessageHeaderField header_field, + const gchar *value) +{ + g_dbus_message_set_header (message, + header_field, + value == NULL ? NULL : g_variant_new_string (value)); +} + +static void +set_object_path_header (GDBusMessage *message, + GDBusMessageHeaderField header_field, + const gchar *value) +{ + g_dbus_message_set_header (message, + header_field, + value == NULL ? NULL : g_variant_new_object_path (value)); +} + +static void +set_signature_header (GDBusMessage *message, + GDBusMessageHeaderField header_field, + const gchar *value) +{ + g_dbus_message_set_header (message, + header_field, + value == NULL ? NULL : g_variant_new_signature (value)); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_reply_serial: + * @message: A #GDBusMessage. + * + * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL header field. + * + * Returns: The value. + * + * Since: 2.26 + */ +guint32 +g_dbus_message_get_reply_serial (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0); + return get_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL); +} + +/** + * g_dbus_message_set_reply_serial: + * @message: A #GDBusMessage. + * @value: The value to set. + * + * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL header field. + * + * Since: 2.26 + */ +void +g_dbus_message_set_reply_serial (GDBusMessage *message, + guint32 value) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + set_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, value); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_interface: + * @message: A #GDBusMessage. + * + * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE header field. + * + * Returns: The value. + * + * Since: 2.26 + */ +const gchar * +g_dbus_message_get_interface (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE); +} + +/** + * g_dbus_message_set_interface: + * @message: A #GDBusMessage. + * @value: The value to set. + * + * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE header field. + * + * Since: 2.26 + */ +void +g_dbus_message_set_interface (GDBusMessage *message, + const gchar *value) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (value == NULL || g_dbus_is_interface_name (value)); + set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE, value); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_member: + * @message: A #GDBusMessage. + * + * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_MEMBER header field. + * + * Returns: The value. + * + * Since: 2.26 + */ +const gchar * +g_dbus_message_get_member (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER); +} + +/** + * g_dbus_message_set_member: + * @message: A #GDBusMessage. + * @value: The value to set. + * + * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_MEMBER header field. + * + * Since: 2.26 + */ +void +g_dbus_message_set_member (GDBusMessage *message, + const gchar *value) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (value == NULL || g_dbus_is_member_name (value)); + set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, value); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_path: + * @message: A #GDBusMessage. + * + * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_PATH header field. + * + * Returns: The value. + * + * Since: 2.26 + */ +const gchar * +g_dbus_message_get_path (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + return get_object_path_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH); +} + +/** + * g_dbus_message_set_path: + * @message: A #GDBusMessage. + * @value: The value to set. + * + * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_PATH header field. + * + * Since: 2.26 + */ +void +g_dbus_message_set_path (GDBusMessage *message, + const gchar *value) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (value == NULL || g_variant_is_object_path (value)); + set_object_path_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, value); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_sender: + * @message: A #GDBusMessage. + * + * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_SENDER header field. + * + * Returns: The value. + * + * Since: 2.26 + */ +const gchar * +g_dbus_message_get_sender (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SENDER); +} + +/** + * g_dbus_message_set_sender: + * @message: A #GDBusMessage. + * @value: The value to set. + * + * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_SENDER header field. + * + * Since: 2.26 + */ +void +g_dbus_message_set_sender (GDBusMessage *message, + const gchar *value) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (value == NULL || g_dbus_is_name (value)); + set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SENDER, value); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_destination: + * @message: A #GDBusMessage. + * + * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION header field. + * + * Returns: The value. + * + * Since: 2.26 + */ +const gchar * +g_dbus_message_get_destination (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION); +} + +/** + * g_dbus_message_set_destination: + * @message: A #GDBusMessage. + * @value: The value to set. + * + * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION header field. + * + * Since: 2.26 + */ +void +g_dbus_message_set_destination (GDBusMessage *message, + const gchar *value) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (value == NULL || g_dbus_is_name (value)); + set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION, value); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_error_name: + * @message: A #GDBusMessage. + * + * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field. + * + * Returns: The value. + * + * Since: 2.26 + */ +const gchar * +g_dbus_message_get_error_name (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME); +} + +/** + * g_dbus_message_set_error_name: + * @message: A #GDBusMessage. + * @value: The value to set. + * + * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field. + * + * Since: 2.26 + */ +void +g_dbus_message_set_error_name (GDBusMessage *message, + const gchar *value) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (value == NULL || g_dbus_is_interface_name (value)); + set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME, value); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_signature: + * @message: A #GDBusMessage. + * + * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field. + * + * Returns: The value. + * + * Since: 2.26 + */ +const gchar * +g_dbus_message_get_signature (GDBusMessage *message) +{ + const gchar *ret; + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + ret = get_signature_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE); + if (ret == NULL) + ret = ""; + return ret; +} + +/** + * g_dbus_message_set_signature: + * @message: A #GDBusMessage. + * @value: The value to set. + * + * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field. + * + * Since: 2.26 + */ +void +g_dbus_message_set_signature (GDBusMessage *message, + const gchar *value) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (value == NULL || g_variant_is_signature (value)); + set_signature_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE, value); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_arg0: + * @message: A #GDBusMessage. + * + * Convenience to get the first item in the body of @message. + * + * Returns: The string item or %NULL if the first item in the body of + * @message is not a string. + * + * Since: 2.26 + */ +const gchar * +g_dbus_message_get_arg0 (GDBusMessage *message) +{ + const gchar *ret; + + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + + ret = NULL; + + if (message->priv->body != NULL && g_variant_is_of_type (message->priv->body, G_VARIANT_TYPE_TUPLE)) + { + GVariant *item; + item = g_variant_get_child_value (message->priv->body, 0); + if (g_variant_is_of_type (item, G_VARIANT_TYPE_STRING)) + ret = g_variant_get_string (item, NULL); + } + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_get_num_unix_fds: + * @message: A #GDBusMessage. + * + * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header field. + * + * Returns: The value. + * + * Since: 2.26 + */ +guint32 +g_dbus_message_get_num_unix_fds (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0); + return get_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS); +} + +/** + * g_dbus_message_set_num_unix_fds: + * @message: A #GDBusMessage. + * @value: The value to set. + * + * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header field. + * + * Since: 2.26 + */ +void +g_dbus_message_set_num_unix_fds (GDBusMessage *message, + guint32 value) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + set_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS, value); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_message_to_gerror: + * @message: A #GDBusMessage. + * @error: The #GError to set. + * + * If @message is not of type %G_DBUS_MESSAGE_TYPE_ERROR does + * nothing and returns %FALSE. + * + * Otherwise this method encodes the error in @message as a #GError + * using g_dbus_error_set_dbus_error() using the information in the + * %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field of @message as + * well as the first string item in @message's body. + * + * Returns: %TRUE if @error was set, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_message_to_gerror (GDBusMessage *message, + GError **error) +{ + gboolean ret; + const gchar *error_name; + + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE); + + ret = FALSE; + if (message->priv->type != G_DBUS_MESSAGE_TYPE_ERROR) + goto out; + + error_name = g_dbus_message_get_error_name (message); + if (error_name != NULL) + { + GVariant *body; + + body = g_dbus_message_get_body (message); + + if (body != NULL && g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)"))) + { + const gchar *error_message; + g_variant_get (body, "(&s)", &error_message); + g_dbus_error_set_dbus_error (error, + error_name, + error_message, + NULL); + } + else + { + /* these two situations are valid, yet pretty rare */ + if (body != NULL) + { + g_dbus_error_set_dbus_error (error, + error_name, + "", + _("Error return with body of type `%s'"), + g_variant_get_type_string (body)); + } + else + { + g_dbus_error_set_dbus_error (error, + error_name, + "", + _("Error return with empty body")); + } + } + } + else + { + /* TOOD: this shouldn't happen - should check this at message serialization + * time and disconnect the peer. + */ + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Error return without error-name header!"); + } + + ret = TRUE; + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +enum_to_string (GType enum_type, gint value) +{ + gchar *ret; + GEnumClass *klass; + GEnumValue *enum_value; + + klass = g_type_class_ref (enum_type); + enum_value = g_enum_get_value (klass, value); + if (enum_value != NULL) + ret = g_strdup (enum_value->value_nick); + else + ret = g_strdup_printf ("unknown (value %d)", value); + g_type_class_unref (klass); + return ret; +} + +static gchar * +flags_to_string (GType flags_type, guint value) +{ + GString *s; + GFlagsClass *klass; + guint n; + + klass = g_type_class_ref (flags_type); + s = g_string_new (NULL); + for (n = 0; n < 32; n++) + { + if ((value & (1<len > 0) + g_string_append_c (s, ','); + if (flags_value != NULL) + g_string_append (s, flags_value->value_nick); + else + g_string_append_printf (s, "unknown (bit %d)", n); + } + } + if (s->len == 0) + g_string_append (s, "none"); + g_type_class_unref (klass); + return g_string_free (s, FALSE);; +} + +static gint +_sort_keys_func (gconstpointer a, + gconstpointer b) +{ + gint ia; + gint ib; + + ia = GPOINTER_TO_INT (a); + ib = GPOINTER_TO_INT (b); + + return ia - ib; +} + +/** + * g_dbus_message_print: + * @message: A #GDBusMessage. + * @indent: Indentation level. + * + * Produces a human-readable multi-line description of @message. + * + * The contents of the description has no ABI guarantees, the contents + * and formatting is subject to change at any time. Typical output + * looks something like this: + * + * Type: method-call + * Flags: none + * Version: 0 + * Serial: 4 + * Headers: + * path -> objectpath '/org/gtk/GDBus/TestObject' + * interface -> 'org.gtk.GDBus.TestInterface' + * member -> 'GimmeStdout' + * destination -> ':1.146' + * Body: () + * UNIX File Descriptors: + * (none) + * + * or + * + * Type: method-return + * Flags: no-reply-expected + * Version: 0 + * Serial: 477 + * Headers: + * reply-serial -> uint32 4 + * destination -> ':1.159' + * sender -> ':1.146' + * num-unix-fds -> uint32 1 + * Body: () + * UNIX File Descriptors: + * fd 12: dev=0:10,mode=020620,ino=5,uid=500,gid=5,rdev=136:2,size=0,atime=1273085037,mtime=1273085851,ctime=1272982635 + * + * + * Returns: A string that should be freed with g_free(). + * + * Since: 2.26 + */ +gchar * +g_dbus_message_print (GDBusMessage *message, + guint indent) +{ + GString *str; + gchar *s; + GList *keys; + GList *l; + + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + + str = g_string_new (NULL); + + s = enum_to_string (G_TYPE_DBUS_MESSAGE_TYPE, message->priv->type); + g_string_append_printf (str, "%*sType: %s\n", indent, "", s); + g_free (s); + s = flags_to_string (G_TYPE_DBUS_MESSAGE_FLAGS, message->priv->flags); + g_string_append_printf (str, "%*sFlags: %s\n", indent, "", s); + g_free (s); + g_string_append_printf (str, "%*sVersion: %d\n", indent, "", message->priv->major_protocol_version); + g_string_append_printf (str, "%*sSerial: %d\n", indent, "", message->priv->serial); + + g_string_append_printf (str, "%*sHeaders:\n", indent, ""); + keys = g_hash_table_get_keys (message->priv->headers); + keys = g_list_sort (keys, _sort_keys_func); + if (keys != NULL) + { + for (l = keys; l != NULL; l = l->next) + { + gint key = GPOINTER_TO_INT (l->data); + GVariant *value; + gchar *value_str; + + value = g_hash_table_lookup (message->priv->headers, l->data); + g_assert (value != NULL); + + s = enum_to_string (G_TYPE_DBUS_MESSAGE_HEADER_FIELD, key); + value_str = g_variant_print (value, TRUE); + g_string_append_printf (str, "%*s %s -> %s\n", indent, "", s, value_str); + g_free (s); + g_free (value_str); + } + } + else + { + g_string_append_printf (str, "%*s (none)\n", indent, ""); + } + g_string_append_printf (str, "%*sBody: ", indent, ""); + if (message->priv->body != NULL) + { + g_variant_print_string (message->priv->body, + str, + TRUE); + } + else + { + g_string_append (str, "()"); + } + g_string_append (str, "\n"); +#ifdef G_OS_UNIX + g_string_append_printf (str, "%*sUNIX File Descriptors:\n", indent, ""); + if (message->priv->fd_list != NULL) + { + gint num_fds; + const gint *fds; + gint n; + + fds = g_unix_fd_list_peek_fds (message->priv->fd_list, &num_fds); + if (num_fds > 0) + { + for (n = 0; n < num_fds; n++) + { + GString *fs; + struct stat statbuf; + fs = g_string_new (NULL); + if (fstat (fds[n], &statbuf) == 0) + { + g_string_append_printf (fs, "%s" "dev=%d:%d", fs->len > 0 ? "," : "", + major (statbuf.st_dev), minor (statbuf.st_dev)); + g_string_append_printf (fs, "%s" "mode=0%o", fs->len > 0 ? "," : "", + statbuf.st_mode); + g_string_append_printf (fs, "%s" "ino=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "", + (guint64) statbuf.st_ino); + g_string_append_printf (fs, "%s" "uid=%d", fs->len > 0 ? "," : "", + statbuf.st_uid); + g_string_append_printf (fs, "%s" "gid=%d", fs->len > 0 ? "," : "", + statbuf.st_gid); + g_string_append_printf (fs, "%s" "rdev=%d:%d", fs->len > 0 ? "," : "", + major (statbuf.st_rdev), minor (statbuf.st_rdev)); + g_string_append_printf (fs, "%s" "size=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "", + (guint64) statbuf.st_size); + g_string_append_printf (fs, "%s" "atime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "", + (guint64) statbuf.st_atime); + g_string_append_printf (fs, "%s" "mtime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "", + (guint64) statbuf.st_mtime); + g_string_append_printf (fs, "%s" "ctime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "", + (guint64) statbuf.st_ctime); + } + else + { + g_string_append_printf (fs, "(fstat failed: %s)", strerror (errno)); + } + g_string_append_printf (str, "%*s fd %d: %s\n", indent, "", fds[n], fs->str); + g_string_free (fs, TRUE); + } + } + else + { + g_string_append_printf (str, "%*s (empty)\n", indent, ""); + } + } + else + { + g_string_append_printf (str, "%*s (none)\n", indent, ""); + } +#endif + + return g_string_free (str, FALSE); +} + + +#define __G_DBUS_MESSAGE_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusmessage.h b/gio/gdbusmessage.h new file mode 100644 index 000000000..b77440917 --- /dev/null +++ b/gio/gdbusmessage.h @@ -0,0 +1,172 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_MESSAGE_H__ +#define __G_DBUS_MESSAGE_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_DBUS_MESSAGE (g_dbus_message_get_type ()) +#define G_DBUS_MESSAGE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_MESSAGE, GDBusMessage)) +#define G_DBUS_MESSAGE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_MESSAGE, GDBusMessageClass)) +#define G_DBUS_MESSAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_MESSAGE, GDBusMessageClass)) +#define G_IS_DBUS_MESSAGE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_MESSAGE)) +#define G_IS_DBUS_MESSAGE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_MESSAGE)) + +typedef struct _GDBusMessageClass GDBusMessageClass; +typedef struct _GDBusMessagePrivate GDBusMessagePrivate; + +/** + * GDBusMessageClass: + * + * Class structure for #GDBusMessage. + * + * Since: 2.26 + */ +struct _GDBusMessageClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +/** + * GDBusMessage: + * + * The #GDBusMessage structure contains only private data and should + * only be accessed using the provided API. + * + * Since: 2.26 + */ +struct _GDBusMessage +{ + /*< private >*/ + GObject parent_instance; + GDBusMessagePrivate *priv; +}; + +GType g_dbus_message_get_type (void) G_GNUC_CONST; +GDBusMessage *g_dbus_message_new (void); +GDBusMessage *g_dbus_message_new_signal (const gchar *path, + const gchar *interface, + const gchar *signal); +GDBusMessage *g_dbus_message_new_method_call (const gchar *name, + const gchar *path, + const gchar *interface, + const gchar *method); +GDBusMessage *g_dbus_message_new_method_reply (GDBusMessage *method_call_message); +GDBusMessage *g_dbus_message_new_method_error (GDBusMessage *method_call_message, + const gchar *error_name, + const gchar *error_message_format, + ...); +GDBusMessage *g_dbus_message_new_method_error_valist (GDBusMessage *method_call_message, + const gchar *error_name, + const gchar *error_message_format, + va_list var_args); +GDBusMessage *g_dbus_message_new_method_error_literal (GDBusMessage *method_call_message, + const gchar *error_name, + const gchar *error_message); +gchar *g_dbus_message_print (GDBusMessage *message, + guint indent); + +GDBusMessageType g_dbus_message_get_message_type (GDBusMessage *message); +void g_dbus_message_set_message_type (GDBusMessage *message, + GDBusMessageType type); +GDBusMessageFlags g_dbus_message_get_flags (GDBusMessage *message); +void g_dbus_message_set_flags (GDBusMessage *message, + GDBusMessageFlags flags); +guint32 g_dbus_message_get_serial (GDBusMessage *message); +void g_dbus_message_set_serial (GDBusMessage *message, + guint32 serial); +GVariant *g_dbus_message_get_header (GDBusMessage *message, + GDBusMessageHeaderField header_field); +void g_dbus_message_set_header (GDBusMessage *message, + GDBusMessageHeaderField header_field, + GVariant *value); +guchar *g_dbus_message_get_header_fields (GDBusMessage *message); +GVariant *g_dbus_message_get_body (GDBusMessage *message); +void g_dbus_message_set_body (GDBusMessage *message, + GVariant *body); +GUnixFDList *g_dbus_message_get_unix_fd_list (GDBusMessage *message); +void g_dbus_message_set_unix_fd_list (GDBusMessage *message, + GUnixFDList *fd_list); + +guint32 g_dbus_message_get_reply_serial (GDBusMessage *message); +void g_dbus_message_set_reply_serial (GDBusMessage *message, + guint32 value); + +const gchar *g_dbus_message_get_interface (GDBusMessage *message); +void g_dbus_message_set_interface (GDBusMessage *message, + const gchar *value); + +const gchar *g_dbus_message_get_member (GDBusMessage *message); +void g_dbus_message_set_member (GDBusMessage *message, + const gchar *value); + +const gchar *g_dbus_message_get_path (GDBusMessage *message); +void g_dbus_message_set_path (GDBusMessage *message, + const gchar *value); + +const gchar *g_dbus_message_get_sender (GDBusMessage *message); +void g_dbus_message_set_sender (GDBusMessage *message, + const gchar *value); + +const gchar *g_dbus_message_get_destination (GDBusMessage *message); +void g_dbus_message_set_destination (GDBusMessage *message, + const gchar *value); + +const gchar *g_dbus_message_get_error_name (GDBusMessage *message); +void g_dbus_message_set_error_name (GDBusMessage *message, + const gchar *value); + +const gchar *g_dbus_message_get_signature (GDBusMessage *message); +void g_dbus_message_set_signature (GDBusMessage *message, + const gchar *value); + +guint32 g_dbus_message_get_num_unix_fds (GDBusMessage *message); +void g_dbus_message_set_num_unix_fds (GDBusMessage *message, + guint32 value); + +const gchar *g_dbus_message_get_arg0 (GDBusMessage *message); + + +GDBusMessage *g_dbus_message_new_from_blob (guchar *blob, + gsize blob_len, + GDBusCapabilityFlags capabilities, + GError **error); + +gssize g_dbus_message_bytes_needed (guchar *blob, + gsize blob_len, + GError **error); + +guchar *g_dbus_message_to_blob (GDBusMessage *message, + gsize *out_size, + GDBusCapabilityFlags capabilities, + GError **error); + +gboolean g_dbus_message_to_gerror (GDBusMessage *message, + GError **error); + +G_END_DECLS + +#endif /* __G_DBUS_MESSAGE_H__ */ diff --git a/gio/gdbusmethodinvocation.c b/gio/gdbusmethodinvocation.c new file mode 100644 index 000000000..404652e3d --- /dev/null +++ b/gio/gdbusmethodinvocation.c @@ -0,0 +1,559 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include + +#include "gdbusutils.h" +#include "gdbusconnection.h" +#include "gdbusmessage.h" +#include "gdbusmethodinvocation.h" +#include "gdbusintrospection.h" +#include "gdbuserror.h" +#include "gdbusprivate.h" + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusmethodinvocation + * @short_description: Object for handling remote calls + * @include: gio/gio.h + * + * Instances of the #GDBusMethodInvocation class are used when + * handling D-Bus method calls. It provides a way to asynchronously + * return results and errors. + * + * The normal way to obtain a #GDBusMethodInvocation object is to receive + * it as an argument to the handle_method_call() function in a + * #GDBusInterfaceVTable that was passed to g_dbus_connection_register_object(). + */ + +struct _GDBusMethodInvocationPrivate +{ + /* construct-only properties */ + gchar *sender; + gchar *object_path; + gchar *interface_name; + gchar *method_name; + const GDBusMethodInfo *method_info; + GDBusConnection *connection; + GDBusMessage *message; + GVariant *parameters; + gpointer user_data; +}; + +G_DEFINE_TYPE (GDBusMethodInvocation, g_dbus_method_invocation, G_TYPE_OBJECT); + +static void +g_dbus_method_invocation_finalize (GObject *object) +{ + GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (object); + + g_free (invocation->priv->sender); + g_free (invocation->priv->object_path); + g_free (invocation->priv->interface_name); + g_free (invocation->priv->method_name); + g_object_unref (invocation->priv->connection); + g_object_unref (invocation->priv->message); + g_variant_unref (invocation->priv->parameters); + + G_OBJECT_CLASS (g_dbus_method_invocation_parent_class)->finalize (object); +} + +static void +g_dbus_method_invocation_class_init (GDBusMethodInvocationClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_dbus_method_invocation_finalize; + + g_type_class_add_private (klass, sizeof (GDBusMethodInvocationPrivate)); +} + +static void +g_dbus_method_invocation_init (GDBusMethodInvocation *invocation) +{ + invocation->priv = G_TYPE_INSTANCE_GET_PRIVATE (invocation, + G_TYPE_DBUS_METHOD_INVOCATION, + GDBusMethodInvocationPrivate); +} + +/** + * g_dbus_method_invocation_get_sender: + * @invocation: A #GDBusMethodInvocation. + * + * Gets the bus name that invoked the method. + * + * Returns: A string. Do not free, it is owned by @invocation. + * + * Since: 2.26 + */ +const gchar * +g_dbus_method_invocation_get_sender (GDBusMethodInvocation *invocation) +{ + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + return invocation->priv->sender; +} + +/** + * g_dbus_method_invocation_get_object_path: + * @invocation: A #GDBusMethodInvocation. + * + * Gets the object path the method was invoked on. + * + * Returns: A string. Do not free, it is owned by @invocation. + * + * Since: 2.26 + */ +const gchar * +g_dbus_method_invocation_get_object_path (GDBusMethodInvocation *invocation) +{ + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + return invocation->priv->object_path; +} + +/** + * g_dbus_method_invocation_get_interface_name: + * @invocation: A #GDBusMethodInvocation. + * + * Gets the name of the D-Bus interface the method was invoked on. + * + * Returns: A string. Do not free, it is owned by @invocation. + * + * Since: 2.26 + */ +const gchar * +g_dbus_method_invocation_get_interface_name (GDBusMethodInvocation *invocation) +{ + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + return invocation->priv->interface_name; +} + +/** + * g_dbus_method_invocation_get_method_info: + * @invocation: A #GDBusMethodInvocation. + * + * Gets information about the method call, if any. + * + * Returns: A #GDBusMethodInfo or %NULL. Do not free, it is owned by @invocation. + * + * Since: 2.26 + */ +const GDBusMethodInfo * +g_dbus_method_invocation_get_method_info (GDBusMethodInvocation *invocation) +{ + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + return invocation->priv->method_info; +} + +/** + * g_dbus_method_invocation_get_method_name: + * @invocation: A #GDBusMethodInvocation. + * + * Gets the name of the method that was invoked. + * + * Returns: A string. Do not free, it is owned by @invocation. + * + * Since: 2.26 + */ +const gchar * +g_dbus_method_invocation_get_method_name (GDBusMethodInvocation *invocation) +{ + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + return invocation->priv->method_name; +} + +/** + * g_dbus_method_invocation_get_connection: + * @invocation: A #GDBusMethodInvocation. + * + * Gets the #GDBusConnection the method was invoked on. + * + * Returns: A #GDBusConnection. Do not free, it is owned by @invocation. + * + * Since: 2.26 + */ +GDBusConnection * +g_dbus_method_invocation_get_connection (GDBusMethodInvocation *invocation) +{ + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + return invocation->priv->connection; +} + +/** + * g_dbus_method_invocation_get_message: + * @invocation: A #GDBusMethodInvocation. + * + * Gets the #GDBusMessage for the method invocation. This is useful if + * you need to use low-level protocol features, such as UNIX file + * descriptor passing, that cannot be properly expressed in the + * #GVariant API. + * + * See and for an example of how to use this + * low-level API to send and receive UNIX file descriptors. + * + * Returns: A #GDBusMessage. Do not free, it is owned by @invocation. + * + * Since: 2.26 + */ +GDBusMessage * +g_dbus_method_invocation_get_message (GDBusMethodInvocation *invocation) +{ + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + return invocation->priv->message; +} + +/** + * g_dbus_method_invocation_get_parameters: + * @invocation: A #GDBusMethodInvocation. + * + * Gets the parameters of the method invocation. + * + * Returns: A #GVariant. Do not free, it is owned by @invocation. + * + * Since: 2.26 + */ +GVariant * +g_dbus_method_invocation_get_parameters (GDBusMethodInvocation *invocation) +{ + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + return invocation->priv->parameters; +} + +/** + * g_dbus_method_invocation_get_user_data: + * @invocation: A #GDBusMethodInvocation. + * + * Gets the @user_data #gpointer passed to g_dbus_connection_register_object(). + * + * Returns: A #gpointer. + * + * Since: 2.26 + */ +gpointer +g_dbus_method_invocation_get_user_data (GDBusMethodInvocation *invocation) +{ + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + return invocation->priv->user_data; +} + +/** + * g_dbus_method_invocation_new: + * @sender: The bus name that invoked the method or %NULL if @connection is not a bus connection. + * @object_path: The object path the method was invoked on. + * @interface_name: The name of the D-Bus interface the method was invoked on. + * @method_name: The name of the method that was invoked. + * @method_info: Information about the method call or %NULL. + * @connection: The #GDBusConnection the method was invoked on. + * @message: The D-Bus message as a #GDBusMessage. + * @parameters: The parameters as a #GVariant tuple. + * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object(). + * + * Creates a new #GDBusMethodInvocation object. + * + * Returns: A #GDBusMethodInvocation. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusMethodInvocation * +g_dbus_method_invocation_new (const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + const GDBusMethodInfo *method_info, + GDBusConnection *connection, + GDBusMessage *message, + GVariant *parameters, + gpointer user_data) +{ + GDBusMethodInvocation *invocation; + GDBusMethodInvocationPrivate *priv; + + g_return_val_if_fail (sender == NULL || g_dbus_is_name (sender), NULL); + g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); + g_return_val_if_fail (interface_name == NULL || g_dbus_is_interface_name (interface_name), NULL); + g_return_val_if_fail (g_dbus_is_member_name (method_name), NULL); + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + g_return_val_if_fail (g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), NULL); + + invocation = G_DBUS_METHOD_INVOCATION (g_object_new (G_TYPE_DBUS_METHOD_INVOCATION, NULL)); + + priv = invocation->priv; + priv->sender = g_strdup (sender); + priv->object_path = g_strdup (object_path); + priv->interface_name = g_strdup (interface_name); + priv->method_name = g_strdup (method_name); + priv->method_info = g_dbus_method_info_ref ((GDBusMethodInfo *)method_info); + priv->connection = g_object_ref (connection); + priv->message = g_object_ref (message); + priv->parameters = g_variant_ref (parameters); + priv->user_data = user_data; + + return invocation; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_method_invocation_return_value: + * @invocation: A #GDBusMethodInvocation. + * @parameters: A #GVariant tuple with out parameters for the method or %NULL if not passing any parameters. + * + * Finishes handling a D-Bus method call by returning @parameters. + * + * It is an error if @parameters is not of the right format. + * + * This method will free @invocation, you cannot use it afterwards. + * + * Since: 2.26 + */ +void +g_dbus_method_invocation_return_value (GDBusMethodInvocation *invocation, + GVariant *parameters) +{ + GDBusMessage *reply; + GError *error; + + g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation)); + g_return_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE)); + + if (parameters != NULL) + g_variant_ref_sink (parameters); + + /* if we have introspection data, check that the signature of @parameters is correct */ + if (invocation->priv->method_info != NULL) + { + gchar *signature; + const gchar *type_string; + + type_string = "()"; + if (parameters != NULL) + type_string = g_variant_get_type_string (parameters); + signature = _g_dbus_compute_complete_signature (invocation->priv->method_info->out_args, TRUE); + + if (g_strcmp0 (type_string, signature) != 0) + { + g_warning (_("Type of return value is incorrect, got `%s', expected `%s'"), + type_string, + signature); + g_free (signature); + goto out; + } + g_free (signature); + } + + reply = g_dbus_message_new_method_reply (invocation->priv->message); + g_dbus_message_set_body (reply, parameters); + error = NULL; + if (!g_dbus_connection_send_message (g_dbus_method_invocation_get_connection (invocation), reply, NULL, &error)) + { + g_warning (_("Error sending message: %s"), error->message); + g_error_free (error); + } + g_object_unref (reply); + + out: + g_object_unref (invocation); + if (parameters != NULL) + g_variant_unref (parameters); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_method_invocation_return_error: + * @invocation: A #GDBusMethodInvocation. + * @domain: A #GQuark for the #GError error domain. + * @code: The error code. + * @format: printf()-style format. + * @...: Parameters for @format. + * + * Finishes handling a D-Bus method call by returning an error. + * + * See g_dbus_error_encode_gerror() for details about what error name + * will be returned on the wire. In a nutshell, if the given error is + * registered using g_dbus_error_register_error() the name given + * during registration is used. Otherwise, a name of the form + * org.gtk.GDBus.UnmappedGError.Quark... is + * used. This provides transparent mapping of #GError between + * applications using GDBus. + * + * If you are writing an application intended to be portable, + * always register errors with g_dbus_error_register_error() + * or use g_dbus_method_invocation_return_dbus_error(). + * + * This method will free @invocation, you cannot use it afterwards. + * + * Since: 2.26 + */ +void +g_dbus_method_invocation_return_error (GDBusMethodInvocation *invocation, + GQuark domain, + gint code, + const gchar *format, + ...) +{ + va_list var_args; + + g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation)); + g_return_if_fail (format != NULL); + + va_start (var_args, format); + g_dbus_method_invocation_return_error_valist (invocation, + domain, + code, + format, + var_args); + va_end (var_args); +} + +/** + * g_dbus_method_invocation_return_error_valist: + * @invocation: A #GDBusMethodInvocation. + * @domain: A #GQuark for the #GError error domain. + * @code: The error code. + * @format: printf()-style format. + * @var_args: #va_list of parameters for @format. + * + * Like g_dbus_method_invocation_return_error() but intended for + * language bindings. + * + * This method will free @invocation, you cannot use it afterwards. + * + * Since: 2.26 + */ +void +g_dbus_method_invocation_return_error_valist (GDBusMethodInvocation *invocation, + GQuark domain, + gint code, + const gchar *format, + va_list var_args) +{ + gchar *literal_message; + + g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation)); + g_return_if_fail (format != NULL); + + literal_message = g_strdup_vprintf (format, var_args); + g_dbus_method_invocation_return_error_literal (invocation, + domain, + code, + literal_message); + g_free (literal_message); +} + +/** + * g_dbus_method_invocation_return_error_literal: + * @invocation: A #GDBusMethodInvocation. + * @domain: A #GQuark for the #GError error domain. + * @code: The error code. + * @message: The error message. + * + * Like g_dbus_method_invocation_return_error() but without printf()-style formatting. + * + * This method will free @invocation, you cannot use it afterwards. + * + * Since: 2.26 + */ +void +g_dbus_method_invocation_return_error_literal (GDBusMethodInvocation *invocation, + GQuark domain, + gint code, + const gchar *message) +{ + GError *error; + + g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation)); + g_return_if_fail (message != NULL); + + error = g_error_new_literal (domain, code, message); + g_dbus_method_invocation_return_gerror (invocation, error); + g_error_free (error); +} + +/** + * g_dbus_method_invocation_return_gerror: + * @invocation: A #GDBusMethodInvocation. + * @error: A #GError. + * + * Like g_dbus_method_invocation_return_error() but takes a #GError + * instead of the error domain, error code and message. + * + * This method will free @invocation, you cannot use it afterwards. + * + * Since: 2.26 + */ +void +g_dbus_method_invocation_return_gerror (GDBusMethodInvocation *invocation, + const GError *error) +{ + gchar *dbus_error_name; + + g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation)); + g_return_if_fail (error != NULL); + + dbus_error_name = g_dbus_error_encode_gerror (error); + + g_dbus_method_invocation_return_dbus_error (invocation, + dbus_error_name, + error->message); + g_free (dbus_error_name); +} + +/** + * g_dbus_method_invocation_return_dbus_error: + * @invocation: A #GDBusMethodInvocation. + * @error_name: A valid D-Bus error name. + * @error_message: A valid D-Bus error message. + * + * Finishes handling a D-Bus method call by returning an error. + * + * This method will free @invocation, you cannot use it afterwards. + * + * Since: 2.26 + */ +void +g_dbus_method_invocation_return_dbus_error (GDBusMethodInvocation *invocation, + const gchar *error_name, + const gchar *error_message) +{ + GDBusMessage *reply; + + g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation)); + g_return_if_fail (error_name != NULL && g_dbus_is_name (error_name)); + g_return_if_fail (error_message != NULL); + + reply = g_dbus_message_new_method_error_literal (invocation->priv->message, + error_name, + error_message); + g_dbus_connection_send_message (g_dbus_method_invocation_get_connection (invocation), reply, NULL, NULL); + g_object_unref (reply); + + g_object_unref (invocation); +} + +#define __G_DBUS_METHOD_INVOCATION_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusmethodinvocation.h b/gio/gdbusmethodinvocation.h new file mode 100644 index 000000000..cb1d751fd --- /dev/null +++ b/gio/gdbusmethodinvocation.h @@ -0,0 +1,123 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_METHOD_INVOCATION_H__ +#define __G_DBUS_METHOD_INVOCATION_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_DBUS_METHOD_INVOCATION (g_dbus_method_invocation_get_type ()) +#define G_DBUS_METHOD_INVOCATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_METHOD_INVOCATION, GDBusMethodInvocation)) +#define G_DBUS_METHOD_INVOCATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_METHOD_INVOCATION, GDBusMethodInvocationClass)) +#define G_DBUS_METHOD_INVOCATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_METHOD_INVOCATION, GDBusMethodInvocationClass)) +#define G_IS_DBUS_METHOD_INVOCATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_METHOD_INVOCATION)) +#define G_IS_DBUS_METHOD_INVOCATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_METHOD_INVOCATION)) + +typedef struct _GDBusMethodInvocationClass GDBusMethodInvocationClass; +typedef struct _GDBusMethodInvocationPrivate GDBusMethodInvocationPrivate; + +/** + * GDBusMethodInvocation: + * + * The #GDBusMethodInvocation structure contains only private data and + * should only be accessed using the provided API. + * + * Since: 2.26 + */ +struct _GDBusMethodInvocation +{ + /*< private >*/ + GObject parent_instance; + GDBusMethodInvocationPrivate *priv; +}; + +/** + * GDBusMethodInvocationClass: + * + * Class structure for #GDBusMethodInvocation. + * + * Since: 2.26 + */ +struct _GDBusMethodInvocationClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); +}; + +GType g_dbus_method_invocation_get_type (void) G_GNUC_CONST; +GDBusMethodInvocation *g_dbus_method_invocation_new (const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + const GDBusMethodInfo *method_info, + GDBusConnection *connection, + GDBusMessage *message, + GVariant *parameters, + gpointer user_data); +const gchar *g_dbus_method_invocation_get_sender (GDBusMethodInvocation *invocation); +const gchar *g_dbus_method_invocation_get_object_path (GDBusMethodInvocation *invocation); +const gchar *g_dbus_method_invocation_get_interface_name (GDBusMethodInvocation *invocation); +const gchar *g_dbus_method_invocation_get_method_name (GDBusMethodInvocation *invocation); +const GDBusMethodInfo *g_dbus_method_invocation_get_method_info (GDBusMethodInvocation *invocation); +GDBusConnection *g_dbus_method_invocation_get_connection (GDBusMethodInvocation *invocation); +GDBusMessage *g_dbus_method_invocation_get_message (GDBusMethodInvocation *invocation); +GVariant *g_dbus_method_invocation_get_parameters (GDBusMethodInvocation *invocation); +gpointer g_dbus_method_invocation_get_user_data (GDBusMethodInvocation *invocation); + +void g_dbus_method_invocation_return_value (GDBusMethodInvocation *invocation, + GVariant *parameters); +void g_dbus_method_invocation_return_error (GDBusMethodInvocation *invocation, + GQuark domain, + gint code, + const gchar *format, + ...); +void g_dbus_method_invocation_return_error_valist (GDBusMethodInvocation *invocation, + GQuark domain, + gint code, + const gchar *format, + va_list var_args); +void g_dbus_method_invocation_return_error_literal (GDBusMethodInvocation *invocation, + GQuark domain, + gint code, + const gchar *message); +void g_dbus_method_invocation_return_gerror (GDBusMethodInvocation *invocation, + const GError *error); +void g_dbus_method_invocation_return_dbus_error (GDBusMethodInvocation *invocation, + const gchar *error_name, + const gchar *error_message); + +G_END_DECLS + +#endif /* __G_DBUS_METHOD_INVOCATION_H__ */ diff --git a/gio/gdbusnameowning.c b/gio/gdbusnameowning.c new file mode 100644 index 000000000..e14913217 --- /dev/null +++ b/gio/gdbusnameowning.c @@ -0,0 +1,724 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include + +#include "gdbusutils.h" +#include "gdbusnameowning.h" +#include "gdbuserror.h" +#include "gdbusprivate.h" +#include "gdbusconnection.h" + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusnameowning + * @title: Owning Bus Names + * @short_description: Simple API for owning bus names + * @include: gio/gio.h + * + * Convenience API for owning bus names. + * + * Simple application owning a nameFIXME: MISSING XINCLUDE CONTENT + */ + +G_LOCK_DEFINE_STATIC (lock); + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef enum +{ + PREVIOUS_CALL_NONE = 0, + PREVIOUS_CALL_ACQUIRED, + PREVIOUS_CALL_LOST, +} PreviousCall; + +typedef struct +{ + volatile gint ref_count; + guint id; + GBusNameOwnerFlags flags; + gchar *name; + GBusAcquiredCallback bus_acquired_handler; + GBusNameAcquiredCallback name_acquired_handler; + GBusNameLostCallback name_lost_handler; + gpointer user_data; + GDestroyNotify user_data_free_func; + GMainContext *main_context; + + PreviousCall previous_call; + + GDBusConnection *connection; + gulong disconnected_signal_handler_id; + guint name_acquired_subscription_id; + guint name_lost_subscription_id; + + gboolean cancelled; + + gboolean needs_release; +} Client; + +static guint next_global_id = 1; +static GHashTable *map_id_to_client = NULL; + + +static Client * +client_ref (Client *client) +{ + g_atomic_int_inc (&client->ref_count); + return client; +} + +static void +client_unref (Client *client) +{ + if (g_atomic_int_dec_and_test (&client->ref_count)) + { + if (client->connection != NULL) + { + if (client->disconnected_signal_handler_id > 0) + g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id); + if (client->name_acquired_subscription_id > 0) + g_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id); + if (client->name_lost_subscription_id > 0) + g_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id); + g_object_unref (client->connection); + } + if (client->main_context != NULL) + g_main_context_unref (client->main_context); + g_free (client->name); + if (client->user_data_free_func != NULL) + client->user_data_free_func (client->user_data); + g_free (client); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + + +typedef enum +{ + CALL_TYPE_NAME_ACQUIRED, + CALL_TYPE_NAME_LOST +} CallType; + +typedef struct +{ + Client *client; + + /* keep this separate because client->connection may + * be set to NULL after scheduling the call + */ + GDBusConnection *connection; + + /* set to TRUE to call acquired */ + CallType call_type; +} CallHandlerData; + +static void +call_handler_data_free (CallHandlerData *data) +{ + if (data->connection != NULL) + g_object_unref (data->connection); + client_unref (data->client); + g_free (data); +} + +static void +actually_do_call (Client *client, GDBusConnection *connection, CallType call_type) +{ + switch (call_type) + { + case CALL_TYPE_NAME_ACQUIRED: + if (client->name_acquired_handler != NULL) + { + client->name_acquired_handler (connection, + client->name, + client->user_data); + } + break; + + case CALL_TYPE_NAME_LOST: + if (client->name_lost_handler != NULL) + { + client->name_lost_handler (connection, + client->name, + client->user_data); + } + break; + + default: + g_assert_not_reached (); + break; + } +} + +static gboolean +call_in_idle_cb (gpointer _data) +{ + CallHandlerData *data = _data; + actually_do_call (data->client, data->connection, data->call_type); + return FALSE; +} + +static void +schedule_call_in_idle (Client *client, CallType call_type) +{ + CallHandlerData *data; + GSource *idle_source; + + data = g_new0 (CallHandlerData, 1); + data->client = client_ref (client); + data->connection = client->connection != NULL ? g_object_ref (client->connection) : NULL; + data->call_type = call_type; + + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_HIGH); + g_source_set_callback (idle_source, + call_in_idle_cb, + data, + (GDestroyNotify) call_handler_data_free); + g_source_attach (idle_source, client->main_context); + g_source_unref (idle_source); +} + +static void +do_call (Client *client, CallType call_type) +{ + /* only schedule in idle if we're not in the right thread */ + if (g_main_context_get_thread_default () != client->main_context) + schedule_call_in_idle (client, call_type); + else + actually_do_call (client, client->connection, call_type); +} + +static void +call_acquired_handler (Client *client) +{ + if (client->previous_call != PREVIOUS_CALL_ACQUIRED) + { + client->previous_call = PREVIOUS_CALL_ACQUIRED; + if (!client->cancelled) + { + do_call (client, CALL_TYPE_NAME_ACQUIRED); + } + } +} + +static void +call_lost_handler (Client *client) +{ + if (client->previous_call != PREVIOUS_CALL_LOST) + { + client->previous_call = PREVIOUS_CALL_LOST; + if (!client->cancelled) + { + do_call (client, CALL_TYPE_NAME_LOST); + } + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +on_name_lost_or_acquired (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + Client *client = user_data; + const gchar *name; + + if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 || + g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 || + g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0) + goto out; + + if (g_strcmp0 (signal_name, "NameLost") == 0) + { + g_variant_get (parameters, "(&s)", &name); + if (g_strcmp0 (name, client->name) == 0) + { + call_lost_handler (client); + } + } + else if (g_strcmp0 (signal_name, "NameAcquired") == 0) + { + g_variant_get (parameters, "(&s)", &name); + if (g_strcmp0 (name, client->name) == 0) + { + call_acquired_handler (client); + } + } + out: + ; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +request_name_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + Client *client = user_data; + GVariant *result; + guint32 request_name_reply; + gboolean subscribe; + + request_name_reply = 0; + result = NULL; + + result = g_dbus_connection_call_finish (client->connection, + res, + NULL); + if (result != NULL) + { + g_variant_get (result, "(u)", &request_name_reply); + g_variant_unref (result); + } + + subscribe = FALSE; + + switch (request_name_reply) + { + case 1: /* DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER */ + /* We got the name - now listen for NameLost and NameAcquired */ + call_acquired_handler (client); + subscribe = TRUE; + client->needs_release = TRUE; + break; + + case 2: /* DBUS_REQUEST_NAME_REPLY_IN_QUEUE */ + /* Waiting in line - listen for NameLost and NameAcquired */ + call_lost_handler (client); + subscribe = TRUE; + client->needs_release = TRUE; + break; + + default: + /* assume we couldn't get the name - explicit fallthrough */ + case 3: /* DBUS_REQUEST_NAME_REPLY_EXISTS */ + case 4: /* DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER */ + /* Some other part of the process is already owning the name */ + call_lost_handler (client); + break; + } + + if (subscribe) + { + /* start listening to NameLost and NameAcquired messages */ + client->name_lost_subscription_id = + g_dbus_connection_signal_subscribe (client->connection, + "org.freedesktop.DBus", + "org.freedesktop.DBus", + "NameLost", + "/org/freedesktop/DBus", + client->name, + on_name_lost_or_acquired, + client, + NULL); + client->name_acquired_subscription_id = + g_dbus_connection_signal_subscribe (client->connection, + "org.freedesktop.DBus", + "org.freedesktop.DBus", + "NameAcquired", + "/org/freedesktop/DBus", + client->name, + on_name_lost_or_acquired, + client, + NULL); + } + + client_unref (client); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +on_connection_disconnected (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + Client *client = user_data; + + if (client->disconnected_signal_handler_id > 0) + g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id); + if (client->name_acquired_subscription_id > 0) + g_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id); + if (client->name_lost_subscription_id > 0) + g_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id); + g_object_unref (client->connection); + client->disconnected_signal_handler_id = 0; + client->name_acquired_subscription_id = 0; + client->name_lost_subscription_id = 0; + client->connection = NULL; + + call_lost_handler (client); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +has_connection (Client *client) +{ + /* listen for disconnection */ + client->disconnected_signal_handler_id = g_signal_connect (client->connection, + "closed", + G_CALLBACK (on_connection_disconnected), + client); + + /* attempt to acquire the name */ + g_dbus_connection_call (client->connection, + "org.freedesktop.DBus", /* bus name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "RequestName", /* method name */ + g_variant_new ("(su)", + client->name, + client->flags), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) request_name_cb, + client_ref (client)); +} + + +static void +connection_get_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + Client *client = user_data; + + client->connection = g_bus_get_finish (res, NULL); + if (client->connection == NULL) + { + call_lost_handler (client); + goto out; + } + + /* No need to schedule this in idle as we're already in the thread + * that the user called g_bus_own_name() from. This is because + * g_bus_get() guarantees that. + * + * Also, we need to ensure that the handler is invoked *before* + * we call RequestName(). Otherwise there is a race. + */ + if (client->bus_acquired_handler != NULL) + { + client->bus_acquired_handler (client->connection, + client->name, + client->user_data); + } + + has_connection (client); + + out: + client_unref (client); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_bus_own_name_on_connection: + * @connection: A #GDBusConnection that is not closed. + * @name: The well-known name to own. + * @flags: A set of flags from the #GBusNameOwnerFlags enumeration. + * @name_acquired_handler: Handler to invoke when @name is acquired or %NULL. + * @name_lost_handler: Handler to invoke when @name is lost or %NULL. + * @user_data: User data to pass to handlers. + * @user_data_free_func: Function for freeing @user_data or %NULL. + * + * Like g_bus_own_name() but takes a #GDBusConnection instead of a + * #GBusType. + * + * Returns: An identifier (never 0) that an be used with + * g_bus_unown_name() to stop owning the name. + * + * Since: 2.26 + */ +guint +g_bus_own_name_on_connection (GDBusConnection *connection, + const gchar *name, + GBusNameOwnerFlags flags, + GBusNameAcquiredCallback name_acquired_handler, + GBusNameLostCallback name_lost_handler, + gpointer user_data, + GDestroyNotify user_data_free_func) +{ + Client *client; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); + g_return_val_if_fail (!g_dbus_connection_is_closed (connection), 0); + g_return_val_if_fail (g_dbus_is_name (name) && !g_dbus_is_unique_name (name), 0); + + G_LOCK (lock); + + client = g_new0 (Client, 1); + client->ref_count = 1; + client->id = next_global_id++; /* TODO: uh oh, handle overflow */ + client->name = g_strdup (name); + client->flags = flags; + client->name_acquired_handler = name_acquired_handler; + client->name_lost_handler = name_lost_handler; + client->user_data = user_data; + client->user_data_free_func = user_data_free_func; + client->main_context = g_main_context_get_thread_default (); + if (client->main_context != NULL) + g_main_context_ref (client->main_context); + + client->connection = g_object_ref (connection); + + if (map_id_to_client == NULL) + { + map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal); + } + g_hash_table_insert (map_id_to_client, + GUINT_TO_POINTER (client->id), + client); + + G_UNLOCK (lock); + + has_connection (client); + + return client->id; +} + +/** + * g_bus_own_name: + * @bus_type: The type of bus to own a name on. + * @name: The well-known name to own. + * @flags: A set of flags from the #GBusNameOwnerFlags enumeration. + * @bus_acquired_handler: Handler to invoke when connected to the bus of type @bus_type or %NULL. + * @name_acquired_handler: Handler to invoke when @name is acquired or %NULL. + * @name_lost_handler: Handler to invoke when @name is lost or %NULL. + * @user_data: User data to pass to handlers. + * @user_data_free_func: Function for freeing @user_data or %NULL. + * + * Starts acquiring @name on the bus specified by @bus_type and calls + * @name_acquired_handler and @name_lost_handler when the name is + * acquired respectively lost. Callbacks will be invoked in the thread-default main + * loop of the thread you are calling this function from. + * + * You are guaranteed that one of the @name_acquired_handler and @name_lost_handler + * callbacks will be invoked after calling this function - there are three + * possible cases: + * + * + * @name_lost_handler with a %NULL connection (if a connection to the bus can't be made). + * + * + * @bus_acquired_handler then @name_lost_handler (if the name can't be obtained) + * + * + * @bus_acquired_handler then @name_acquired_handler (if the name was obtained). + * + * + * When you are done owning the name, just call g_bus_unown_name() + * with the owner id this function returns. + * + * If the name is acquired or lost (for example another application + * could acquire the name if you allow replacement or the application + * currently owning the name exits), the handlers are also invoked. If the + * #GDBusConnection that is used for attempting to own the name + * closes, then @name_lost_handler is invoked since it is no + * longer possible for other processes to access the process. + * + * You cannot use g_bus_own_name() several times for the same name (unless + * interleaved with calls to g_bus_unown_name()) - only the first call + * will work. + * + * Another guarantee is that invocations of @name_acquired_handler + * and @name_lost_handler are guaranteed to alternate; that + * is, if @name_acquired_handler is invoked then you are + * guaranteed that the next time one of the handlers is invoked, it + * will be @name_lost_handler. The reverse is also true. + * + * If you plan on exporting objects (using e.g. + * g_dbus_connection_register_object()), note that it is generally too late + * to export the objects in @name_acquired_handler. Instead, you can do this + * in @bus_acquired_handler since you are guaranteed that this will run + * before @name is requested from the bus. + * + * This behavior makes it very simple to write applications that wants + * to own names and export objects, see . + * Simply register objects to be exported in @bus_acquired_handler and + * unregister the objects (if any) in @name_lost_handler. + * + * Returns: An identifier (never 0) that an be used with + * g_bus_unown_name() to stop owning the name. + * + * Since: 2.26 + */ +guint +g_bus_own_name (GBusType bus_type, + const gchar *name, + GBusNameOwnerFlags flags, + GBusAcquiredCallback bus_acquired_handler, + GBusNameAcquiredCallback name_acquired_handler, + GBusNameLostCallback name_lost_handler, + gpointer user_data, + GDestroyNotify user_data_free_func) +{ + Client *client; + + g_return_val_if_fail (g_dbus_is_name (name) && !g_dbus_is_unique_name (name), 0); + + G_LOCK (lock); + + client = g_new0 (Client, 1); + client->ref_count = 1; + client->id = next_global_id++; /* TODO: uh oh, handle overflow */ + client->name = g_strdup (name); + client->flags = flags; + client->bus_acquired_handler = bus_acquired_handler; + client->name_acquired_handler = name_acquired_handler; + client->name_lost_handler = name_lost_handler; + client->user_data = user_data; + client->user_data_free_func = user_data_free_func; + client->main_context = g_main_context_get_thread_default (); + if (client->main_context != NULL) + g_main_context_ref (client->main_context); + + if (map_id_to_client == NULL) + { + map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal); + } + g_hash_table_insert (map_id_to_client, + GUINT_TO_POINTER (client->id), + client); + + g_bus_get (bus_type, + NULL, + connection_get_cb, + client_ref (client)); + + G_UNLOCK (lock); + + return client->id; +} + +/** + * g_bus_unown_name: + * @owner_id: An identifier obtained from g_bus_own_name() + * + * Stops owning a name. + * + * Since: 2.26 + */ +void +g_bus_unown_name (guint owner_id) +{ + Client *client; + + g_return_if_fail (owner_id > 0); + + client = NULL; + + G_LOCK (lock); + if (owner_id == 0 || map_id_to_client == NULL || + (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (owner_id))) == NULL) + { + g_warning ("Invalid id %d passed to g_bus_unown_name()", owner_id); + goto out; + } + + client->cancelled = TRUE; + g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (owner_id))); + + out: + G_UNLOCK (lock); + + /* do callback without holding lock */ + if (client != NULL) + { + /* Release the name if needed */ + if (client->needs_release && client->connection != NULL) + { + GVariant *result; + GError *error; + guint32 release_name_reply; + + /* TODO: it kinda sucks having to do a sync call to release the name - but if + * we don't, then a subsequent grab of the name will make the bus daemon return + * IN_QUEUE which will trigger name_lost(). + * + * I believe this is a bug in the bus daemon. + */ + error = NULL; + result = g_dbus_connection_call_sync (client->connection, + "org.freedesktop.DBus", /* bus name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "ReleaseName", /* method name */ + g_variant_new ("(s)", client->name), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (result == NULL) + { + g_warning ("Error releasing name %s: %s", client->name, error->message); + g_error_free (error); + } + else + { + g_variant_get (result, "(u)", &release_name_reply); + if (release_name_reply != 1 /* DBUS_RELEASE_NAME_REPLY_RELEASED */) + { + g_warning ("Unexpected reply %d when releasing name %s", release_name_reply, client->name); + } + g_variant_unref (result); + } + } + + if (client->disconnected_signal_handler_id > 0) + g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id); + if (client->name_acquired_subscription_id > 0) + g_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id); + if (client->name_lost_subscription_id > 0) + g_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id); + client->disconnected_signal_handler_id = 0; + client->name_acquired_subscription_id = 0; + client->name_lost_subscription_id = 0; + if (client->connection != NULL) + { + g_object_unref (client->connection); + client->connection = NULL; + } + + client_unref (client); + } +} + +#define __G_DBUS_NAME_OWNING_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusnameowning.h b/gio/gdbusnameowning.h new file mode 100644 index 000000000..a1adc3f53 --- /dev/null +++ b/gio/gdbusnameowning.h @@ -0,0 +1,94 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_NAME_OWNING_H__ +#define __G_DBUS_NAME_OWNING_H__ + +#include + +G_BEGIN_DECLS + +/** + * GBusAcquiredCallback: + * @connection: The #GDBusConnection to a message bus. + * @name: The name that is requested to be owned. + * @user_data: User data passed to g_bus_own_name(). + * + * Invoked when a connection to a message bus has been obtained. + * + * Since: 2.26 + */ +typedef void (*GBusAcquiredCallback) (GDBusConnection *connection, + const gchar *name, + gpointer user_data); + +/** + * GBusNameAcquiredCallback: + * @connection: The #GDBusConnection on which to acquired the name. + * @name: The name being owned. + * @user_data: User data passed to g_bus_own_name() or g_bus_own_name_on_connection(). + * + * Invoked when the name is acquired. + * + * Since: 2.26 + */ +typedef void (*GBusNameAcquiredCallback) (GDBusConnection *connection, + const gchar *name, + gpointer user_data); + +/** + * GBusNameLostCallback: + * @connection: The #GDBusConnection on which to acquire the name or %NULL if + * the connection was disconnected. + * @name: The name being owned. + * @user_data: User data passed to g_bus_own_name() or g_bus_own_name_on_connection(). + * + * Invoked when the name is lost or @connection has been closed. + * + * Since: 2.26 + */ +typedef void (*GBusNameLostCallback) (GDBusConnection *connection, + const gchar *name, + gpointer user_data); + +guint g_bus_own_name (GBusType bus_type, + const gchar *name, + GBusNameOwnerFlags flags, + GBusAcquiredCallback bus_acquired_handler, + GBusNameAcquiredCallback name_acquired_handler, + GBusNameLostCallback name_lost_handler, + gpointer user_data, + GDestroyNotify user_data_free_func); + +guint g_bus_own_name_on_connection (GDBusConnection *connection, + const gchar *name, + GBusNameOwnerFlags flags, + GBusNameAcquiredCallback name_acquired_handler, + GBusNameLostCallback name_lost_handler, + gpointer user_data, + GDestroyNotify user_data_free_func); + +void g_bus_unown_name (guint owner_id); + +G_END_DECLS + +#endif /* __G_DBUS_NAME_OWNING_H__ */ diff --git a/gio/gdbusnamewatching.c b/gio/gdbusnamewatching.c new file mode 100644 index 000000000..9fe944d81 --- /dev/null +++ b/gio/gdbusnamewatching.c @@ -0,0 +1,691 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include +#include + +#include "gdbusutils.h" +#include "gdbusnamewatching.h" +#include "gdbuserror.h" +#include "gdbusprivate.h" +#include "gdbusconnection.h" + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusnamewatching + * @title: Watching Bus Names + * @short_description: Simple API for watching bus names + * @include: gio/gio.h + * + * Convenience API for watching bus names. + * + * Simple application watching a nameFIXME: MISSING XINCLUDE CONTENT + */ + +G_LOCK_DEFINE_STATIC (lock); + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef enum +{ + PREVIOUS_CALL_NONE = 0, + PREVIOUS_CALL_APPEARED, + PREVIOUS_CALL_VANISHED, +} PreviousCall; + +typedef struct +{ + volatile gint ref_count; + guint id; + gchar *name; + GBusNameWatcherFlags flags; + gchar *name_owner; + GBusNameAppearedCallback name_appeared_handler; + GBusNameVanishedCallback name_vanished_handler; + gpointer user_data; + GDestroyNotify user_data_free_func; + GMainContext *main_context; + + GDBusConnection *connection; + gulong disconnected_signal_handler_id; + guint name_owner_changed_subscription_id; + + PreviousCall previous_call; + + gboolean cancelled; + gboolean initialized; +} Client; + +static guint next_global_id = 1; +static GHashTable *map_id_to_client = NULL; + +static Client * +client_ref (Client *client) +{ + g_atomic_int_inc (&client->ref_count); + return client; +} + +static void +client_unref (Client *client) +{ + if (g_atomic_int_dec_and_test (&client->ref_count)) + { + if (client->connection != NULL) + { + if (client->name_owner_changed_subscription_id > 0) + g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id); + if (client->disconnected_signal_handler_id > 0) + g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id); + g_object_unref (client->connection); + } + g_free (client->name); + g_free (client->name_owner); + if (client->main_context != NULL) + g_main_context_unref (client->main_context); + if (client->user_data_free_func != NULL) + client->user_data_free_func (client->user_data); + g_free (client); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef enum +{ + CALL_TYPE_NAME_APPEARED, + CALL_TYPE_NAME_VANISHED +} CallType; + +typedef struct +{ + Client *client; + + /* keep this separate because client->connection may + * be set to NULL after scheduling the call + */ + GDBusConnection *connection; + + /* ditto */ + gchar *name_owner; + + CallType call_type; +} CallHandlerData; + +static void +call_handler_data_free (CallHandlerData *data) +{ + if (data->connection != NULL) + g_object_unref (data->connection); + g_free (data->name_owner); + client_unref (data->client); + g_free (data); +} + +static void +actually_do_call (Client *client, GDBusConnection *connection, const gchar *name_owner, CallType call_type) +{ + switch (call_type) + { + case CALL_TYPE_NAME_APPEARED: + if (client->name_appeared_handler != NULL) + { + client->name_appeared_handler (connection, + client->name, + name_owner, + client->user_data); + } + break; + + case CALL_TYPE_NAME_VANISHED: + if (client->name_vanished_handler != NULL) + { + client->name_vanished_handler (connection, + client->name, + client->user_data); + } + break; + + default: + g_assert_not_reached (); + break; + } +} + +static gboolean +call_in_idle_cb (gpointer _data) +{ + CallHandlerData *data = _data; + actually_do_call (data->client, data->connection, data->name_owner, data->call_type); + return FALSE; +} + +static void +schedule_call_in_idle (Client *client, CallType call_type) +{ + CallHandlerData *data; + GSource *idle_source; + + data = g_new0 (CallHandlerData, 1); + data->client = client_ref (client); + data->connection = client->connection != NULL ? g_object_ref (client->connection) : NULL; + data->name_owner = g_strdup (client->name_owner); + data->call_type = call_type; + + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_HIGH); + g_source_set_callback (idle_source, + call_in_idle_cb, + data, + (GDestroyNotify) call_handler_data_free); + g_source_attach (idle_source, client->main_context); + g_source_unref (idle_source); +} + +static void +do_call (Client *client, CallType call_type) +{ + /* only schedule in idle if we're not in the right thread */ + if (g_main_context_get_thread_default () != client->main_context) + schedule_call_in_idle (client, call_type); + else + actually_do_call (client, client->connection, client->name_owner, call_type); +} + +static void +call_appeared_handler (Client *client) +{ + if (client->previous_call != PREVIOUS_CALL_APPEARED) + { + client->previous_call = PREVIOUS_CALL_APPEARED; + if (!client->cancelled && client->name_appeared_handler != NULL) + { + do_call (client, CALL_TYPE_NAME_APPEARED); + } + } +} + +static void +call_vanished_handler (Client *client, + gboolean ignore_cancelled) +{ + if (client->previous_call != PREVIOUS_CALL_VANISHED) + { + client->previous_call = PREVIOUS_CALL_VANISHED; + if (((!client->cancelled) || ignore_cancelled) && client->name_vanished_handler != NULL) + { + do_call (client, CALL_TYPE_NAME_VANISHED); + } + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +on_connection_disconnected (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + Client *client = user_data; + + if (client->name_owner_changed_subscription_id > 0) + g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id); + if (client->disconnected_signal_handler_id > 0) + g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id); + g_object_unref (client->connection); + client->disconnected_signal_handler_id = 0; + client->name_owner_changed_subscription_id = 0; + client->connection = NULL; + + call_vanished_handler (client, FALSE); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +on_name_owner_changed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + Client *client = user_data; + const gchar *name; + const gchar *old_owner; + const gchar *new_owner; + + if (!client->initialized) + goto out; + + if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 || + g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 || + g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0) + goto out; + + g_variant_get (parameters, + "(&s&s&s)", + &name, + &old_owner, + &new_owner); + + /* we only care about a specific name */ + if (g_strcmp0 (name, client->name) != 0) + goto out; + + if ((old_owner != NULL && strlen (old_owner) > 0) && client->name_owner != NULL) + { + g_free (client->name_owner); + client->name_owner = NULL; + call_vanished_handler (client, FALSE); + } + + if (new_owner != NULL && strlen (new_owner) > 0) + { + g_warn_if_fail (client->name_owner == NULL); + g_free (client->name_owner); + client->name_owner = g_strdup (new_owner); + call_appeared_handler (client); + } + + out: + ; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +get_name_owner_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + Client *client = user_data; + GVariant *result; + const char *name_owner; + + name_owner = NULL; + result = NULL; + + result = g_dbus_connection_call_finish (client->connection, + res, + NULL); + if (result != NULL) + { + g_variant_get (result, "(&s)", &name_owner); + } + + if (name_owner != NULL) + { + g_warn_if_fail (client->name_owner == NULL); + client->name_owner = g_strdup (name_owner); + call_appeared_handler (client); + } + else + { + call_vanished_handler (client, FALSE); + } + + client->initialized = TRUE; + + if (result != NULL) + g_variant_unref (result); + client_unref (client); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +invoke_get_name_owner (Client *client) +{ + g_dbus_connection_call (client->connection, + "org.freedesktop.DBus", /* bus name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "GetNameOwner", /* method name */ + g_variant_new ("(s)", client->name), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) get_name_owner_cb, + client_ref (client)); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +start_service_by_name_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + Client *client = user_data; + GVariant *result; + + result = NULL; + + result = g_dbus_connection_call_finish (client->connection, + res, + NULL); + if (result != NULL) + { + guint32 start_service_result; + g_variant_get (result, "(u)", &start_service_result); + + if (start_service_result == 1) /* DBUS_START_REPLY_SUCCESS */ + { + invoke_get_name_owner (client); + } + else if (start_service_result == 2) /* DBUS_START_REPLY_ALREADY_RUNNING */ + { + invoke_get_name_owner (client); + } + else + { + g_warning ("Unexpected reply %d from StartServiceByName() method", start_service_result); + call_vanished_handler (client, FALSE); + client->initialized = TRUE; + } + } + else + { + /* Errors are not unexpected; the bus will reply e.g. + * + * org.freedesktop.DBus.Error.ServiceUnknown: The name org.gnome.Epiphany2 + * was not provided by any .service files + * + * This doesn't mean that the name doesn't have an owner, just + * that it's not provided by a .service file. So proceed to + * invoke GetNameOwner(). + */ + invoke_get_name_owner (client); + } + + if (result != NULL) + g_variant_unref (result); + client_unref (client); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +has_connection (Client *client) +{ + /* listen for disconnection */ + client->disconnected_signal_handler_id = g_signal_connect (client->connection, + "closed", + G_CALLBACK (on_connection_disconnected), + client); + + /* start listening to NameOwnerChanged messages immediately */ + client->name_owner_changed_subscription_id = g_dbus_connection_signal_subscribe (client->connection, + "org.freedesktop.DBus", /* name */ + "org.freedesktop.DBus", /* if */ + "NameOwnerChanged", /* signal */ + "/org/freedesktop/DBus", /* path */ + client->name, + on_name_owner_changed, + client, + NULL); + + if (client->flags & G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + g_dbus_connection_call (client->connection, + "org.freedesktop.DBus", /* bus name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "StartServiceByName", /* method name */ + g_variant_new ("(su)", client->name, 0), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) start_service_by_name_cb, + client_ref (client)); + } + else + { + /* check owner */ + invoke_get_name_owner (client); + } +} + + +static void +connection_get_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + Client *client = user_data; + + client->connection = g_bus_get_finish (res, NULL); + if (client->connection == NULL) + { + call_vanished_handler (client, FALSE); + goto out; + } + + has_connection (client); + + out: + client_unref (client); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_bus_watch_name: + * @bus_type: The type of bus to watch a name on. + * @name: The name (well-known or unique) to watch. + * @flags: Flags from the #GBusNameWatcherFlags enumeration. + * @name_appeared_handler: Handler to invoke when @name is known to exist or %NULL. + * @name_vanished_handler: Handler to invoke when @name is known to not exist or %NULL. + * @user_data: User data to pass to handlers. + * @user_data_free_func: Function for freeing @user_data or %NULL. + * + * Starts watching @name on the bus specified by @bus_type and calls + * @name_appeared_handler and @name_vanished_handler when the name is + * known to have a owner respectively known to lose its + * owner. Callbacks will be invoked in the thread-default main + * loop of the thread you are calling this function from. + * + * You are guaranteed that one of the handlers will be invoked after + * calling this function. When you are done watching the name, just + * call g_bus_unwatch_name() with the watcher id this function + * returns. + * + * If the name vanishes or appears (for example the application owning + * the name could restart), the handlers are also invoked. If the + * #GDBusConnection that is used for watching the name disconnects, then + * @name_vanished_handler is invoked since it is no longer + * possible to access the name. + * + * Another guarantee is that invocations of @name_appeared_handler + * and @name_vanished_handler are guaranteed to alternate; that + * is, if @name_appeared_handler is invoked then you are + * guaranteed that the next time one of the handlers is invoked, it + * will be @name_vanished_handler. The reverse is also true. + * + * This behavior makes it very simple to write applications that wants + * to take action when a certain name exists, see . Basically, the application + * should create object proxies in @name_appeared_handler and destroy + * them again (if any) in @name_vanished_handler. + * + * Returns: An identifier (never 0) that an be used with + * g_bus_unwatch_name() to stop watching the name. + * + * Since: 2.26 + */ +guint +g_bus_watch_name (GBusType bus_type, + const gchar *name, + GBusNameWatcherFlags flags, + GBusNameAppearedCallback name_appeared_handler, + GBusNameVanishedCallback name_vanished_handler, + gpointer user_data, + GDestroyNotify user_data_free_func) +{ + Client *client; + + g_return_val_if_fail (g_dbus_is_name (name), 0); + + G_LOCK (lock); + + client = g_new0 (Client, 1); + client->ref_count = 1; + client->id = next_global_id++; /* TODO: uh oh, handle overflow */ + client->name = g_strdup (name); + client->flags = flags; + client->name_appeared_handler = name_appeared_handler; + client->name_vanished_handler = name_vanished_handler; + client->user_data = user_data; + client->user_data_free_func = user_data_free_func; + client->main_context = g_main_context_get_thread_default (); + if (client->main_context != NULL) + g_main_context_ref (client->main_context); + + if (map_id_to_client == NULL) + { + map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal); + } + g_hash_table_insert (map_id_to_client, + GUINT_TO_POINTER (client->id), + client); + + g_bus_get (bus_type, + NULL, + connection_get_cb, + client_ref (client)); + + G_UNLOCK (lock); + + return client->id; +} + +/** + * g_bus_watch_name_on_connection: + * @connection: A #GDBusConnection that is not closed. + * @name: The name (well-known or unique) to watch. + * @flags: Flags from the #GBusNameWatcherFlags enumeration. + * @name_appeared_handler: Handler to invoke when @name is known to exist or %NULL. + * @name_vanished_handler: Handler to invoke when @name is known to not exist or %NULL. + * @user_data: User data to pass to handlers. + * @user_data_free_func: Function for freeing @user_data or %NULL. + * + * Like g_bus_watch_name() but takes a #GDBusConnection instead of a + * #GBusType. + * + * Returns: An identifier (never 0) that an be used with + * g_bus_unwatch_name() to stop watching the name. + * + * Since: 2.26 + */ +guint g_bus_watch_name_on_connection (GDBusConnection *connection, + const gchar *name, + GBusNameWatcherFlags flags, + GBusNameAppearedCallback name_appeared_handler, + GBusNameVanishedCallback name_vanished_handler, + gpointer user_data, + GDestroyNotify user_data_free_func) +{ + Client *client; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); + g_return_val_if_fail (g_dbus_is_name (name), 0); + + G_LOCK (lock); + + client = g_new0 (Client, 1); + client->ref_count = 1; + client->id = next_global_id++; /* TODO: uh oh, handle overflow */ + client->name = g_strdup (name); + client->flags = flags; + client->name_appeared_handler = name_appeared_handler; + client->name_vanished_handler = name_vanished_handler; + client->user_data = user_data; + client->user_data_free_func = user_data_free_func; + client->main_context = g_main_context_get_thread_default (); + if (client->main_context != NULL) + g_main_context_ref (client->main_context); + + if (map_id_to_client == NULL) + { + map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal); + } + g_hash_table_insert (map_id_to_client, + GUINT_TO_POINTER (client->id), + client); + + client->connection = g_object_ref (connection); + G_UNLOCK (lock); + + has_connection (client); + + return client->id; +} + +/** + * g_bus_unwatch_name: + * @watcher_id: An identifier obtained from g_bus_watch_name() + * + * Stops watching a name. + * + * Since: 2.26 + */ +void +g_bus_unwatch_name (guint watcher_id) +{ + Client *client; + + g_return_if_fail (watcher_id > 0); + + client = NULL; + + G_LOCK (lock); + if (watcher_id == 0 || + map_id_to_client == NULL || + (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL) + { + g_warning ("Invalid id %d passed to g_bus_unwatch_name()", watcher_id); + goto out; + } + + client->cancelled = TRUE; + g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id))); + + out: + G_UNLOCK (lock); + + /* do callback without holding lock */ + if (client != NULL) + { + client_unref (client); + } +} + +#define __G_DBUS_NAME_WATCHING_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusnamewatching.h b/gio/gdbusnamewatching.h new file mode 100644 index 000000000..a424f4362 --- /dev/null +++ b/gio/gdbusnamewatching.h @@ -0,0 +1,79 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_NAME_WATCHING_H__ +#define __G_DBUS_NAME_WATCHING_H__ + +#include + +G_BEGIN_DECLS + +/** + * GBusNameAppearedCallback: + * @connection: The #GDBusConnection the name is being watched on. + * @name: The name being watched. + * @name_owner: Unique name of the owner of the name being watched. + * @user_data: User data passed to g_bus_watch_name(). + * + * Invoked when the name being watched is known to have to have a owner. + * + * Since: 2.26 + */ +typedef void (*GBusNameAppearedCallback) (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data); + +/** + * GBusNameVanishedCallback: + * @connection: The #GDBusConnection the name is being watched on. + * @name: The name being watched. + * @user_data: User data passed to g_bus_watch_name(). + * + * Invoked when the name being watched is known not to have to have a owner. + * + * Since: 2.26 + */ +typedef void (*GBusNameVanishedCallback) (GDBusConnection *connection, + const gchar *name, + gpointer user_data); + + +guint g_bus_watch_name (GBusType bus_type, + const gchar *name, + GBusNameWatcherFlags flags, + GBusNameAppearedCallback name_appeared_handler, + GBusNameVanishedCallback name_vanished_handler, + gpointer user_data, + GDestroyNotify user_data_free_func); +guint g_bus_watch_name_on_connection (GDBusConnection *connection, + const gchar *name, + GBusNameWatcherFlags flags, + GBusNameAppearedCallback name_appeared_handler, + GBusNameVanishedCallback name_vanished_handler, + gpointer user_data, + GDestroyNotify user_data_free_func); +void g_bus_unwatch_name (guint watcher_id); + +G_END_DECLS + +#endif /* __G_DBUS_NAME_WATCHING_H__ */ diff --git a/gio/gdbusprivate.c b/gio/gdbusprivate.c new file mode 100644 index 000000000..da06fe56c --- /dev/null +++ b/gio/gdbusprivate.c @@ -0,0 +1,1057 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include +#include + +#ifdef G_OS_UNIX +#include +#include +#include "gunixcredentialsmessage.h" +#include +#endif + +#include "giotypes.h" +#include "gsocket.h" +#include "gdbusprivate.h" +#include "gdbusmessage.h" +#include "gdbuserror.h" +#include "gdbusintrospection.h" +#include "gasyncresult.h" +#include "gsimpleasyncresult.h" +#include "ginputstream.h" +#include "giostream.h" +#include "gsocketcontrolmessage.h" + +#ifdef G_OS_UNIX +#include +#include "gunixfdmessage.h" +#include "gunixconnection.h" +#endif + +#include "glibintl.h" +#include "gioalias.h" + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +hexdump (const gchar *data, gsize len, guint indent) +{ + guint n, m; + GString *ret; + + ret = g_string_new (NULL); + + for (n = 0; n < len; n += 16) + { + g_string_append_printf (ret, "%*s%04x: ", indent, "", n); + + for (m = n; m < n + 16; m++) + { + if (m > n && (m%4) == 0) + g_string_append_c (ret, ' '); + if (m < len) + g_string_append_printf (ret, "%02x ", (guchar) data[m]); + else + g_string_append (ret, " "); + } + + g_string_append (ret, " "); + + for (m = n; m < len && m < n + 16; m++) + g_string_append_c (ret, g_ascii_isprint (data[m]) ? data[m] : '.'); + + g_string_append_c (ret, '\n'); + } + + return g_string_free (ret, FALSE); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* Unfortunately ancillary messages are discarded when reading from a + * socket using the GSocketInputStream abstraction. So we provide a + * very GInputStream-ish API that uses GSocket in this case (very + * similar to GSocketInputStream). + */ + +typedef struct +{ + GSocket *socket; + GCancellable *cancellable; + + void *buffer; + gsize count; + + GSocketControlMessage ***messages; + gint *num_messages; + + GSimpleAsyncResult *simple; + + gboolean from_mainloop; +} ReadWithControlData; + +static void +read_with_control_data_free (ReadWithControlData *data) +{ + g_object_unref (data->socket); + if (data->cancellable != NULL) + g_object_unref (data->cancellable); + g_free (data); +} + +static gboolean +_g_socket_read_with_control_messages_ready (GSocket *socket, + GIOCondition condition, + gpointer user_data) +{ + ReadWithControlData *data = user_data; + GError *error; + gssize result; + GInputVector vector; + + error = NULL; + vector.buffer = data->buffer; + vector.size = data->count; + result = g_socket_receive_message (data->socket, + NULL, /* address */ + &vector, + 1, + data->messages, + data->num_messages, + NULL, + data->cancellable, + &error); + if (result >= 0) + { + g_simple_async_result_set_op_res_gssize (data->simple, result); + } + else + { + g_assert (error != NULL); + g_simple_async_result_set_from_error (data->simple, error); + g_error_free (error); + } + + if (data->from_mainloop) + g_simple_async_result_complete (data->simple); + else + g_simple_async_result_complete_in_idle (data->simple); + + return FALSE; +} + +static void +_g_socket_read_with_control_messages (GSocket *socket, + void *buffer, + gsize count, + GSocketControlMessage ***messages, + gint *num_messages, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ReadWithControlData *data; + + data = g_new0 (ReadWithControlData, 1); + data->socket = g_object_ref (socket); + data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL; + data->buffer = buffer; + data->count = count; + data->messages = messages; + data->num_messages = num_messages; + + data->simple = g_simple_async_result_new (G_OBJECT (socket), + callback, + user_data, + _g_socket_read_with_control_messages); + + if (!g_socket_condition_check (socket, G_IO_IN)) + { + GSource *source; + data->from_mainloop = TRUE; + source = g_socket_create_source (data->socket, + G_IO_IN | G_IO_HUP | G_IO_ERR, + cancellable); + g_source_set_callback (source, + (GSourceFunc) _g_socket_read_with_control_messages_ready, + data, + (GDestroyNotify) read_with_control_data_free); + g_source_attach (source, g_main_context_get_thread_default ()); + g_source_unref (source); + } + else + { + _g_socket_read_with_control_messages_ready (data->socket, G_IO_IN, data); + read_with_control_data_free (data); + } +} + +static gssize +_g_socket_read_with_control_messages_finish (GSocket *socket, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + + g_return_val_if_fail (G_IS_SOCKET (socket), -1); + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == _g_socket_read_with_control_messages); + + if (g_simple_async_result_propagate_error (simple, error)) + return -1; + else + return g_simple_async_result_get_op_res_gssize (simple); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +G_LOCK_DEFINE_STATIC (shared_thread_lock); + +typedef struct +{ + gint num_users; + GThread *thread; + GMainContext *context; + GMainLoop *loop; +} SharedThreadData; + +static SharedThreadData *shared_thread_data = NULL; + +static gpointer +shared_thread_func (gpointer data) +{ + g_main_context_push_thread_default (shared_thread_data->context); + g_main_loop_run (shared_thread_data->loop); + g_main_context_pop_thread_default (shared_thread_data->context); + return NULL; +} + +typedef void (*GDBusSharedThreadFunc) (gpointer user_data); + +typedef struct +{ + GDBusSharedThreadFunc func; + gpointer user_data; + gboolean done; +} CallerData; + +static gboolean +invoke_caller (gpointer user_data) +{ + CallerData *data = user_data; + data->func (data->user_data); + data->done = TRUE; + return FALSE; +} + +static void +_g_dbus_shared_thread_ref (GDBusSharedThreadFunc func, + gpointer user_data) +{ + GError *error; + GSource *idle_source; + CallerData *data; + + G_LOCK (shared_thread_lock); + + if (shared_thread_data != NULL) + { + shared_thread_data->num_users += 1; + goto have_thread; + } + + shared_thread_data = g_new0 (SharedThreadData, 1); + shared_thread_data->num_users = 1; + + error = NULL; + shared_thread_data->context = g_main_context_new (); + shared_thread_data->loop = g_main_loop_new (shared_thread_data->context, FALSE); + shared_thread_data->thread = g_thread_create (shared_thread_func, + NULL, + TRUE, + &error); + g_assert_no_error (error); + + have_thread: + + data = g_new0 (CallerData, 1); + data->func = func; + data->user_data = user_data; + data->done = FALSE; + + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); + g_source_set_callback (idle_source, + invoke_caller, + data, + NULL); + g_source_attach (idle_source, shared_thread_data->context); + g_source_unref (idle_source); + + /* wait for the user code to run.. hmm.. probably use a condition variable instead */ + while (!data->done) + g_thread_yield (); + + g_free (data); + + G_UNLOCK (shared_thread_lock); +} + +static void +_g_dbus_shared_thread_unref (void) +{ + /* TODO: actually destroy the shared thread here */ +#if 0 + G_LOCK (shared_thread_lock); + g_assert (shared_thread_data != NULL); + shared_thread_data->num_users -= 1; + if (shared_thread_data->num_users == 0) + { + g_main_loop_quit (shared_thread_data->loop); + //g_thread_join (shared_thread_data->thread); + g_main_loop_unref (shared_thread_data->loop); + g_main_context_unref (shared_thread_data->context); + g_free (shared_thread_data); + shared_thread_data = NULL; + G_UNLOCK (shared_thread_lock); + } + else + { + G_UNLOCK (shared_thread_lock); + } +#endif +} + +/* ---------------------------------------------------------------------------------------------------- */ + +struct GDBusWorker +{ + volatile gint ref_count; + gboolean stopped; + GIOStream *stream; + GDBusCapabilityFlags capabilities; + GCancellable *cancellable; + GDBusWorkerMessageReceivedCallback message_received_callback; + GDBusWorkerDisconnectedCallback disconnected_callback; + gpointer user_data; + + GThread *thread; + + /* if not NULL, stream is GSocketConnection */ + GSocket *socket; + + /* used for reading */ + GMutex *read_lock; + gchar *read_buffer; + gsize read_buffer_allocated_size; + gsize read_buffer_cur_size; + gsize read_buffer_bytes_wanted; + GUnixFDList *read_fd_list; + GSocketControlMessage **read_ancillary_messages; + gint read_num_ancillary_messages; + + /* used for writing */ + GMutex *write_lock; + GQueue *write_queue; + gboolean write_is_pending; +}; + +struct _MessageToWriteData ; +typedef struct _MessageToWriteData MessageToWriteData; + +static void message_to_write_data_free (MessageToWriteData *data); + +static GDBusWorker * +_g_dbus_worker_ref (GDBusWorker *worker) +{ + g_atomic_int_inc (&worker->ref_count); + return worker; +} + +static void +_g_dbus_worker_unref (GDBusWorker *worker) +{ + if (g_atomic_int_dec_and_test (&worker->ref_count)) + { + _g_dbus_shared_thread_unref (); + + g_object_unref (worker->stream); + + g_mutex_free (worker->read_lock); + g_object_unref (worker->cancellable); + if (worker->read_fd_list != NULL) + g_object_unref (worker->read_fd_list); + + g_mutex_free (worker->write_lock); + g_queue_foreach (worker->write_queue, + (GFunc) message_to_write_data_free, + NULL); + g_queue_free (worker->write_queue); + g_free (worker); + } +} + +static void +_g_dbus_worker_emit_disconnected (GDBusWorker *worker, + gboolean remote_peer_vanished, + GError *error) +{ + if (!worker->stopped) + worker->disconnected_callback (worker, remote_peer_vanished, error, worker->user_data); +} + +static void +_g_dbus_worker_emit_message (GDBusWorker *worker, + GDBusMessage *message) +{ + if (!worker->stopped) + worker->message_received_callback (worker, message, worker->user_data); +} + +static void _g_dbus_worker_do_read_unlocked (GDBusWorker *worker); + +/* called in private thread shared by all GDBusConnection instances (without read-lock held) */ +static void +_g_dbus_worker_do_read_cb (GInputStream *input_stream, + GAsyncResult *res, + gpointer user_data) +{ + GDBusWorker *worker = user_data; + GError *error; + gssize bytes_read; + + g_mutex_lock (worker->read_lock); + + /* If already stopped, don't even process the reply */ + if (worker->stopped) + goto out; + + error = NULL; + if (worker->socket == NULL) + bytes_read = g_input_stream_read_finish (g_io_stream_get_input_stream (worker->stream), + res, + &error); + else + bytes_read = _g_socket_read_with_control_messages_finish (worker->socket, + res, + &error); + if (worker->read_num_ancillary_messages > 0) + { + gint n; + for (n = 0; n < worker->read_num_ancillary_messages; n++) + { + GSocketControlMessage *control_message = G_SOCKET_CONTROL_MESSAGE (worker->read_ancillary_messages[n]); + + if (FALSE) + { + } +#ifdef G_OS_UNIX + else if (G_IS_UNIX_FD_MESSAGE (control_message)) + { + GUnixFDMessage *fd_message; + gint *fds; + gint num_fds; + + fd_message = G_UNIX_FD_MESSAGE (control_message); + fds = g_unix_fd_message_steal_fds (fd_message, &num_fds); + if (worker->read_fd_list == NULL) + { + worker->read_fd_list = g_unix_fd_list_new_from_array (fds, num_fds); + } + else + { + gint n; + for (n = 0; n < num_fds; n++) + { + /* TODO: really want a append_steal() */ + g_unix_fd_list_append (worker->read_fd_list, fds[n], NULL); + close (fds[n]); + } + } + g_free (fds); + } + else if (G_IS_UNIX_CREDENTIALS_MESSAGE (control_message)) + { + /* do nothing */ + } +#endif + else + { + if (error == NULL) + { + g_set_error (&error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Unexpected ancillary message of type %s received from peer", + g_type_name (G_TYPE_FROM_INSTANCE (control_message))); + _g_dbus_worker_emit_disconnected (worker, TRUE, error); + g_error_free (error); + g_object_unref (control_message); + n++; + while (n < worker->read_num_ancillary_messages) + g_object_unref (worker->read_ancillary_messages[n++]); + g_free (worker->read_ancillary_messages); + goto out; + } + } + g_object_unref (control_message); + } + g_free (worker->read_ancillary_messages); + } + + if (bytes_read == -1) + { + _g_dbus_worker_emit_disconnected (worker, TRUE, error); + g_error_free (error); + goto out; + } + +#if 0 + g_debug ("read %d bytes (is_closed=%d blocking=%d condition=0x%02x) stream %p, %p", + (gint) bytes_read, + g_socket_is_closed (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream))), + g_socket_get_blocking (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream))), + g_socket_condition_check (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream)), + G_IO_IN | G_IO_OUT | G_IO_HUP), + worker->stream, + worker); +#endif + + /* TODO: hmm, hmm... */ + if (bytes_read == 0) + { + g_set_error (&error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Underlying GIOStream returned 0 bytes on an async read"); + _g_dbus_worker_emit_disconnected (worker, TRUE, error); + g_error_free (error); + goto out; + } + + worker->read_buffer_cur_size += bytes_read; + if (worker->read_buffer_bytes_wanted == worker->read_buffer_cur_size) + { + /* OK, got what we asked for! */ + if (worker->read_buffer_bytes_wanted == 16) + { + gssize message_len; + /* OK, got the header - determine how many more bytes are needed */ + error = NULL; + message_len = g_dbus_message_bytes_needed ((guchar *) worker->read_buffer, + 16, + &error); + if (message_len == -1) + { + g_warning ("_g_dbus_worker_do_read_cb: error determing bytes needed: %s", error->message); + _g_dbus_worker_emit_disconnected (worker, FALSE, error); + g_error_free (error); + goto out; + } + + worker->read_buffer_bytes_wanted = message_len; + _g_dbus_worker_do_read_unlocked (worker); + } + else + { + GDBusMessage *message; + error = NULL; + + /* TODO: use connection->priv->auth to decode the message */ + + message = g_dbus_message_new_from_blob ((guchar *) worker->read_buffer, + worker->read_buffer_cur_size, + worker->capabilities, + &error); + if (message == NULL) + { + _g_dbus_worker_emit_disconnected (worker, FALSE, error); + g_error_free (error); + goto out; + } + + if (worker->read_fd_list != NULL) + { + g_dbus_message_set_unix_fd_list (message, worker->read_fd_list); + worker->read_fd_list = NULL; + } + + if (G_UNLIKELY (_g_dbus_debug_message ())) + { + gchar *s; + g_print ("========================================================================\n" + "GDBus-debug:Message:\n" + " <<<< RECEIVED D-Bus message (%" G_GSIZE_FORMAT " bytes)\n", + worker->read_buffer_cur_size); + s = g_dbus_message_print (message, 2); + g_print ("%s", s); + g_free (s); + s = hexdump (worker->read_buffer, worker->read_buffer_cur_size, 2); + g_print ("%s\n", s); + g_free (s); + } + + /* yay, got a message, go deliver it */ + _g_dbus_worker_emit_message (worker, message); + g_object_unref (message); + + /* start reading another message! */ + worker->read_buffer_bytes_wanted = 0; + worker->read_buffer_cur_size = 0; + _g_dbus_worker_do_read_unlocked (worker); + } + } + else + { + /* didn't get all the bytes we requested - so repeat the request... */ + _g_dbus_worker_do_read_unlocked (worker); + } + + out: + g_mutex_unlock (worker->read_lock); + + /* gives up the reference acquired when calling g_input_stream_read_async() */ + _g_dbus_worker_unref (worker); +} + +/* called in private thread shared by all GDBusConnection instances (with read-lock held) */ +static void +_g_dbus_worker_do_read_unlocked (GDBusWorker *worker) +{ + /* if bytes_wanted is zero, it means start reading a message */ + if (worker->read_buffer_bytes_wanted == 0) + { + worker->read_buffer_cur_size = 0; + worker->read_buffer_bytes_wanted = 16; + } + + /* ensure we have a (big enough) buffer */ + if (worker->read_buffer == NULL || worker->read_buffer_bytes_wanted > worker->read_buffer_allocated_size) + { + /* TODO: 4096 is randomly chosen; might want a better chosen default minimum */ + worker->read_buffer_allocated_size = MAX (worker->read_buffer_bytes_wanted, 4096); + worker->read_buffer = g_realloc (worker->read_buffer, worker->read_buffer_allocated_size); + } + + if (worker->socket == NULL) + g_input_stream_read_async (g_io_stream_get_input_stream (worker->stream), + worker->read_buffer + worker->read_buffer_cur_size, + worker->read_buffer_bytes_wanted - worker->read_buffer_cur_size, + G_PRIORITY_DEFAULT, + worker->cancellable, + (GAsyncReadyCallback) _g_dbus_worker_do_read_cb, + _g_dbus_worker_ref (worker)); + else + { + worker->read_ancillary_messages = NULL; + worker->read_num_ancillary_messages = 0; + _g_socket_read_with_control_messages (worker->socket, + worker->read_buffer + worker->read_buffer_cur_size, + worker->read_buffer_bytes_wanted - worker->read_buffer_cur_size, + &worker->read_ancillary_messages, + &worker->read_num_ancillary_messages, + G_PRIORITY_DEFAULT, + worker->cancellable, + (GAsyncReadyCallback) _g_dbus_worker_do_read_cb, + _g_dbus_worker_ref (worker)); + } +} + +/* called in private thread shared by all GDBusConnection instances (without read-lock held) */ +static void +_g_dbus_worker_do_read (GDBusWorker *worker) +{ + g_mutex_lock (worker->read_lock); + _g_dbus_worker_do_read_unlocked (worker); + g_mutex_unlock (worker->read_lock); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +struct _MessageToWriteData +{ + GDBusMessage *message; + gchar *blob; + gsize blob_size; +}; + +static void +message_to_write_data_free (MessageToWriteData *data) +{ + g_object_unref (data->message); + g_free (data->blob); + g_free (data); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* called in private thread shared by all GDBusConnection instances (with write-lock held) */ +static gboolean +write_message (GDBusWorker *worker, + MessageToWriteData *data, + GError **error) +{ + gboolean ret; + + g_return_val_if_fail (data->blob_size > 16, FALSE); + + ret = FALSE; + + /* First, the initial 16 bytes - special case UNIX sockets here + * since it may involve writing an ancillary message with file + * descriptors + */ +#ifdef G_OS_UNIX + { + GOutputVector vector; + GSocketControlMessage *message; + GUnixFDList *fd_list; + gssize bytes_written; + + fd_list = g_dbus_message_get_unix_fd_list (data->message); + + message = NULL; + if (fd_list != NULL) + { + if (!G_IS_UNIX_CONNECTION (worker->stream)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Tried sending a file descriptor on unsupported stream of type %s", + g_type_name (G_TYPE_FROM_INSTANCE (worker->stream))); + goto out; + } + else if (!(worker->capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING)) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Tried sending a file descriptor but remote peer does not support this capability"); + goto out; + } + message = g_unix_fd_message_new_with_fd_list (fd_list); + } + + vector.buffer = data->blob; + vector.size = 16; + + bytes_written = g_socket_send_message (worker->socket, + NULL, /* address */ + &vector, + 1, + message != NULL ? &message : NULL, + message != NULL ? 1 : 0, + G_SOCKET_MSG_NONE, + worker->cancellable, + error); + if (bytes_written == -1) + { + g_prefix_error (error, _("Error writing first 16 bytes of message to socket: ")); + if (message != NULL) + g_object_unref (message); + goto out; + } + if (message != NULL) + g_object_unref (message); + + if (bytes_written < 16) + { + /* TODO: I think this needs to be handled ... are we guaranteed that the ancillary + * messages are sent? + */ + g_assert_not_reached (); + } + } +#else + /* write the first 16 bytes (guaranteed to return an error if everything can't be written) */ + if (!g_output_stream_write_all (g_io_stream_get_output_stream (worker->stream), + (const gchar *) data->blob, + 16, + NULL, /* bytes_written */ + worker->cancellable, /* cancellable */ + error)) + goto out; +#endif + + /* Then write the rest of the message (guaranteed to return an error if everything can't be written) */ + if (!g_output_stream_write_all (g_io_stream_get_output_stream (worker->stream), + (const gchar *) data->blob + 16, + data->blob_size - 16, + NULL, /* bytes_written */ + worker->cancellable, /* cancellable */ + error)) + goto out; + + ret = TRUE; + + if (G_UNLIKELY (_g_dbus_debug_message ())) + { + gchar *s; + g_print ("========================================================================\n" + "GDBus-debug:Message:\n" + " >>>> SENT D-Bus message (%" G_GSIZE_FORMAT " bytes)\n", + data->blob_size); + s = g_dbus_message_print (data->message, 2); + g_print ("%s", s); + g_free (s); + s = hexdump (data->blob, data->blob_size, 2); + g_print ("%s\n", s); + g_free (s); + } + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* called in private thread shared by all GDBusConnection instances (without write-lock held) */ +static gboolean +write_message_in_idle_cb (gpointer user_data) +{ + GDBusWorker *worker = user_data; + gboolean more_writes_are_pending; + MessageToWriteData *data; + GError *error; + + g_mutex_lock (worker->write_lock); + + data = g_queue_pop_head (worker->write_queue); + g_assert (data != NULL); + + error = NULL; + if (!write_message (worker, + data, + &error)) + { + /* TODO: handle */ + _g_dbus_worker_emit_disconnected (worker, TRUE, error); + g_error_free (error); + } + message_to_write_data_free (data); + + more_writes_are_pending = (g_queue_get_length (worker->write_queue) > 0); + + worker->write_is_pending = more_writes_are_pending; + g_mutex_unlock (worker->write_lock); + + return more_writes_are_pending; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* can be called from any thread - steals blob */ +void +_g_dbus_worker_send_message (GDBusWorker *worker, + GDBusMessage *message, + gchar *blob, + gsize blob_len) +{ + MessageToWriteData *data; + + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + g_return_if_fail (blob != NULL); + g_return_if_fail (blob_len > 16); + + data = g_new0 (MessageToWriteData, 1); + data->message = g_object_ref (message); + data->blob = blob; /* steal! */ + data->blob_size = blob_len; + + g_mutex_lock (worker->write_lock); + g_queue_push_tail (worker->write_queue, data); + if (!worker->write_is_pending) + { + GSource *idle_source; + + worker->write_is_pending = TRUE; + + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); + g_source_set_callback (idle_source, + write_message_in_idle_cb, + _g_dbus_worker_ref (worker), + (GDestroyNotify) _g_dbus_worker_unref); + g_source_attach (idle_source, shared_thread_data->context); + g_source_unref (idle_source); + } + g_mutex_unlock (worker->write_lock); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +_g_dbus_worker_thread_begin_func (gpointer user_data) +{ + GDBusWorker *worker = user_data; + + worker->thread = g_thread_self (); + + /* begin reading */ + _g_dbus_worker_do_read (worker); +} + +GDBusWorker * +_g_dbus_worker_new (GIOStream *stream, + GDBusCapabilityFlags capabilities, + GDBusWorkerMessageReceivedCallback message_received_callback, + GDBusWorkerDisconnectedCallback disconnected_callback, + gpointer user_data) +{ + GDBusWorker *worker; + + g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL); + g_return_val_if_fail (message_received_callback != NULL, NULL); + g_return_val_if_fail (disconnected_callback != NULL, NULL); + + worker = g_new0 (GDBusWorker, 1); + worker->ref_count = 1; + + worker->read_lock = g_mutex_new (); + worker->message_received_callback = message_received_callback; + worker->disconnected_callback = disconnected_callback; + worker->user_data = user_data; + worker->stream = g_object_ref (stream); + worker->capabilities = capabilities; + worker->cancellable = g_cancellable_new (); + + worker->write_lock = g_mutex_new (); + worker->write_queue = g_queue_new (); + + if (G_IS_SOCKET_CONNECTION (worker->stream)) + worker->socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream)); + + _g_dbus_shared_thread_ref (_g_dbus_worker_thread_begin_func, worker); + + return worker; +} + +/* This can be called from any thread - frees worker - guarantees no callbacks + * will ever be issued again + */ +void +_g_dbus_worker_stop (GDBusWorker *worker) +{ + /* If we're called in the worker thread it means we are called from + * a worker callback. This is fine, we just can't lock in that case since + * we're already holding the lock... + */ + if (g_thread_self () != worker->thread) + g_mutex_lock (worker->read_lock); + worker->stopped = TRUE; + if (g_thread_self () != worker->thread) + g_mutex_unlock (worker->read_lock); + + g_cancellable_cancel (worker->cancellable); + _g_dbus_worker_unref (worker); +} + +#define G_DBUS_DEBUG_AUTHENTICATION (1<<0) +#define G_DBUS_DEBUG_MESSAGE (1<<1) +#define G_DBUS_DEBUG_ALL 0xffffffff +static gint _gdbus_debug_flags = 0; + +gboolean +_g_dbus_debug_authentication (void) +{ + _g_dbus_initialize (); + return (_gdbus_debug_flags & G_DBUS_DEBUG_AUTHENTICATION) != 0; +} + +gboolean +_g_dbus_debug_message (void) +{ + _g_dbus_initialize (); + return (_gdbus_debug_flags & G_DBUS_DEBUG_MESSAGE) != 0; +} + +/* + * _g_dbus_initialize: + * + * Does various one-time init things such as + * + * - registering the G_DBUS_ERROR error domain + * - parses the G_DBUS_DEBUG environment variable + */ +void +_g_dbus_initialize (void) +{ + static volatile gsize initialized = 0; + + if (g_once_init_enter (&initialized)) + { + volatile GQuark g_dbus_error_domain; + const gchar *debug; + + g_dbus_error_domain = G_DBUS_ERROR; + + debug = g_getenv ("G_DBUS_DEBUG"); + if (debug != NULL) + { + gchar **tokens; + guint n; + tokens = g_strsplit (debug, ",", 0); + for (n = 0; tokens[n] != NULL; n++) + { + if (g_strcmp0 (tokens[n], "authentication") == 0) + _gdbus_debug_flags |= G_DBUS_DEBUG_AUTHENTICATION; + else if (g_strcmp0 (tokens[n], "message") == 0) + _gdbus_debug_flags |= G_DBUS_DEBUG_MESSAGE; + else if (g_strcmp0 (tokens[n], "all") == 0) + _gdbus_debug_flags |= G_DBUS_DEBUG_ALL; + } + g_strfreev (tokens); + } + + g_once_init_leave (&initialized, 1); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +gchar * +_g_dbus_compute_complete_signature (GDBusArgInfo **args, + gboolean include_parentheses) +{ + GString *s; + guint n; + + if (include_parentheses) + s = g_string_new ("("); + else + s = g_string_new (""); + if (args != NULL) + for (n = 0; args[n] != NULL; n++) + g_string_append (s, args[n]->signature); + + if (include_parentheses) + g_string_append_c (s, ')'); + + return g_string_free (s, FALSE); +} + +#define __G_DBUS_PRIVATE_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusprivate.h b/gio/gdbusprivate.h new file mode 100644 index 000000000..37cc03d56 --- /dev/null +++ b/gio/gdbusprivate.h @@ -0,0 +1,83 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#if !defined (GIO_COMPILATION) +#error "gdbusprivate.h is a private header file." +#endif + +#ifndef __G_DBUS_PRIVATE_H__ +#define __G_DBUS_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct GDBusWorker GDBusWorker; + +typedef void (*GDBusWorkerMessageReceivedCallback) (GDBusWorker *worker, + GDBusMessage *message, + gpointer user_data); + +typedef void (*GDBusWorkerDisconnectedCallback) (GDBusWorker *worker, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data); + +/* This function may be called from any thread - callbacks will be in the shared private message thread + * and must not block. + */ +GDBusWorker *_g_dbus_worker_new (GIOStream *stream, + GDBusCapabilityFlags capabilities, + GDBusWorkerMessageReceivedCallback message_received_callback, + GDBusWorkerDisconnectedCallback disconnected_callback, + gpointer user_data); + +/* can be called from any thread - steals blob */ +void _g_dbus_worker_send_message (GDBusWorker *worker, + GDBusMessage *message, + gchar *blob, + gsize blob_len); + +/* can be called from any thread */ +void _g_dbus_worker_stop (GDBusWorker *worker); + +/* ---------------------------------------------------------------------------------------------------- */ + +void _g_dbus_initialize (void); +gboolean _g_dbus_debug_authentication (void); +gboolean _g_dbus_debug_message (void); + +gboolean _g_dbus_address_parse_entry (const gchar *address_entry, + gchar **out_transport_name, + GHashTable **out_key_value_pairs, + GError **error); + +gchar * _g_dbus_compute_complete_signature (GDBusArgInfo **args, + gboolean include_parentheses); + +/* ---------------------------------------------------------------------------------------------------- */ + +G_END_DECLS + +#endif /* __G_DBUS_PRIVATE_H__ */ diff --git a/gio/gdbusproxy.c b/gio/gdbusproxy.c new file mode 100644 index 000000000..911c65950 --- /dev/null +++ b/gio/gdbusproxy.c @@ -0,0 +1,1714 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include +#include + +#include + +#include "gdbusutils.h" +#include "gdbusproxy.h" +#include "gioenumtypes.h" +#include "gdbusconnection.h" +#include "gdbuserror.h" +#include "gdbusprivate.h" +#include "gio-marshal.h" +#include "ginitable.h" +#include "gasyncinitable.h" +#include "gioerror.h" +#include "gasyncresult.h" +#include "gsimpleasyncresult.h" + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusproxy + * @short_description: Base class for proxies + * @include: gio/gio.h + * + * #GDBusProxy is a base class used for proxies to access a D-Bus + * interface on a remote object. A #GDBusProxy can only be constructed + * for unique name bus and does not track whether the name + * vanishes. Use g_bus_watch_proxy() to construct #GDBusProxy proxies + * for owners of a well-known names. + * + * By default, #GDBusProxy will cache all properties (and listen for + * their changes) of the remote object, and proxy all signals that gets + * emitted. This behaviour can be changed by passing suitable + * #GDBusProxyFlags when the proxy is created. + * + * The generic #GDBusProxy::g-properties-changed and #GDBusProxy::g-signal + * signals are not very convenient to work with. Therefore, the recommended + * way of working with proxies is to subclass #GDBusProxy, and have + * more natural properties and signals in your derived class. The + * @interface_type argument of g_bus_watch_proxy() lets you obtain + * instances of your derived class when using the high-level API. + * + * See for an example. + */ + +struct _GDBusProxyPrivate +{ + GDBusConnection *connection; + GDBusProxyFlags flags; + gchar *unique_bus_name; + gchar *object_path; + gchar *interface_name; + gint timeout_msec; + + /* gchar* -> GVariant* */ + GHashTable *properties; + + GDBusInterfaceInfo *expected_interface; + + guint properties_changed_subscriber_id; + guint signals_subscriber_id; + + gboolean initialized; +}; + +enum +{ + PROP_0, + PROP_G_CONNECTION, + PROP_G_UNIQUE_BUS_NAME, + PROP_G_FLAGS, + PROP_G_OBJECT_PATH, + PROP_G_INTERFACE_NAME, + PROP_G_DEFAULT_TIMEOUT, + PROP_G_INTERFACE_INFO +}; + +enum +{ + PROPERTIES_CHANGED_SIGNAL, + SIGNAL_SIGNAL, + LAST_SIGNAL, +}; + +static void g_dbus_proxy_constructed (GObject *object); + +guint signals[LAST_SIGNAL] = {0}; + +static void initable_iface_init (GInitableIface *initable_iface); +static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface); + +G_DEFINE_TYPE_WITH_CODE (GDBusProxy, g_dbus_proxy, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init) + ); + +static void +g_dbus_proxy_finalize (GObject *object) +{ + GDBusProxy *proxy = G_DBUS_PROXY (object); + + if (proxy->priv->properties_changed_subscriber_id > 0) + g_dbus_connection_signal_unsubscribe (proxy->priv->connection, + proxy->priv->properties_changed_subscriber_id); + + if (proxy->priv->signals_subscriber_id > 0) + g_dbus_connection_signal_unsubscribe (proxy->priv->connection, + proxy->priv->signals_subscriber_id); + + g_object_unref (proxy->priv->connection); + g_free (proxy->priv->unique_bus_name); + g_free (proxy->priv->object_path); + g_free (proxy->priv->interface_name); + if (proxy->priv->properties != NULL) + g_hash_table_unref (proxy->priv->properties); + + if (proxy->priv->expected_interface != NULL) + g_dbus_interface_info_unref (proxy->priv->expected_interface); + + G_OBJECT_CLASS (g_dbus_proxy_parent_class)->finalize (object); +} + +static void +g_dbus_proxy_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GDBusProxy *proxy = G_DBUS_PROXY (object); + + switch (prop_id) + { + case PROP_G_CONNECTION: + g_value_set_object (value, proxy->priv->connection); + break; + + case PROP_G_FLAGS: + g_value_set_flags (value, proxy->priv->flags); + break; + + case PROP_G_UNIQUE_BUS_NAME: + g_value_set_string (value, proxy->priv->unique_bus_name); + break; + + case PROP_G_OBJECT_PATH: + g_value_set_string (value, proxy->priv->object_path); + break; + + case PROP_G_INTERFACE_NAME: + g_value_set_string (value, proxy->priv->interface_name); + break; + + case PROP_G_DEFAULT_TIMEOUT: + g_value_set_int (value, proxy->priv->timeout_msec); + break; + + case PROP_G_INTERFACE_INFO: + g_value_set_boxed (value, g_dbus_proxy_get_interface_info (proxy)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_dbus_proxy_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GDBusProxy *proxy = G_DBUS_PROXY (object); + + switch (prop_id) + { + case PROP_G_CONNECTION: + proxy->priv->connection = g_value_dup_object (value); + break; + + case PROP_G_FLAGS: + proxy->priv->flags = g_value_get_flags (value); + break; + + case PROP_G_UNIQUE_BUS_NAME: + proxy->priv->unique_bus_name = g_value_dup_string (value); + break; + + case PROP_G_OBJECT_PATH: + proxy->priv->object_path = g_value_dup_string (value); + break; + + case PROP_G_INTERFACE_NAME: + proxy->priv->interface_name = g_value_dup_string (value); + break; + + case PROP_G_DEFAULT_TIMEOUT: + g_dbus_proxy_set_default_timeout (proxy, g_value_get_int (value)); + break; + + case PROP_G_INTERFACE_INFO: + g_dbus_proxy_set_interface_info (proxy, g_value_get_boxed (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_dbus_proxy_class_init (GDBusProxyClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_dbus_proxy_finalize; + gobject_class->set_property = g_dbus_proxy_set_property; + gobject_class->get_property = g_dbus_proxy_get_property; + gobject_class->constructed = g_dbus_proxy_constructed; + + /* Note that all property names are prefixed to avoid collisions with D-Bus property names + * in derived classes */ + + /** + * GDBusProxy:g-interface-info: + * + * Ensure that interactions with this proxy conform to the given + * interface. For example, when completing a method call, if the + * type signature of the message isn't what's expected, the given + * #GError is set. Signals that have a type signature mismatch are + * simply dropped. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_G_INTERFACE_INFO, + g_param_spec_boxed ("g-interface-info", + P_("Interface Information"), + P_("Interface Information"), + G_TYPE_DBUS_INTERFACE_INFO, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusProxy:g-connection: + * + * The #GDBusConnection the proxy is for. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_G_CONNECTION, + g_param_spec_object ("g-connection", + P_("g-connection"), + P_("The connection the proxy is for"), + G_TYPE_DBUS_CONNECTION, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusProxy:g-flags: + * + * Flags from the #GDBusProxyFlags enumeration. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_G_FLAGS, + g_param_spec_flags ("g-flags", + P_("g-flags"), + P_("Flags for the proxy"), + G_TYPE_DBUS_PROXY_FLAGS, + G_DBUS_PROXY_FLAGS_NONE, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusProxy:g-unique-bus-name: + * + * The unique bus name the proxy is for. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_G_UNIQUE_BUS_NAME, + g_param_spec_string ("g-unique-bus-name", + P_("g-unique-bus-name"), + P_("The unique bus name the proxy is for"), + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusProxy:g-object-path: + * + * The object path the proxy is for. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_G_OBJECT_PATH, + g_param_spec_string ("g-object-path", + P_("g-object-path"), + P_("The object path the proxy is for"), + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusProxy:g-interface-name: + * + * The D-Bus interface name the proxy is for. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_G_INTERFACE_NAME, + g_param_spec_string ("g-interface-name", + P_("g-interface-name"), + P_("The D-Bus interface name the proxy is for"), + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusProxy:g-default-timeout: + * + * The timeout to use if -1 (specifying default timeout) is passed + * as @timeout_msec in the g_dbus_proxy_call() and + * g_dbus_proxy_call_sync() functions. + * + * This allows applications to set a proxy-wide timeout for all + * remote method invocations on the proxy. If this property is -1, + * the default timeout (typically 25 seconds) is used. If set to + * %G_MAXINT, then no timeout is used. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_G_DEFAULT_TIMEOUT, + g_param_spec_int ("g-default-timeout", + P_("Default Timeout"), + P_("Timeout for remote method invocation"), + -1, + G_MAXINT, + -1, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusProxy::g-properties-changed: + * @proxy: The #GDBusProxy emitting the signal. + * @changed_properties: A #GVariant containing the properties that changed + * @invalidated_properties: A %NULL terminated array of properties that was invalidated + * + * Emitted when one or more D-Bus properties on @proxy changes. The + * local cache has already been updated when this signal fires. Note + * that both @changed_properties and @invalidated_properties are + * guaranteed to never be %NULL (either may be empty though). + * + * This signal corresponds to the + * PropertiesChanged D-Bus signal on the + * org.freedesktop.DBus.Properties interface. + * + * Since: 2.26 + */ + signals[PROPERTIES_CHANGED_SIGNAL] = g_signal_new ("g-properties-changed", + G_TYPE_DBUS_PROXY, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GDBusProxyClass, g_properties_changed), + NULL, + NULL, + _gio_marshal_VOID__BOXED_BOXED, + G_TYPE_NONE, + 2, + G_TYPE_VARIANT, + G_TYPE_STRV | G_SIGNAL_TYPE_STATIC_SCOPE); + + /** + * GDBusProxy::g-signal: + * @proxy: The #GDBusProxy emitting the signal. + * @sender_name: The sender of the signal or %NULL if the connection is not a bus connection. + * @signal_name: The name of the signal. + * @parameters: A #GVariant tuple with parameters for the signal. + * + * Emitted when a signal from the remote object and interface that @proxy is for, has been received. + * + * Since: 2.26 + */ + signals[SIGNAL_SIGNAL] = g_signal_new ("g-signal", + G_TYPE_DBUS_PROXY, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GDBusProxyClass, g_signal), + NULL, + NULL, + _gio_marshal_VOID__STRING_STRING_BOXED, + G_TYPE_NONE, + 3, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_VARIANT); + + + g_type_class_add_private (klass, sizeof (GDBusProxyPrivate)); +} + +static void +g_dbus_proxy_init (GDBusProxy *proxy) +{ + proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (proxy, G_TYPE_DBUS_PROXY, GDBusProxyPrivate); + proxy->priv->properties = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_variant_unref); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_proxy_get_cached_property_names: + * @proxy: A #GDBusProxy. + * + * Gets the names of all cached properties on @proxy. + * + * Returns: A %NULL-terminated array of strings or %NULL if @proxy has + * no cached properties. Free the returned array with g_strfreev(). + * + * Since: 2.26 + */ +gchar ** +g_dbus_proxy_get_cached_property_names (GDBusProxy *proxy) +{ + gchar **names; + GPtrArray *p; + GHashTableIter iter; + const gchar *key; + + g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL); + + names = NULL; + if (g_hash_table_size (proxy->priv->properties) == 0) + goto out; + + p = g_ptr_array_new (); + + g_hash_table_iter_init (&iter, proxy->priv->properties); + while (g_hash_table_iter_next (&iter, (gpointer) &key, NULL)) + g_ptr_array_add (p, g_strdup (key)); + g_ptr_array_sort (p, (GCompareFunc) g_strcmp0); + g_ptr_array_add (p, NULL); + + names = (gchar **) g_ptr_array_free (p, FALSE); + + out: + return names; +} + +static const GDBusPropertyInfo * +lookup_property_info_or_warn (GDBusProxy *proxy, + const gchar *property_name) +{ + const GDBusPropertyInfo *info; + + if (proxy->priv->expected_interface == NULL) + return NULL; + + info = g_dbus_interface_info_lookup_property (proxy->priv->expected_interface, property_name); + if (info == NULL) + { + g_warning ("Trying to lookup property %s which isn't in expected interface %s", + property_name, + proxy->priv->expected_interface->name); + } + + return info; +} + +/** + * g_dbus_proxy_get_cached_property: + * @proxy: A #GDBusProxy. + * @property_name: Property name. + * + * Looks up the value for a property from the cache. This call does no + * blocking IO. + * + * If @proxy has an expected interface (see + * #GDBusProxy:g-interface-info), then @property_name (for existence) + * is checked against it. + * + * Returns: A reference to the #GVariant instance that holds the value + * for @property_name or %NULL if the value is not in the cache. The + * returned reference must be freed with g_variant_unref(). + * + * Since: 2.26 + */ +GVariant * +g_dbus_proxy_get_cached_property (GDBusProxy *proxy, + const gchar *property_name) +{ + GVariant *value; + + g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + value = g_hash_table_lookup (proxy->priv->properties, property_name); + if (value == NULL) + { + const GDBusPropertyInfo *info; + info = lookup_property_info_or_warn (proxy, property_name); + /* no difference */ + goto out; + } + + g_variant_ref (value); + + out: + return value; +} + +/** + * g_dbus_proxy_set_cached_property: + * @proxy: A #GDBusProxy + * @property_name: Property name. + * @value: Value for the property or %NULL to remove it from the cache. + * + * If @value is not %NULL, sets the cached value for the property with + * name @property_name to the value in @value. + * + * If @value is %NULL, then the cached value is removed from the + * property cache. + * + * If @proxy has an expected interface (see + * #GDBusProxy:g-interface-info), then @property_name (for existence) + * and @value (for the type) is checked against it. + * + * If the @value #GVariant is floating, it is consumed. This allows + * convenient 'inline' use of g_variant_new(), e.g. + * |[ + * g_dbus_proxy_set_cached_property (proxy, + * "SomeProperty", + * g_variant_new ("(si)", + * "A String", + * 42)); + * ]| + * + * Normally you will not need to use this method since @proxy is + * tracking changes using the + * org.freedesktop.DBus.Properties.PropertiesChanged + * D-Bus signal. However, for performance reasons an object may decide + * to not use this signal for some properties and instead use a + * proprietary out-of-band mechanism to transmit changes. + * + * As a concrete example, consider an object with a property + * ChatroomParticipants which is an array of + * strings. Instead of transmitting the same (long) array every time + * the property changes, it is more efficient to only transmit the + * delta using e.g. signals ChatroomParticipantJoined(String + * name) and ChatroomParticipantParted(String + * name). + * + * Since: 2.26 + */ +void +g_dbus_proxy_set_cached_property (GDBusProxy *proxy, + const gchar *property_name, + GVariant *value) +{ + const GDBusPropertyInfo *info; + + g_return_if_fail (G_IS_DBUS_PROXY (proxy)); + g_return_if_fail (property_name != NULL); + + if (value != NULL) + { + info = lookup_property_info_or_warn (proxy, property_name); + if (info != NULL) + { + if (g_strcmp0 (info->signature, g_variant_get_type_string (value)) != 0) + { + g_warning (_("Trying to set property %s of type %s but according to the expected " + "interface the type is %s"), + property_name, + g_variant_get_type_string (value), + info->signature); + goto out; + } + } + g_hash_table_insert (proxy->priv->properties, + g_strdup (property_name), + g_variant_ref_sink (value)); + } + else + { + g_hash_table_remove (proxy->priv->properties, property_name); + } + + out: + ; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +on_signal_received (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (user_data); + + if (!proxy->priv->initialized) + goto out; + + g_signal_emit (proxy, + signals[SIGNAL_SIGNAL], + 0, + sender_name, + signal_name, + parameters); + out: + ; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +on_properties_changed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (user_data); + GError *error; + const gchar *interface_name_for_signal; + GVariant *changed_properties; + gchar **invalidated_properties; + GVariantIter iter; + gchar *key; + GVariant *value; + + error = NULL; + changed_properties = NULL; + invalidated_properties = NULL; + + if (!proxy->priv->initialized) + goto out; + + if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)"))) + { + g_warning ("Value for PropertiesChanged signal with type `%s' does not match `(sa{sv}as)'", + g_variant_get_type_string (parameters)); + goto out; + } + + g_variant_get (parameters, + "(&s@a{sv}^a&s)", + &interface_name_for_signal, + &changed_properties, + &invalidated_properties); + + if (g_strcmp0 (interface_name_for_signal, proxy->priv->interface_name) != 0) + goto out; + + g_variant_iter_init (&iter, changed_properties); + while (g_variant_iter_next (&iter, "{sv}", &key, &value)) + { + g_hash_table_insert (proxy->priv->properties, + key, /* adopts string */ + value); /* adopts value */ + } + + /* emit signal */ + g_signal_emit (proxy, signals[PROPERTIES_CHANGED_SIGNAL], + 0, + changed_properties, + invalidated_properties); + + out: + if (changed_properties != NULL) + g_variant_unref (changed_properties); + g_free (invalidated_properties); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +g_dbus_proxy_constructed (GObject *object) +{ + if (G_OBJECT_CLASS (g_dbus_proxy_parent_class)->constructed != NULL) + G_OBJECT_CLASS (g_dbus_proxy_parent_class)->constructed (object); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +subscribe_to_signals (GDBusProxy *proxy) +{ + if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)) + { + /* subscribe to PropertiesChanged() */ + proxy->priv->properties_changed_subscriber_id = + g_dbus_connection_signal_subscribe (proxy->priv->connection, + proxy->priv->unique_bus_name, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + proxy->priv->object_path, + proxy->priv->interface_name, + on_properties_changed, + proxy, + NULL); + } + + if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS)) + { + /* subscribe to all signals for the object */ + proxy->priv->signals_subscriber_id = + g_dbus_connection_signal_subscribe (proxy->priv->connection, + proxy->priv->unique_bus_name, + proxy->priv->interface_name, + NULL, /* member */ + proxy->priv->object_path, + NULL, /* arg0 */ + on_signal_received, + proxy, + NULL); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +process_get_all_reply (GDBusProxy *proxy, + GVariant *result) +{ + GVariantIter iter; + GVariant *item; + + if (strcmp (g_variant_get_type_string (result), "(a{sv})") != 0) + { + g_warning ("Value for GetAll reply with type `%s' does not match `(a{sv})'", + g_variant_get_type_string (result)); + goto out; + } + + g_variant_iter_init (&iter, g_variant_get_child_value (result, 0)); + while ((item = g_variant_iter_next_value (&iter)) != NULL) + { + gchar *key; + GVariant *value; + + g_variant_get (item, + "{sv}", + &key, + &value); + //g_print ("got %s -> %s\n", key, g_variant_markup_print (value, FALSE, 0, 0)); + + g_hash_table_insert (proxy->priv->properties, + key, + value); /* steals value */ + } + out: + ; +} + +static gboolean +initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GDBusProxy *proxy = G_DBUS_PROXY (initable); + GVariant *result; + gboolean ret; + + ret = FALSE; + + subscribe_to_signals (proxy); + + if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)) + { + /* load all properties synchronously */ + result = g_dbus_connection_call_sync (proxy->priv->connection, + proxy->priv->unique_bus_name, + proxy->priv->object_path, + "org.freedesktop.DBus.Properties", + "GetAll", + g_variant_new ("(s)", proxy->priv->interface_name), + G_DBUS_CALL_FLAGS_NONE, + -1, /* timeout */ + cancellable, + error); + if (result == NULL) + goto out; + + process_get_all_reply (proxy, result); + + g_variant_unref (result); + } + + ret = TRUE; + + out: + proxy->priv->initialized = TRUE; + return ret; +} + +static void +initable_iface_init (GInitableIface *initable_iface) +{ + initable_iface->init = initable_init; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +get_all_cb (GDBusConnection *connection, + GAsyncResult *res, + gpointer user_data) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); + GVariant *result; + GError *error; + + error = NULL; + result = g_dbus_connection_call_finish (connection, + res, + &error); + if (result == NULL) + { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } + else + { + g_simple_async_result_set_op_res_gpointer (simple, + result, + (GDestroyNotify) g_variant_unref); + } + + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} + +static void +async_initable_init_async (GAsyncInitable *initable, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (initable); + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new (G_OBJECT (proxy), + callback, + user_data, + NULL); + + subscribe_to_signals (proxy); + + if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)) + { + /* load all properties asynchronously */ + g_dbus_connection_call (proxy->priv->connection, + proxy->priv->unique_bus_name, + proxy->priv->object_path, + "org.freedesktop.DBus.Properties", + "GetAll", + g_variant_new ("(s)", proxy->priv->interface_name), + G_DBUS_CALL_FLAGS_NONE, + -1, /* timeout */ + cancellable, + (GAsyncReadyCallback) get_all_cb, + simple); + } + else + { + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + } +} + +static gboolean +async_initable_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error) +{ + GDBusProxy *proxy = G_DBUS_PROXY (initable); + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + GVariant *result; + gboolean ret; + + ret = FALSE; + + result = g_simple_async_result_get_op_res_gpointer (simple); + if (result == NULL) + { + if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)) + { + g_simple_async_result_propagate_error (simple, error); + goto out; + } + } + else + { + process_get_all_reply (proxy, result); + } + + ret = TRUE; + + out: + proxy->priv->initialized = TRUE; + return ret; +} + +static void +async_initable_iface_init (GAsyncInitableIface *async_initable_iface) +{ + async_initable_iface->init_async = async_initable_init_async; + async_initable_iface->init_finish = async_initable_init_finish; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_proxy_new: + * @connection: A #GDBusConnection. + * @object_type: Either #G_TYPE_DBUS_PROXY or the #GType for the #GDBusProxy-derived type of proxy to create. + * @flags: Flags used when constructing the proxy. + * @info: A #GDBusInterfaceInfo specifying the minimal interface that @proxy conforms to or %NULL. + * @unique_bus_name: A unique bus name or %NULL if @connection is not a message bus connection. + * @object_path: An object path. + * @interface_name: A D-Bus interface name. + * @cancellable: A #GCancellable or %NULL. + * @callback: Callback function to invoke when the proxy is ready. + * @user_data: User data to pass to @callback. + * + * Creates a proxy for accessing @interface_name on the remote object at @object_path + * owned by @unique_bus_name at @connection and asynchronously loads D-Bus properties unless the + * #G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES flag is used. Connect to the + * #GDBusProxy::g-properties-changed signal to get notified about property changes. + * + * If the #G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS flag is not set, also sets up + * match rules for signals. Connect to the #GDBusProxy::g-signal signal + * to handle signals from the remote object. + * + * This is a failable asynchronous constructor - when the proxy is + * ready, @callback will be invoked and you can use + * g_dbus_proxy_new_finish() to get the result. + * + * See g_dbus_proxy_new_sync() and for a synchronous version of this constructor. + * + * Since: 2.26 + */ +void +g_dbus_proxy_new (GDBusConnection *connection, + GType object_type, + GDBusProxyFlags flags, + GDBusInterfaceInfo *info, + const gchar *unique_bus_name, + const gchar *object_path, + const gchar *interface_name, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); + g_return_if_fail (g_type_is_a (object_type, G_TYPE_DBUS_PROXY)); + g_return_if_fail ((unique_bus_name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) || + g_dbus_is_unique_name (unique_bus_name)); + g_return_if_fail (g_variant_is_object_path (object_path)); + g_return_if_fail (g_dbus_is_interface_name (interface_name)); + + g_async_initable_new_async (object_type, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + "g-flags", flags, + "g-interface-info", info, + "g-unique-bus-name", unique_bus_name, + "g-connection", connection, + "g-object-path", object_path, + "g-interface-name", interface_name, + NULL); +} + +/** + * g_dbus_proxy_new_finish: + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback function passed to g_dbus_proxy_new(). + * @error: Return location for error or %NULL. + * + * Finishes creating a #GDBusProxy. + * + * Returns: A #GDBusProxy or %NULL if @error is set. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusProxy * +g_dbus_proxy_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *object; + GObject *source_object; + + source_object = g_async_result_get_source_object (res); + g_assert (source_object != NULL); + + object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), + res, + error); + g_object_unref (source_object); + + if (object != NULL) + return G_DBUS_PROXY (object); + else + return NULL; +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_proxy_new_sync: + * @connection: A #GDBusConnection. + * @object_type: Either #G_TYPE_DBUS_PROXY or the #GType for the #GDBusProxy-derived type of proxy to create. + * @flags: Flags used when constructing the proxy. + * @info: A #GDBusInterfaceInfo specifying the minimal interface that @proxy conforms to or %NULL. + * @unique_bus_name: A unique bus name or %NULL if @connection is not a message bus connection. + * @object_path: An object path. + * @interface_name: A D-Bus interface name. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Creates a proxy for accessing @interface_name on the remote object at @object_path + * owned by @unique_bus_name at @connection and synchronously loads D-Bus properties unless the + * #G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES flag is used. + * + * If the #G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS flag is not set, also sets up + * match rules for signals. Connect to the #GDBusProxy::g-signal signal + * to handle signals from the remote object. + * + * This is a synchronous failable constructor. See g_dbus_proxy_new() + * and g_dbus_proxy_new_finish() for the asynchronous version. + * + * Returns: A #GDBusProxy or %NULL if error is set. Free with g_object_unref(). + * + * Since: 2.26 + */ +GDBusProxy * +g_dbus_proxy_new_sync (GDBusConnection *connection, + GType object_type, + GDBusProxyFlags flags, + GDBusInterfaceInfo *info, + const gchar *unique_bus_name, + const gchar *object_path, + const gchar *interface_name, + GCancellable *cancellable, + GError **error) +{ + GInitable *initable; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (g_type_is_a (object_type, G_TYPE_DBUS_PROXY), NULL); + g_return_val_if_fail ((unique_bus_name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) || + g_dbus_is_unique_name (unique_bus_name), NULL); + g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); + g_return_val_if_fail (g_dbus_is_interface_name (interface_name), NULL); + + initable = g_initable_new (object_type, + cancellable, + error, + "g-flags", flags, + "g-interface-info", info, + "g-unique-bus-name", unique_bus_name, + "g-connection", connection, + "g-object-path", object_path, + "g-interface-name", interface_name, + NULL); + if (initable != NULL) + return G_DBUS_PROXY (initable); + else + return NULL; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_dbus_proxy_get_connection: + * @proxy: A #GDBusProxy. + * + * Gets the connection @proxy is for. + * + * Returns: A #GDBusConnection owned by @proxy. Do not free. + * + * Since: 2.26 + */ +GDBusConnection * +g_dbus_proxy_get_connection (GDBusProxy *proxy) +{ + g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL); + return proxy->priv->connection; +} + +/** + * g_dbus_proxy_get_flags: + * @proxy: A #GDBusProxy. + * + * Gets the flags that @proxy was constructed with. + * + * Returns: Flags from the #GDBusProxyFlags enumeration. + * + * Since: 2.26 + */ +GDBusProxyFlags +g_dbus_proxy_get_flags (GDBusProxy *proxy) +{ + g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), 0); + return proxy->priv->flags; +} + +/** + * g_dbus_proxy_get_unique_bus_name: + * @proxy: A #GDBusProxy. + * + * Gets the unique bus name @proxy is for. + * + * Returns: A string owned by @proxy. Do not free. + * + * Since: 2.26 + */ +const gchar * +g_dbus_proxy_get_unique_bus_name (GDBusProxy *proxy) +{ + g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL); + return proxy->priv->unique_bus_name; +} + +/** + * g_dbus_proxy_get_object_path: + * @proxy: A #GDBusProxy. + * + * Gets the object path @proxy is for. + * + * Returns: A string owned by @proxy. Do not free. + * + * Since: 2.26 + */ +const gchar * +g_dbus_proxy_get_object_path (GDBusProxy *proxy) +{ + g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL); + return proxy->priv->object_path; +} + +/** + * g_dbus_proxy_get_interface_name: + * @proxy: A #GDBusProxy. + * + * Gets the D-Bus interface name @proxy is for. + * + * Returns: A string owned by @proxy. Do not free. + * + * Since: 2.26 + */ +const gchar * +g_dbus_proxy_get_interface_name (GDBusProxy *proxy) +{ + g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL); + return proxy->priv->interface_name; +} + +/** + * g_dbus_proxy_get_default_timeout: + * @proxy: A #GDBusProxy. + * + * Gets the timeout to use if -1 (specifying default timeout) is + * passed as @timeout_msec in the g_dbus_proxy_call() and + * g_dbus_proxy_call_sync() functions. + * + * See the #GDBusProxy:g-default-timeout property for more details. + * + * Returns: Timeout to use for @proxy. + * + * Since: 2.26 + */ +gint +g_dbus_proxy_get_default_timeout (GDBusProxy *proxy) +{ + g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), -1); + return proxy->priv->timeout_msec; +} + +/** + * g_dbus_proxy_set_default_timeout: + * @proxy: A #GDBusProxy. + * @timeout_msec: Timeout in milliseconds. + * + * Sets the timeout to use if -1 (specifying default timeout) is + * passed as @timeout_msec in the g_dbus_proxy_call() and + * g_dbus_proxy_call_sync() functions. + * + * See the #GDBusProxy:g-default-timeout property for more details. + * + * Since: 2.26 + */ +void +g_dbus_proxy_set_default_timeout (GDBusProxy *proxy, + gint timeout_msec) +{ + g_return_if_fail (G_IS_DBUS_PROXY (proxy)); + g_return_if_fail (timeout_msec == -1 || timeout_msec >= 0); + + /* TODO: locking? */ + if (proxy->priv->timeout_msec != timeout_msec) + { + proxy->priv->timeout_msec = timeout_msec; + g_object_notify (G_OBJECT (proxy), "g-default-timeout"); + } +} + +/** + * g_dbus_proxy_get_interface_info: + * @proxy: A #GDBusProxy + * + * Returns the #GDBusInterfaceInfo, if any, specifying the minimal + * interface that @proxy conforms to. + * + * See the #GDBusProxy:g-interface-info property for more details. + * + * Returns: A #GDBusInterfaceInfo or %NULL. Do not unref the returned + * object, it is owned by @proxy. + * + * Since: 2.26 + */ +GDBusInterfaceInfo * +g_dbus_proxy_get_interface_info (GDBusProxy *proxy) +{ + g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL); + return proxy->priv->expected_interface; +} + +/** + * g_dbus_proxy_set_interface_info: + * @proxy: A #GDBusProxy + * @info: Minimum interface this proxy conforms to or %NULL to unset. + * + * Ensure that interactions with @proxy conform to the given + * interface. For example, when completing a method call, if the type + * signature of the message isn't what's expected, the given #GError + * is set. Signals that have a type signature mismatch are simply + * dropped. + * + * See the #GDBusProxy:g-interface-info property for more details. + * + * Since: 2.26 + */ +void +g_dbus_proxy_set_interface_info (GDBusProxy *proxy, + GDBusInterfaceInfo *info) +{ + g_return_if_fail (G_IS_DBUS_PROXY (proxy)); + if (proxy->priv->expected_interface != NULL) + g_dbus_interface_info_unref (proxy->priv->expected_interface); + proxy->priv->expected_interface = info != NULL ? g_dbus_interface_info_ref (info) : NULL; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +maybe_split_method_name (const gchar *method_name, + gchar **out_interface_name, + const gchar **out_method_name) +{ + gboolean was_split; + + was_split = FALSE; + g_assert (out_interface_name != NULL); + g_assert (out_method_name != NULL); + *out_interface_name = NULL; + *out_method_name = NULL; + + if (strchr (method_name, '.') != NULL) + { + gchar *p; + gchar *last_dot; + + p = g_strdup (method_name); + last_dot = strrchr (p, '.'); + *last_dot = '\0'; + + *out_interface_name = p; + *out_method_name = last_dot + 1; + + was_split = TRUE; + } + + return was_split; +} + + +static void +reply_cb (GDBusConnection *connection, + GAsyncResult *res, + gpointer user_data) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); + GVariant *value; + GError *error; + + error = NULL; + value = g_dbus_connection_call_finish (connection, + res, + &error); + if (error != NULL) + { + g_simple_async_result_set_from_error (simple, + error); + g_error_free (error); + } + else + { + g_simple_async_result_set_op_res_gpointer (simple, + value, + (GDestroyNotify) g_variant_unref); + } + + /* no need to complete in idle since the method GDBusConnection already does */ + g_simple_async_result_complete (simple); +} + +static const GDBusMethodInfo * +lookup_method_info_or_warn (GDBusProxy *proxy, + const gchar *method_name) +{ + const GDBusMethodInfo *info; + + if (proxy->priv->expected_interface == NULL) + return NULL; + + info = g_dbus_interface_info_lookup_method (proxy->priv->expected_interface, method_name); + if (info == NULL) + { + g_warning ("Trying to invoke method %s which isn't in expected interface %s", + method_name, proxy->priv->expected_interface->name); + } + + return info; +} + +static gboolean +validate_method_return (const char *method_name, + GVariant *value, + const GDBusMethodInfo *expected_method_info, + GError **error) +{ + const gchar *type_string; + gchar *signature; + gboolean ret; + + ret = TRUE; + signature = NULL; + + if (value == NULL || expected_method_info == NULL) + goto out; + + /* Shouldn't happen... */ + if (g_variant_classify (value) != G_VARIANT_CLASS_TUPLE) + goto out; + + type_string = g_variant_get_type_string (value); + signature = _g_dbus_compute_complete_signature (expected_method_info->out_args, TRUE); + if (g_strcmp0 (type_string, signature) != 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Method `%s' returned signature `%s', but expected `%s'"), + method_name, + type_string, + signature); + ret = FALSE; + } + + out: + g_free (signature); + return ret; +} + +/** + * g_dbus_proxy_call: + * @proxy: A #GDBusProxy. + * @method_name: Name of method to invoke. + * @parameters: A #GVariant tuple with parameters for the signal or %NULL if not passing parameters. + * @flags: Flags from the #GDBusCallFlags enumeration. + * @timeout_msec: The timeout in milliseconds or -1 to use the proxy default timeout. + * @cancellable: A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't + * care about the result of the method invocation. + * @user_data: The data to pass to @callback. + * + * Asynchronously invokes the @method_name method on @proxy. + * + * If @method_name contains any dots, then @name is split into interface and + * method name parts. This allows using @proxy for invoking methods on + * other interfaces. + * + * If the #GDBusConnection associated with @proxy is closed then + * the operation will fail with %G_IO_ERROR_CLOSED. If + * @cancellable is canceled, the operation will fail with + * %G_IO_ERROR_CANCELLED. If @parameters contains a value not + * compatible with the D-Bus protocol, the operation fails with + * %G_IO_ERROR_INVALID_ARGUMENT. + * + * If the @parameters #GVariant is floating, it is consumed. This allows + * convenient 'inline' use of g_variant_new(), e.g.: + * |[ + * g_dbus_proxy_call (proxy, + * "TwoStrings", + * g_variant_new ("(ss)", + * "Thing One", + * "Thing Two"), + * G_DBUS_CALL_FLAGS_NONE, + * -1, + * NULL, + * (GAsyncReadyCallback) two_strings_done, + * &data); + * ]| + * + * This is an asynchronous method. When the operation is finished, + * @callback will be invoked in the + * thread-default + * main loop of the thread you are calling this method from. + * You can then call g_dbus_proxy_call_finish() to get the result of + * the operation. See g_dbus_proxy_call_sync() for the synchronous + * version of this method. + * + * Since: 2.26 + */ +void +g_dbus_proxy_call (GDBusProxy *proxy, + const gchar *method_name, + GVariant *parameters, + GDBusCallFlags flags, + gint timeout_msec, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + gboolean was_split; + gchar *split_interface_name; + const gchar *split_method_name; + const GDBusMethodInfo *expected_method_info; + const gchar *target_method_name; + const gchar *target_interface_name; + + g_return_if_fail (G_IS_DBUS_PROXY (proxy)); + g_return_if_fail (g_dbus_is_member_name (method_name) || g_dbus_is_interface_name (method_name)); + g_return_if_fail (parameters == NULL || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE)); + g_return_if_fail (timeout_msec == -1 || timeout_msec >= 0); + + simple = g_simple_async_result_new (G_OBJECT (proxy), + callback, + user_data, + g_dbus_proxy_call); + + was_split = maybe_split_method_name (method_name, &split_interface_name, &split_method_name); + target_method_name = was_split ? split_method_name : method_name; + target_interface_name = was_split ? split_interface_name : proxy->priv->interface_name; + + g_object_set_data_full (G_OBJECT (simple), "-gdbus-proxy-method-name", g_strdup (target_method_name), g_free); + + /* Just warn here */ + expected_method_info = lookup_method_info_or_warn (proxy, target_method_name); + + g_dbus_connection_call (proxy->priv->connection, + proxy->priv->unique_bus_name, + proxy->priv->object_path, + target_interface_name, + target_method_name, + parameters, + flags, + timeout_msec == -1 ? proxy->priv->timeout_msec : timeout_msec, + cancellable, + (GAsyncReadyCallback) reply_cb, + simple); + + g_free (split_interface_name); +} + +/** + * g_dbus_proxy_call_finish: + * @proxy: A #GDBusProxy. + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_proxy_call(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with g_dbus_proxy_call(). + * + * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with + * return values. Free with g_variant_unref(). + * + * Since: 2.26 + */ +GVariant * +g_dbus_proxy_call_finish (GDBusProxy *proxy, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + GVariant *value; + const char *method_name; + const GDBusMethodInfo *expected_method_info; + + g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_dbus_proxy_call); + + value = NULL; + + if (g_simple_async_result_propagate_error (simple, error)) + goto out; + + value = g_simple_async_result_get_op_res_gpointer (simple); + method_name = g_object_get_data (G_OBJECT (simple), "-gdbus-proxy-method-name"); + + /* We may not have a method name for internally-generated proxy calls like GetAll */ + if (value && method_name && proxy->priv->expected_interface) + { + expected_method_info = g_dbus_interface_info_lookup_method (proxy->priv->expected_interface, method_name); + if (!validate_method_return (method_name, value, expected_method_info, error)) + { + g_variant_unref (value); + value = NULL; + } + } + + out: + return value; +} + +/** + * g_dbus_proxy_call_sync: + * @proxy: A #GDBusProxy. + * @method_name: Name of method to invoke. + * @parameters: A #GVariant tuple with parameters for the signal or %NULL if not passing parameters. + * @flags: Flags from the #GDBusCallFlags enumeration. + * @timeout_msec: The timeout in milliseconds or -1 to use the proxy default timeout. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the @method_name method on @proxy. + * + * If @method_name contains any dots, then @name is split into interface and + * method name parts. This allows using @proxy for invoking methods on + * other interfaces. + * + * If the #GDBusConnection associated with @proxy is disconnected then + * the operation will fail with %G_IO_ERROR_CLOSED. If + * @cancellable is canceled, the operation will fail with + * %G_IO_ERROR_CANCELLED. If @parameters contains a value not + * compatible with the D-Bus protocol, the operation fails with + * %G_IO_ERROR_INVALID_ARGUMENT. + * + * If the @parameters #GVariant is floating, it is consumed. This allows + * convenient 'inline' use of g_variant_new(), e.g.: + * |[ + * g_dbus_proxy_call_sync (proxy, + * "TwoStrings", + * g_variant_new ("(ss)", + * "Thing One", + * "Thing Two"), + * G_DBUS_CALL_FLAGS_NONE, + * -1, + * NULL, + * &error); + * ]| + * + * The calling thread is blocked until a reply is received. See + * g_dbus_proxy_call() for the asynchronous version of this + * method. + * + * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with + * return values. Free with g_variant_unref(). + * + * Since: 2.26 + */ +GVariant * +g_dbus_proxy_call_sync (GDBusProxy *proxy, + const gchar *method_name, + GVariant *parameters, + GDBusCallFlags flags, + gint timeout_msec, + GCancellable *cancellable, + GError **error) +{ + GVariant *ret; + gboolean was_split; + gchar *split_interface_name; + const gchar *split_method_name; + const GDBusMethodInfo *expected_method_info; + const gchar *target_method_name; + const gchar *target_interface_name; + + g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL); + g_return_val_if_fail (g_dbus_is_member_name (method_name) || g_dbus_is_interface_name (method_name), NULL); + g_return_val_if_fail (parameters == NULL || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), NULL); + g_return_val_if_fail (timeout_msec == -1 || timeout_msec >= 0, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + was_split = maybe_split_method_name (method_name, &split_interface_name, &split_method_name); + target_method_name = was_split ? split_method_name : method_name; + target_interface_name = was_split ? split_interface_name : proxy->priv->interface_name; + + if (proxy->priv->expected_interface) + { + expected_method_info = g_dbus_interface_info_lookup_method (proxy->priv->expected_interface, target_method_name); + if (expected_method_info == NULL) + { + g_warning ("Trying to invoke method `%s' which isn't in expected interface `%s'", + target_method_name, + target_interface_name); + } + } + else + { + expected_method_info = NULL; + } + + ret = g_dbus_connection_call_sync (proxy->priv->connection, + proxy->priv->unique_bus_name, + proxy->priv->object_path, + target_interface_name, + target_method_name, + parameters, + flags, + timeout_msec == -1 ? proxy->priv->timeout_msec : timeout_msec, + cancellable, + error); + if (!validate_method_return (target_method_name, ret, expected_method_info, error)) + { + g_variant_unref (ret); + ret = NULL; + } + + g_free (split_interface_name); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#define __G_DBUS_PROXY_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusproxy.h b/gio/gdbusproxy.h new file mode 100644 index 000000000..48688531e --- /dev/null +++ b/gio/gdbusproxy.h @@ -0,0 +1,152 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_PROXY_H__ +#define __G_DBUS_PROXY_H__ + +#include +#include + +G_BEGIN_DECLS + +#define G_TYPE_DBUS_PROXY (g_dbus_proxy_get_type ()) +#define G_DBUS_PROXY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_PROXY, GDBusProxy)) +#define G_DBUS_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_PROXY, GDBusProxyClass)) +#define G_DBUS_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_PROXY, GDBusProxyClass)) +#define G_IS_DBUS_PROXY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_PROXY)) +#define G_IS_DBUS_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_PROXY)) + +typedef struct _GDBusProxyClass GDBusProxyClass; +typedef struct _GDBusProxyPrivate GDBusProxyPrivate; + +/** + * GDBusProxy: + * + * The #GDBusProxy structure contains only private data and + * should only be accessed using the provided API. + * + * Since: 2.26 + */ +struct _GDBusProxy +{ + /*< private >*/ + GObject parent_instance; + GDBusProxyPrivate *priv; +}; + +/** + * GDBusProxyClass: + * @g_properties_changed: Signal class handler for the #GDBusProxy::g-properties-changed signal. + * @g_signal: Signal class handler for the #GDBusProxy::g-signal signal. + * + * Class structure for #GDBusProxy. + * + * Since: 2.26 + */ +struct _GDBusProxyClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + /* Signals */ + void (*g_properties_changed) (GDBusProxy *proxy, + GVariant *changed_properties, + const gchar* const *invalidated_properties); + void (*g_signal) (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters); + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); +}; + +GType g_dbus_proxy_get_type (void) G_GNUC_CONST; +void g_dbus_proxy_new (GDBusConnection *connection, + GType object_type, + GDBusProxyFlags flags, + GDBusInterfaceInfo *info, + const gchar *unique_bus_name, + const gchar *object_path, + const gchar *interface_name, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GDBusProxy *g_dbus_proxy_new_finish (GAsyncResult *res, + GError **error); +GDBusProxy *g_dbus_proxy_new_sync (GDBusConnection *connection, + GType object_type, + GDBusProxyFlags flags, + GDBusInterfaceInfo *info, + const gchar *unique_bus_name, + const gchar *object_path, + const gchar *interface_name, + GCancellable *cancellable, + GError **error); +GDBusConnection *g_dbus_proxy_get_connection (GDBusProxy *proxy); +GDBusProxyFlags g_dbus_proxy_get_flags (GDBusProxy *proxy); +const gchar *g_dbus_proxy_get_unique_bus_name (GDBusProxy *proxy); +const gchar *g_dbus_proxy_get_object_path (GDBusProxy *proxy); +const gchar *g_dbus_proxy_get_interface_name (GDBusProxy *proxy); +gint g_dbus_proxy_get_default_timeout (GDBusProxy *proxy); +void g_dbus_proxy_set_default_timeout (GDBusProxy *proxy, + gint timeout_msec); +GDBusInterfaceInfo *g_dbus_proxy_get_interface_info (GDBusProxy *proxy); +void g_dbus_proxy_set_interface_info (GDBusProxy *proxy, + GDBusInterfaceInfo *info); +GVariant *g_dbus_proxy_get_cached_property (GDBusProxy *proxy, + const gchar *property_name); +void g_dbus_proxy_set_cached_property (GDBusProxy *proxy, + const gchar *property_name, + GVariant *value); +gchar **g_dbus_proxy_get_cached_property_names (GDBusProxy *proxy); +void g_dbus_proxy_call (GDBusProxy *proxy, + const gchar *method_name, + GVariant *parameters, + GDBusCallFlags flags, + gint timeout_msec, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GVariant *g_dbus_proxy_call_finish (GDBusProxy *proxy, + GAsyncResult *res, + GError **error); +GVariant *g_dbus_proxy_call_sync (GDBusProxy *proxy, + const gchar *method_name, + GVariant *parameters, + GDBusCallFlags flags, + gint timeout_msec, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* __G_DBUS_PROXY_H__ */ diff --git a/gio/gdbusproxywatching.c b/gio/gdbusproxywatching.c new file mode 100644 index 000000000..2edcb82a4 --- /dev/null +++ b/gio/gdbusproxywatching.c @@ -0,0 +1,490 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include + +#include "gdbusutils.h" +#include "gdbusconnection.h" +#include "gdbusnamewatching.h" +#include "gdbusproxywatching.h" +#include "gdbuserror.h" +#include "gdbusprivate.h" +#include "gdbusproxy.h" +#include "gdbusnamewatching.h" +#include "gcancellable.h" + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusproxywatching + * @title: Watching Proxies + * @short_description: Simple API for watching proxies + * @include: gio/gio.h + * + * Convenience API for watching bus proxies. + * + * Simple application watching a proxyFIXME: MISSING XINCLUDE CONTENT + */ + +/* ---------------------------------------------------------------------------------------------------- */ + +G_LOCK_DEFINE_STATIC (lock); + +static guint next_global_id = 1; +static GHashTable *map_id_to_client = NULL; + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + guint id; + GBusProxyAppearedCallback proxy_appeared_handler; + GBusProxyVanishedCallback proxy_vanished_handler; + gpointer user_data; + GDestroyNotify user_data_free_func; + GMainContext *main_context; + + gchar *name; + gchar *name_owner; + GDBusConnection *connection; + guint name_watcher_id; + + GCancellable *cancellable; + + gchar *object_path; + gchar *interface_name; + GType interface_type; + GDBusProxyFlags proxy_flags; + GDBusProxy *proxy; + + gboolean initial_construction; +} Client; + +static void +client_unref (Client *client) +{ + /* ensure we're only called from g_bus_unwatch_proxy */ + g_assert (client->name_watcher_id == 0); + + g_free (client->name_owner); + if (client->connection != NULL) + g_object_unref (client->connection); + if (client->proxy != NULL) + g_object_unref (client->proxy); + + g_free (client->name); + g_free (client->object_path); + g_free (client->interface_name); + + if (client->main_context != NULL) + g_main_context_unref (client->main_context); + + if (client->user_data_free_func != NULL) + client->user_data_free_func (client->user_data); + g_free (client); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +proxy_constructed_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + Client *client = user_data; + GDBusProxy *proxy; + GError *error; + + error = NULL; + proxy = g_dbus_proxy_new_finish (res, &error); + if (proxy == NULL) + { + /* g_warning ("error while constructing proxy: %s", error->message); */ + g_error_free (error); + + /* handle initial construction, send out vanished if the name + * is there but we constructing a proxy fails + */ + if (client->initial_construction) + { + if (client->proxy_vanished_handler != NULL) + { + client->proxy_vanished_handler (client->connection, + client->name, + client->user_data); + } + client->initial_construction = FALSE; + } + } + else + { + g_assert (client->proxy == NULL); + g_assert (client->cancellable != NULL); + client->proxy = G_DBUS_PROXY (proxy); + + g_object_unref (client->cancellable); + client->cancellable = NULL; + + /* perform callback */ + if (client->proxy_appeared_handler != NULL) + { + client->proxy_appeared_handler (client->connection, + client->name, + client->name_owner, + client->proxy, + client->user_data); + } + client->initial_construction = FALSE; + } +} + +static void +on_name_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + Client *client = user_data; + + //g_debug ("\n\nname appeared (owner `%s')", name_owner); + + /* invariants */ + g_assert (client->name_owner == NULL); + g_assert (client->connection == NULL); + g_assert (client->cancellable == NULL); + + client->name_owner = g_strdup (name_owner); + client->connection = g_object_ref (connection); + client->cancellable = g_cancellable_new (); + + g_dbus_proxy_new (client->connection, + client->interface_type, + client->proxy_flags, + NULL, /* GDBusInterfaceInfo */ + client->name_owner, + client->object_path, + client->interface_name, + client->cancellable, + proxy_constructed_cb, + client); +} + +static void +on_name_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + Client *client = user_data; + + /*g_debug ("\n\nname vanished");*/ + + g_free (client->name_owner); + if (client->connection != NULL) + g_object_unref (client->connection); + client->name_owner = NULL; + client->connection = NULL; + + /* free the proxy if we have it */ + if (client->proxy != NULL) + { + g_assert (client->cancellable == NULL); + + g_object_unref (client->proxy); + client->proxy = NULL; + + /* if we have the proxy, it means we last sent out a 'appeared' + * callback - so send out a 'vanished' callback + */ + if (client->proxy_vanished_handler != NULL) + { + client->proxy_vanished_handler (client->connection, + client->name, + client->user_data); + } + client->initial_construction = FALSE; + } + else + { + /* otherwise cancel construction of the proxy if applicable */ + if (client->cancellable != NULL) + { + g_cancellable_cancel (client->cancellable); + g_object_unref (client->cancellable); + client->cancellable = NULL; + } + else + { + /* handle initial construction, send out vanished if + * the name isn't there + */ + if (client->initial_construction) + { + if (client->proxy_vanished_handler != NULL) + { + client->proxy_vanished_handler (client->connection, + client->name, + client->user_data); + } + client->initial_construction = FALSE; + } + } + } +} + +/** + * g_bus_watch_proxy: + * @bus_type: The type of bus to watch a name on. + * @name: The name (well-known or unique) to watch. + * @flags: Flags from the #GBusNameWatcherFlags enumeration. + * @object_path: The object path of the remote object to watch. + * @interface_name: The D-Bus interface name for the proxy. + * @interface_type: The #GType for the kind of proxy to create. This must be a #GDBusProxy derived type. + * @proxy_flags: Flags from #GDBusProxyFlags to use when constructing the proxy. + * @proxy_appeared_handler: Handler to invoke when @name is known to exist and the + * requested proxy is available. + * @proxy_vanished_handler: Handler to invoke when @name is known to not exist + * and the previously created proxy is no longer available. + * @user_data: User data to pass to handlers. + * @user_data_free_func: Function for freeing @user_data or %NULL. + * + * Starts watching a remote object at @object_path owned by @name on + * the bus specified by @bus_type. When the object is available, a + * #GDBusProxy (or derived class cf. @interface_type) instance is + * constructed for the @interface_name D-Bus interface and then + * @proxy_appeared_handler will be called when the proxy is ready and + * all properties have been loaded. When @name vanishes, + * @proxy_vanished_handler is called. + * + * This function makes it very simple to write applications that wants + * to watch a well-known remote object on a well-known name, see . Basically, the application simply + * starts using the proxy when @proxy_appeared_handler is called and + * stops using it when @proxy_vanished_handler is called. Callbacks + * will be invoked in the thread-default main + * loop of the thread you are calling this function from. + * + * Applications typically use this function to watch the + * manager object of a well-known name. Upon acquiring + * a proxy for the manager object, applications typically construct + * additional proxies in response to the result of enumeration methods + * on the manager object. + * + * Many of the comments that apply to g_bus_watch_name() also apply + * here. For example, you are guaranteed that one of the handlers will + * be invoked (on the main thread) after calling this function and + * also that the two handlers alternate. When you are done watching the + * proxy, just call g_bus_unwatch_proxy(). + * + * Returns: An identifier (never 0) that can be used with + * g_bus_unwatch_proxy() to stop watching the remote object. + * + * Since: 2.26 + */ +guint +g_bus_watch_proxy (GBusType bus_type, + const gchar *name, + GBusNameWatcherFlags flags, + const gchar *object_path, + const gchar *interface_name, + GType interface_type, + GDBusProxyFlags proxy_flags, + GBusProxyAppearedCallback proxy_appeared_handler, + GBusProxyVanishedCallback proxy_vanished_handler, + gpointer user_data, + GDestroyNotify user_data_free_func) +{ + Client *client; + + g_return_val_if_fail (g_dbus_is_name (name), 0); + g_return_val_if_fail (g_variant_is_object_path (object_path), 0); + g_return_val_if_fail (g_dbus_is_interface_name (interface_name), 0); + g_return_val_if_fail (g_type_is_a (interface_type, G_TYPE_DBUS_PROXY), 0); + + G_LOCK (lock); + + client = g_new0 (Client, 1); + client->id = next_global_id++; /* TODO: uh oh, handle overflow */ + client->name = g_strdup (name); + client->proxy_appeared_handler = proxy_appeared_handler; + client->proxy_vanished_handler = proxy_vanished_handler; + client->user_data = user_data; + client->user_data_free_func = user_data_free_func; + client->main_context = g_main_context_get_thread_default (); + if (client->main_context != NULL) + g_main_context_ref (client->main_context); + client->name_watcher_id = g_bus_watch_name (bus_type, + name, + flags, + on_name_appeared, + on_name_vanished, + client, + NULL); + + client->object_path = g_strdup (object_path); + client->interface_name = g_strdup (interface_name); + client->interface_type = interface_type; + client->proxy_flags = proxy_flags; + client->initial_construction = TRUE; + + if (map_id_to_client == NULL) + { + map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal); + } + g_hash_table_insert (map_id_to_client, + GUINT_TO_POINTER (client->id), + client); + + G_UNLOCK (lock); + + return client->id; +} + +/** + * g_bus_watch_proxy_on_connection: + * @connection: A #GDBusConnection that is not closed. + * @name: The name (well-known or unique) to watch. + * @flags: Flags from the #GBusNameWatcherFlags enumeration. + * @object_path: The object path of the remote object to watch. + * @interface_name: The D-Bus interface name for the proxy. + * @interface_type: The #GType for the kind of proxy to create. This must be a #GDBusProxy derived type. + * @proxy_flags: Flags from #GDBusProxyFlags to use when constructing the proxy. + * @proxy_appeared_handler: Handler to invoke when @name is known to exist and the + * requested proxy is available. + * @proxy_vanished_handler: Handler to invoke when @name is known to not exist + * and the previously created proxy is no longer available. + * @user_data: User data to pass to handlers. + * @user_data_free_func: Function for freeing @user_data or %NULL. + * + * Like g_bus_watch_proxy() but takes a #GDBusConnection instead of a + * #GBusType. + * + * Returns: An identifier (never 0) that can be used with + * g_bus_unwatch_proxy() to stop watching the remote object. + * + * Since: 2.26 + */ +guint +g_bus_watch_proxy_on_connection (GDBusConnection *connection, + const gchar *name, + GBusNameWatcherFlags flags, + const gchar *object_path, + const gchar *interface_name, + GType interface_type, + GDBusProxyFlags proxy_flags, + GBusProxyAppearedCallback proxy_appeared_handler, + GBusProxyVanishedCallback proxy_vanished_handler, + gpointer user_data, + GDestroyNotify user_data_free_func) +{ + Client *client; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); + g_return_val_if_fail (g_dbus_is_name (name), 0); + g_return_val_if_fail (g_variant_is_object_path (object_path), 0); + g_return_val_if_fail (g_dbus_is_interface_name (interface_name), 0); + g_return_val_if_fail (g_type_is_a (interface_type, G_TYPE_DBUS_PROXY), 0); + + G_LOCK (lock); + + client = g_new0 (Client, 1); + client->id = next_global_id++; /* TODO: uh oh, handle overflow */ + client->name = g_strdup (name); + client->proxy_appeared_handler = proxy_appeared_handler; + client->proxy_vanished_handler = proxy_vanished_handler; + client->user_data = user_data; + client->user_data_free_func = user_data_free_func; + client->main_context = g_main_context_get_thread_default (); + if (client->main_context != NULL) + g_main_context_ref (client->main_context); + client->name_watcher_id = g_bus_watch_name_on_connection (connection, + name, + flags, + on_name_appeared, + on_name_vanished, + client, + NULL); + + client->object_path = g_strdup (object_path); + client->interface_name = g_strdup (interface_name); + client->interface_type = interface_type; + client->proxy_flags = proxy_flags; + client->initial_construction = TRUE; + + if (map_id_to_client == NULL) + { + map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal); + } + g_hash_table_insert (map_id_to_client, + GUINT_TO_POINTER (client->id), + client); + + G_UNLOCK (lock); + + return client->id; +} + + +/** + * g_bus_unwatch_proxy: + * @watcher_id: An identifier obtained from g_bus_watch_proxy() + * + * Stops watching proxy. + * + * Since: 2.26 + */ +void +g_bus_unwatch_proxy (guint watcher_id) +{ + Client *client; + + g_return_if_fail (watcher_id > 0); + + client = NULL; + + G_LOCK (lock); + if (watcher_id == 0 || + map_id_to_client == NULL || + (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL) + { + g_warning ("Invalid id %d passed to g_bus_unwatch_proxy()", watcher_id); + goto out; + } + + g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id))); + + out: + G_UNLOCK (lock); + + if (client != NULL) + { + g_bus_unwatch_name (client->name_watcher_id); + client->name_watcher_id = 0; + client_unref (client); + } +} + +#define __G_DBUS_PROXY_WATCHING_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusproxywatching.h b/gio/gdbusproxywatching.h new file mode 100644 index 000000000..37c88dd35 --- /dev/null +++ b/gio/gdbusproxywatching.h @@ -0,0 +1,92 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_PROXY_WATCHING_H__ +#define __G_DBUS_PROXY_WATCHING_H__ + +#include + +G_BEGIN_DECLS + +/** + * GBusProxyAppearedCallback: + * @connection: The #GDBusConnection the proxy is being watched on. + * @name: The name being watched. + * @name_owner: Unique name of the owner of the name being watched. + * @proxy: A #GDBusProxy (or derived) instance with all properties loaded. + * @user_data: User data passed to g_bus_watch_proxy(). + * + * Invoked when the proxy being watched is ready for use - the passed + * @proxy object is valid until the #GBusProxyVanishedCallback + * callback is invoked. + * + * Since: 2.26 + */ +typedef void (*GBusProxyAppearedCallback) (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + GDBusProxy *proxy, + gpointer user_data); + +/** + * GBusProxyVanishedCallback: + * @connection: The #GDBusConnection the proxy is being watched on. + * @name: The name being watched. + * @user_data: User data passed to g_bus_watch_proxy(). + * + * Invoked when the proxy being watched has vanished. The #GDBusProxy + * object passed in the #GBusProxyAppearedCallback callback is no + * longer valid. + * + * Since: 2.26 + */ +typedef void (*GBusProxyVanishedCallback) (GDBusConnection *connection, + const gchar *name, + gpointer user_data); + +guint g_bus_watch_proxy (GBusType bus_type, + const gchar *name, + GBusNameWatcherFlags flags, + const gchar *object_path, + const gchar *interface_name, + GType interface_type, + GDBusProxyFlags proxy_flags, + GBusProxyAppearedCallback proxy_appeared_handler, + GBusProxyVanishedCallback proxy_vanished_handler, + gpointer user_data, + GDestroyNotify user_data_free_func); +guint g_bus_watch_proxy_on_connection (GDBusConnection *connection, + const gchar *name, + GBusNameWatcherFlags flags, + const gchar *object_path, + const gchar *interface_name, + GType interface_type, + GDBusProxyFlags proxy_flags, + GBusProxyAppearedCallback proxy_appeared_handler, + GBusProxyVanishedCallback proxy_vanished_handler, + gpointer user_data, + GDestroyNotify user_data_free_func); +void g_bus_unwatch_proxy (guint watcher_id); + +G_END_DECLS + +#endif /* __G_DBUS_PROXY_WATCHING_H__ */ diff --git a/gio/gdbusserver.c b/gio/gdbusserver.c new file mode 100644 index 000000000..b6bd931cf --- /dev/null +++ b/gio/gdbusserver.c @@ -0,0 +1,1068 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include +#include +#include + +#include "giotypes.h" +#include "gdbusaddress.h" +#include "gdbusutils.h" +#include "gdbusconnection.h" +#include "gdbusserver.h" +#include "gioenumtypes.h" +#include "gdbusprivate.h" +#include "gdbusauthobserver.h" +#include "gio-marshal.h" +#include "ginitable.h" +#include "gsocketservice.h" + +#ifdef G_OS_UNIX +#include +#include "gunixsocketaddress.h" +#endif + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusserver + * @short_description: Helper for accepting connections + * @include: gio/gio.h + * + * #GDBusServer is a helper for listening to and accepting D-Bus + * connections. + * + * D-Bus peer-to-peer exampleFIXME: MISSING XINCLUDE CONTENT + */ + +struct _GDBusServerPrivate +{ + GDBusServerFlags flags; + gchar *address; + gchar *guid; + + guchar *nonce; + gchar *nonce_file; + + gchar *client_address; + + GSocketListener *listener; + gboolean is_using_listener; + + /* The result of g_main_context_get_thread_default() when the object + * was created (the GObject _init() function) - this is used for delivery + * of the :new-connection GObject signal. + */ + GMainContext *main_context_at_construction; + + gboolean active; + + GDBusAuthObserver *authentication_observer; +}; + +enum +{ + PROP_0, + PROP_ADDRESS, + PROP_CLIENT_ADDRESS, + PROP_FLAGS, + PROP_GUID, + PROP_ACTIVE, + PROP_AUTHENTICATION_OBSERVER, +}; + +enum +{ + NEW_CONNECTION_SIGNAL, + LAST_SIGNAL, +}; + +guint _signals[LAST_SIGNAL] = {0}; + +static void initable_iface_init (GInitableIface *initable_iface); + +G_DEFINE_TYPE_WITH_CODE (GDBusServer, g_dbus_server, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init) + ); + +static void +g_dbus_server_finalize (GObject *object) +{ + GDBusServer *server = G_DBUS_SERVER (object); + + if (server->priv->authentication_observer != NULL) + g_object_unref (server->priv->authentication_observer); + + if (server->priv->listener != NULL) + g_object_unref (server->priv->listener); + + g_free (server->priv->address); + g_free (server->priv->guid); + g_free (server->priv->client_address); + if (server->priv->nonce != NULL) + { + memset (server->priv->nonce, '\0', 16); + g_free (server->priv->nonce); + } + /* we could unlink the nonce file but I don't + * think it's really worth the effort/risk + */ + g_free (server->priv->nonce_file); + + if (server->priv->main_context_at_construction != NULL) + g_main_context_unref (server->priv->main_context_at_construction); + + G_OBJECT_CLASS (g_dbus_server_parent_class)->finalize (object); +} + +static void +g_dbus_server_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GDBusServer *server = G_DBUS_SERVER (object); + + switch (prop_id) + { + case PROP_FLAGS: + g_value_set_flags (value, server->priv->flags); + break; + + case PROP_GUID: + g_value_set_string (value, server->priv->guid); + break; + + case PROP_ADDRESS: + g_value_set_string (value, server->priv->address); + break; + + case PROP_CLIENT_ADDRESS: + g_value_set_string (value, server->priv->client_address); + break; + + case PROP_ACTIVE: + g_value_set_boolean (value, server->priv->active); + break; + + case PROP_AUTHENTICATION_OBSERVER: + g_value_set_object (value, server->priv->authentication_observer); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_dbus_server_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GDBusServer *server = G_DBUS_SERVER (object); + + switch (prop_id) + { + case PROP_FLAGS: + server->priv->flags = g_value_get_flags (value); + break; + + case PROP_GUID: + server->priv->guid = g_value_dup_string (value); + break; + + case PROP_ADDRESS: + server->priv->address = g_value_dup_string (value); + break; + + case PROP_AUTHENTICATION_OBSERVER: + server->priv->authentication_observer = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_dbus_server_class_init (GDBusServerClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_dbus_server_finalize; + gobject_class->set_property = g_dbus_server_set_property; + gobject_class->get_property = g_dbus_server_get_property; + + /** + * GDBusServer:flags: + * + * Flags from the #GDBusServerFlags enumeration. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_FLAGS, + g_param_spec_flags ("flags", + P_("Flags"), + P_("Flags for the server"), + G_TYPE_DBUS_SERVER_FLAGS, + G_DBUS_SERVER_FLAGS_NONE, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusServer:guid: + * + * The guid of the server. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_GUID, + g_param_spec_string ("guid", + P_("GUID"), + P_("The guid of the server"), + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusServer:address: + * + * The D-Bus address to listen on. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_ADDRESS, + g_param_spec_string ("address", + P_("Address"), + P_("The address to listen on"), + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusServer:client-address: + * + * The D-Bus address that clients can use. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_CLIENT_ADDRESS, + g_param_spec_string ("client-address", + P_("Client Address"), + P_("The address clients can use"), + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusServer:active: + * + * Whether the server is currently active. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_ACTIVE, + g_param_spec_string ("active", + P_("Active"), + P_("Whether the server is currently active"), + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusServer:authentication-observer: + * + * A #GDBusAuthObserver object to assist in the authentication process or %NULL. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_AUTHENTICATION_OBSERVER, + g_param_spec_object ("authentication-observer", + P_("Authentication Observer"), + P_("Object used to assist in the authentication process"), + G_TYPE_DBUS_AUTH_OBSERVER, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GDBusServer::new-connection: + * @server: The #GDBusServer emitting the signal. + * @connection: A #GDBusConnection for the new connection. + * + * Emitted when a new authenticated connection has been made. Use + * g_dbus_connection_get_peer_credentials() to figure out what + * identity (if any), was authenticated. + * + * If you want to accept the connection, simply ref the @connection + * object. Then call g_dbus_connection_close() and unref it when you + * are done with it. A typical thing to do when accepting a + * connection is to listen to the #GDBusConnection::closed signal. + * + * If #GDBusServer:flags contains %G_DBUS_SERVER_FLAGS_RUN_IN_THREAD + * then the signal is emitted in a new thread dedicated to the + * connection. Otherwise the signal is emitted in the thread-default main + * loop of the thread that @server was constructed in. + * + * Since: 2.26 + */ + _signals[NEW_CONNECTION_SIGNAL] = g_signal_new ("new-connection", + G_TYPE_DBUS_SERVER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GDBusServerClass, new_connection), + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + G_TYPE_DBUS_CONNECTION); + + + g_type_class_add_private (klass, sizeof (GDBusServerPrivate)); +} + +static void +g_dbus_server_init (GDBusServer *server) +{ + server->priv = G_TYPE_INSTANCE_GET_PRIVATE (server, G_TYPE_DBUS_SERVER, GDBusServerPrivate); + + server->priv->main_context_at_construction = g_main_context_get_thread_default (); + if (server->priv->main_context_at_construction != NULL) + g_main_context_ref (server->priv->main_context_at_construction); +} + +static gboolean +on_run (GSocketService *service, + GSocketConnection *socket_connection, + GObject *source_object, + gpointer user_data); + +/** + * g_dbus_server_new_sync: + * @address: A D-Bus address. + * @flags: Flags from the #GDBusServerFlags enumeration. + * @guid: A D-Bus GUID. + * @observer: A #GDBusAuthObserver or %NULL. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for server or %NULL. + * + * Creates a new D-Bus server that listens on the first address in + * @address that works. + * + * Once constructed, you can use g_dbus_server_get_client_address() to + * get a D-Bus address string that clients can use to connect. + * + * Connect to the #GDBusServer::new-connection signal to handle + * incoming connections. + * + * The returned #GDBusServer isn't active - you have to start it with + * g_dbus_server_start(). + * + * See for how #GDBusServer can + * be used. + * + * This is a synchronous failable constructor. See + * g_dbus_server_new() for the asynchronous version. + * + * Returns: A #GDBusServer or %NULL if @error is set. Free with + * g_object_unref(). + * + * Since: 2.26 + */ +GDBusServer * +g_dbus_server_new_sync (const gchar *address, + GDBusServerFlags flags, + const gchar *guid, + GDBusAuthObserver *observer, + GCancellable *cancellable, + GError **error) +{ + GDBusServer *server; + + g_return_val_if_fail (address != NULL, NULL); + g_return_val_if_fail (g_dbus_is_guid (guid), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + server = g_initable_new (G_TYPE_DBUS_SERVER, + cancellable, + error, + "address", address, + "flags", flags, + "guid", guid, + "authentication-observer", observer, + NULL); + if (server != NULL) + { + /* Right now we don't have any transport not using the listener... */ + g_assert (server->priv->is_using_listener); + g_signal_connect (G_SOCKET_SERVICE (server->priv->listener), + "run", + G_CALLBACK (on_run), + server); + } + + return server; +} + +/** + * g_dbus_server_get_client_address: + * @server: A #GDBusServer. + * + * Gets a D-Bus address string that can be used by clients to connect + * to @server. + * + * Returns: A D-Bus address string. Do not free, the string is owned + * by @server. + * + * Since: 2.26 + */ +const gchar * +g_dbus_server_get_client_address (GDBusServer *server) +{ + g_return_val_if_fail (G_IS_DBUS_SERVER (server), NULL); + return server->priv->client_address; +} + +/** + * g_dbus_server_get_guid: + * @server: A #GDBusServer. + * + * Gets the GUID for @server. + * + * Returns: A D-Bus GUID. Do not free this string, it is owned by @server. + * + * Since: 2.26 + */ +const gchar * +g_dbus_server_get_guid (GDBusServer *server) +{ + g_return_val_if_fail (G_IS_DBUS_SERVER (server), NULL); + return server->priv->guid; +} + +/** + * g_dbus_server_get_flags: + * @server: A #GDBusServer. + * + * Gets the flags for @server. + * + * Returns: A set of flags from the #GDBusServerFlags enumeration. + * + * Since: 2.26 + */ +GDBusServerFlags +g_dbus_server_get_flags (GDBusServer *server) +{ + g_return_val_if_fail (G_IS_DBUS_SERVER (server), G_DBUS_SERVER_FLAGS_NONE); + return server->priv->flags; +} + +/** + * g_dbus_server_is_active: + * @server: A #GDBusServer. + * + * Gets whether @server is active. + * + * Returns: %TRUE if server is active, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_server_is_active (GDBusServer *server) +{ + g_return_val_if_fail (G_IS_DBUS_SERVER (server), G_DBUS_SERVER_FLAGS_NONE); + return server->priv->active; +} + +/** + * g_dbus_server_start: + * @server: A #GDBusServer. + * + * Starts @server. + * + * Since: 2.26 + */ +void +g_dbus_server_start (GDBusServer *server) +{ + g_return_if_fail (G_IS_DBUS_SERVER (server)); + if (server->priv->active) + return; + /* Right now we don't have any transport not using the listener... */ + g_assert (server->priv->is_using_listener); + g_socket_service_start (G_SOCKET_SERVICE (server->priv->listener)); + server->priv->active = TRUE; + g_object_notify (G_OBJECT (server), "active"); +} + +/** + * g_dbus_server_stop: + * @server: A #GDBusServer. + * + * Stops @server. + * + * Since: 2.26 + */ +void +g_dbus_server_stop (GDBusServer *server) +{ + g_return_if_fail (G_IS_DBUS_SERVER (server)); + if (!server->priv->active) + return; + /* Right now we don't have any transport not using the listener... */ + g_assert (server->priv->is_using_listener); + g_socket_service_stop (G_SOCKET_SERVICE (server->priv->listener)); + server->priv->active = FALSE; + g_object_notify (G_OBJECT (server), "active"); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#ifdef G_OS_UNIX + +static gint +random_ascii (void) +{ + gint ret; + ret = g_random_int_range (0, 60); + if (ret < 25) + ret += 'A'; + else if (ret < 50) + ret += 'a' - 25; + else + ret += '0' - 50; + return ret; +} + +/* note that address_entry has already been validated => exactly one of path, tmpdir or abstract keys are set */ +static gboolean +try_unix (GDBusServer *server, + const gchar *address_entry, + GHashTable *key_value_pairs, + GError **error) +{ + gboolean ret; + const gchar *path; + const gchar *tmpdir; + const gchar *abstract; + GSocketAddress *address; + + ret = FALSE; + address = NULL; + + path = g_hash_table_lookup (key_value_pairs, "path"); + tmpdir = g_hash_table_lookup (key_value_pairs, "tmpdir"); + abstract = g_hash_table_lookup (key_value_pairs, "abstract"); + + if (path != NULL) + { + address = g_unix_socket_address_new (path); + } + else if (tmpdir != NULL) + { + gint n; + GString *s; + GError *local_error; + + retry: + s = g_string_new (tmpdir); + g_string_append (s, "/dbus-"); + for (n = 0; n < 8; n++) + g_string_append_c (s, random_ascii ()); + + /* prefer abstract namespace if available */ + if (g_unix_socket_address_abstract_names_supported ()) + address = g_unix_socket_address_new_with_type (s->str, + -1, + G_UNIX_SOCKET_ADDRESS_ABSTRACT); + else + address = g_unix_socket_address_new (s->str); + g_string_free (s, TRUE); + + local_error = NULL; + if (!g_socket_listener_add_address (server->priv->listener, + address, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + NULL, /* source_object */ + NULL, /* effective_address */ + &local_error)) + { + if (local_error->domain == G_IO_ERROR && local_error->code == G_IO_ERROR_ADDRESS_IN_USE) + { + g_error_free (local_error); + goto retry; + } + g_propagate_error (error, local_error); + goto out; + } + ret = TRUE; + goto out; + } + else if (abstract != NULL) + { + if (!g_unix_socket_address_abstract_names_supported ()) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Abstract name space not supported")); + goto out; + } + address = g_unix_socket_address_new_with_type (abstract, + -1, + G_UNIX_SOCKET_ADDRESS_ABSTRACT); + } + else + { + g_assert_not_reached (); + } + + if (!g_socket_listener_add_address (server->priv->listener, + address, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + NULL, /* source_object */ + NULL, /* effective_address */ + error)) + goto out; + + ret = TRUE; + + out: + + if (address != NULL) + { + /* Fill out client_address if the connection attempt worked */ + if (ret) + { + server->priv->is_using_listener = TRUE; + + switch (g_unix_socket_address_get_address_type (G_UNIX_SOCKET_ADDRESS (address))) + { + case G_UNIX_SOCKET_ADDRESS_ABSTRACT: + server->priv->client_address = g_strdup_printf ("unix:abstract=%s", + g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (address))); + break; + + case G_UNIX_SOCKET_ADDRESS_PATH: + server->priv->client_address = g_strdup_printf ("unix:path=%s", + g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (address))); + break; + + default: + g_assert_not_reached (); + break; + } + } + g_object_unref (address); + } + return ret; +} +#endif + +/* ---------------------------------------------------------------------------------------------------- */ + +/* note that address_entry has already been validated => + * both host and port (guranteed to be a number in [0, 65535]) are set (family is optional) + */ +static gboolean +try_tcp (GDBusServer *server, + const gchar *address_entry, + GHashTable *key_value_pairs, + gboolean do_nonce, + GError **error) +{ + gboolean ret; + const gchar *host; + const gchar *port; + const gchar *family; + gint port_num; + GSocketAddress *address; + GResolver *resolver; + GList *resolved_addresses; + GList *l; + + ret = FALSE; + address = NULL; + resolver = NULL; + resolved_addresses = NULL; + + host = g_hash_table_lookup (key_value_pairs, "host"); + port = g_hash_table_lookup (key_value_pairs, "port"); + family = g_hash_table_lookup (key_value_pairs, "family"); + if (g_hash_table_lookup (key_value_pairs, "noncefile") != NULL) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Cannot specify nonce file when creating a server")); + goto out; + } + + if (host == NULL) + host = "localhost"; + if (port == NULL) + port = "0"; + port_num = strtol (port, NULL, 10); + + resolver = g_resolver_get_default (); + resolved_addresses = g_resolver_lookup_by_name (resolver, + host, + NULL, + error); + if (resolved_addresses == NULL) + goto out; + + /* TODO: handle family */ + for (l = resolved_addresses; l != NULL; l = l->next) + { + GInetAddress *address = G_INET_ADDRESS (l->data); + GSocketAddress *socket_address; + GSocketAddress *effective_address; + + socket_address = g_inet_socket_address_new (address, port_num); + if (!g_socket_listener_add_address (server->priv->listener, + socket_address, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_TCP, + NULL, /* GObject *source_object */ + &effective_address, + error)) + { + g_object_unref (socket_address); + goto out; + } + if (port_num == 0) + /* make sure we allocate the same port number for other listeners */ + port_num = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (effective_address)); + + g_object_unref (effective_address); + g_object_unref (socket_address); + } + + if (do_nonce) + { + gint fd; + guint n; + gsize bytes_written; + gsize bytes_remaining; + + server->priv->nonce = g_new0 (guchar, 16); + for (n = 0; n < 16; n++) + server->priv->nonce[n] = g_random_int_range (0, 256); + fd = g_file_open_tmp ("gdbus-nonce-file-XXXXXX", + &server->priv->nonce_file, + error); + if (fd == -1) + { + g_socket_listener_close (server->priv->listener); + goto out; + } + again: + bytes_written = 0; + bytes_remaining = 16; + while (bytes_remaining > 0) + { + gssize ret; + ret = write (fd, server->priv->nonce + bytes_written, bytes_remaining); + if (ret == -1) + { + if (errno == EINTR) + goto again; + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error writing nonce file at `%s': %s"), + server->priv->nonce_file, + strerror (errno)); + goto out; + } + bytes_written += ret; + bytes_remaining -= ret; + } + close (fd); + server->priv->client_address = g_strdup_printf ("nonce-tcp:host=%s,port=%d,noncefile=%s", + host, + port_num, + server->priv->nonce_file); + } + else + { + server->priv->client_address = g_strdup_printf ("tcp:host=%s,port=%d", host, port_num); + } + server->priv->is_using_listener = TRUE; + ret = TRUE; + + out: + g_list_foreach (resolved_addresses, (GFunc) g_object_unref, NULL); + g_list_free (resolved_addresses); + g_object_unref (resolver); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GDBusServer *server; + GDBusConnection *connection; +} EmitIdleData; + +static void +emit_idle_data_free (EmitIdleData *data) +{ + g_object_unref (data->server); + g_object_unref (data->connection); + g_free (data); +} + +static gboolean +emit_new_connection_in_idle (gpointer user_data) +{ + EmitIdleData *data = user_data; + + g_signal_emit (data->server, + _signals[NEW_CONNECTION_SIGNAL], + 0, + data->connection); + g_object_unref (data->connection); + + return FALSE; +} + +/* Called in new thread */ +static gboolean +on_run (GSocketService *service, + GSocketConnection *socket_connection, + GObject *source_object, + gpointer user_data) +{ + GDBusServer *server = G_DBUS_SERVER (user_data); + GDBusConnection *connection; + GDBusConnectionFlags connection_flags; + + if (server->priv->nonce != NULL) + { + gchar buf[16]; + gsize bytes_read; + + if (!g_input_stream_read_all (g_io_stream_get_input_stream (G_IO_STREAM (socket_connection)), + buf, + 16, + &bytes_read, + NULL, /* GCancellable */ + NULL)) /* GError */ + goto out; + + if (bytes_read != 16) + goto out; + + if (memcmp (buf, server->priv->nonce, 16) != 0) + goto out; + } + + connection_flags = G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER; + if (server->priv->flags & G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS) + connection_flags |= G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS; + + connection = g_dbus_connection_new_sync (G_IO_STREAM (socket_connection), + server->priv->guid, + connection_flags, + server->priv->authentication_observer, + NULL, /* GCancellable */ + NULL); /* GError */ + if (connection == NULL) + goto out; + + if (server->priv->flags & G_DBUS_SERVER_FLAGS_RUN_IN_THREAD) + { + g_signal_emit (server, + _signals[NEW_CONNECTION_SIGNAL], + 0, + connection); + g_object_unref (connection); + } + else + { + GSource *idle_source; + EmitIdleData *data; + + data = g_new0 (EmitIdleData, 1); + data->server = g_object_ref (server); + data->connection = g_object_ref (connection); + + idle_source = g_idle_source_new (); + g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); + g_source_set_callback (idle_source, + emit_new_connection_in_idle, + data, + (GDestroyNotify) emit_idle_data_free); + g_source_attach (idle_source, server->priv->main_context_at_construction); + g_source_unref (idle_source); + } + + out: + return TRUE; +} + +static gboolean +initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GDBusServer *server = G_DBUS_SERVER (initable); + gboolean ret; + guint n; + gchar **addr_array; + GError *last_error; + + ret = FALSE; + last_error = NULL; + + if (!g_dbus_is_guid (server->priv->guid)) + { + g_set_error (&last_error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("The string `%s' is not a valid D-Bus GUID"), + server->priv->guid); + goto out; + } + + server->priv->listener = G_SOCKET_LISTENER (g_threaded_socket_service_new (-1)); + + addr_array = g_strsplit (server->priv->address, ";", 0); + last_error = NULL; + for (n = 0; addr_array != NULL && addr_array[n] != NULL; n++) + { + const gchar *address_entry = addr_array[n]; + GHashTable *key_value_pairs; + gchar *transport_name; + GError *this_error; + + this_error = NULL; + if (g_dbus_is_supported_address (address_entry, + &this_error) && + _g_dbus_address_parse_entry (address_entry, + &transport_name, + &key_value_pairs, + &this_error)) + { + + if (FALSE) + { + } +#ifdef G_OS_UNIX + else if (g_strcmp0 (transport_name, "unix") == 0) + ret = try_unix (server, address_entry, key_value_pairs, &this_error); +#endif + else if (g_strcmp0 (transport_name, "tcp") == 0) + ret = try_tcp (server, address_entry, key_value_pairs, FALSE, &this_error); + else if (g_strcmp0 (transport_name, "nonce-tcp") == 0) + ret = try_tcp (server, address_entry, key_value_pairs, TRUE, &this_error); + else + g_set_error (&this_error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Cannot listen on unsupported transport `%s'"), + transport_name); + + g_free (transport_name); + if (key_value_pairs != NULL) + g_hash_table_unref (key_value_pairs); + + if (ret) + { + g_assert (this_error == NULL); + goto out; + } + } + + if (this_error != NULL) + { + if (last_error != NULL) + g_error_free (last_error); + last_error = this_error; + } + } + + if (!ret) + goto out; + + out: + if (ret) + { + if (last_error != NULL) + g_error_free (last_error); + } + else + { + g_assert (last_error != NULL); + g_propagate_error (error, last_error); + } + return ret; +} + + +static void +initable_iface_init (GInitableIface *initable_iface) +{ + initable_iface->init = initable_init; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#define __G_DBUS_SERVER_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusserver.h b/gio/gdbusserver.h new file mode 100644 index 000000000..5822b9cf3 --- /dev/null +++ b/gio/gdbusserver.h @@ -0,0 +1,101 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_SERVER_H__ +#define __G_DBUS_SERVER_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_DBUS_SERVER (g_dbus_server_get_type ()) +#define G_DBUS_SERVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_SERVER, GDBusServer)) +#define G_DBUS_SERVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_SERVER, GDBusServerClass)) +#define G_DBUS_SERVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_SERVER, GDBusServerClass)) +#define G_IS_DBUS_SERVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_SERVER)) +#define G_IS_DBUS_SERVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_SERVER)) + +typedef struct _GDBusServerClass GDBusServerClass; +typedef struct _GDBusServerPrivate GDBusServerPrivate; + +/** + * GDBusServer: + * + * The #GDBusServer structure contains only private data and + * should only be accessed using the provided API. + * + * Since: 2.26 + */ +struct _GDBusServer +{ + /*< private >*/ + GObject parent_instance; + GDBusServerPrivate *priv; +}; + +/** + * GDBusServerClass: + * @new_connection: Signal class handler for the #GDBusServer::new-connection signal. + * + * Class structure for #GDBusServer. + * + * Since: 2.26 + */ +struct _GDBusServerClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + /* Signals */ + void (*new_connection) (GDBusServer *server, + GDBusConnection *connection); + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); +}; + +GType g_dbus_server_get_type (void) G_GNUC_CONST; +GDBusServer *g_dbus_server_new_sync (const gchar *address, + GDBusServerFlags flags, + const gchar *guid, + GDBusAuthObserver *observer, + GCancellable *cancellable, + GError **error); +const gchar *g_dbus_server_get_client_address (GDBusServer *server); +const gchar *g_dbus_server_get_guid (GDBusServer *server); +GDBusServerFlags g_dbus_server_get_flags (GDBusServer *server); +void g_dbus_server_start (GDBusServer *server); +void g_dbus_server_stop (GDBusServer *server); +gboolean g_dbus_server_is_active (GDBusServer *server); + +G_END_DECLS + +#endif /* __G_DBUS_SERVER_H__ */ diff --git a/gio/gdbusutils.c b/gio/gdbusutils.c new file mode 100644 index 000000000..2a1ca67eb --- /dev/null +++ b/gio/gdbusutils.c @@ -0,0 +1,361 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include +#include + +#include "gdbusutils.h" + +#include "glibintl.h" +#include "gioalias.h" + +/** + * SECTION:gdbusutils + * @title: D-Bus Utilities + * @short_description: Various utilities related to D-Bus. + * @include: gio/gio.h + * + * Various utility routines related to D-Bus. + */ + +static gboolean +is_valid_bus_name_character (gint c, + gboolean allow_hyphen) +{ + return + (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '_') || + (allow_hyphen && c == '-'); +} + +static gboolean +is_valid_initial_bus_name_character (gint c, + gboolean allow_initial_digit, + gboolean allow_hyphen) +{ + if (allow_initial_digit) + return is_valid_bus_name_character (c, allow_hyphen); + else + return + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '_') || + (allow_hyphen && c == '-'); +} + +static gboolean +is_valid_name (const gchar *start, + guint len, + gboolean allow_initial_digit, + gboolean allow_hyphen) +{ + gboolean ret; + const gchar *s; + const gchar *end; + gboolean has_dot; + + ret = FALSE; + + if (len == 0) + goto out; + + s = start; + end = s + len; + has_dot = FALSE; + while (s != end) + { + if (*s == '.') + { + s += 1; + if (G_UNLIKELY (!is_valid_initial_bus_name_character (*s, allow_initial_digit, allow_hyphen))) + goto out; + has_dot = TRUE; + } + else if (G_UNLIKELY (!is_valid_bus_name_character (*s, allow_hyphen))) + { + goto out; + } + s += 1; + } + + if (G_UNLIKELY (!has_dot)) + goto out; + + ret = TRUE; + + out: + return ret; +} + +/** + * g_dbus_is_name: + * @string: The string to check. + * + * Checks if @string is a valid D-Bus bus name (either unique or well-known). + * + * Returns: %TRUE if valid, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_is_name (const gchar *string) +{ + guint len; + gboolean ret; + const gchar *s; + const gchar *end; + + g_return_val_if_fail (string != NULL, FALSE); + + ret = FALSE; + + len = strlen (string); + if (G_UNLIKELY (len == 0 || len > 255)) + goto out; + + s = string; + end = s + len; + if (*s == ':') + { + /* handle unique name */ + if (!is_valid_name (s + 1, len - 1, TRUE, TRUE)) + goto out; + ret = TRUE; + goto out; + } + else if (G_UNLIKELY (*s == '.')) + { + /* can't start with a . */ + goto out; + } + else if (G_UNLIKELY (!is_valid_initial_bus_name_character (*s, FALSE, TRUE))) + goto out; + + ret = is_valid_name (s + 1, len - 1, FALSE, TRUE); + + out: + return ret; +} + +/** + * g_dbus_is_unique_name: + * @string: The string to check. + * + * Checks if @string is a valid D-Bus unique bus name. + * + * Returns: %TRUE if valid, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_is_unique_name (const gchar *string) +{ + gboolean ret; + guint len; + + g_return_val_if_fail (string != NULL, FALSE); + + ret = FALSE; + + len = strlen (string); + if (G_UNLIKELY (len == 0 || len > 255)) + goto out; + + if (G_UNLIKELY (*string != ':')) + goto out; + + if (G_UNLIKELY (!is_valid_name (string + 1, len - 1, TRUE, TRUE))) + goto out; + + ret = TRUE; + + out: + return ret; +} + +/** + * g_dbus_is_member_name: + * @string: The string to check. + * + * Checks if @string is a valid D-Bus member (e.g. signal or method) name. + * + * Returns: %TRUE if valid, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_is_member_name (const gchar *string) +{ + gboolean ret; + guint n; + + ret = FALSE; + if (G_UNLIKELY (string == NULL)) + goto out; + + if (G_UNLIKELY (!is_valid_initial_bus_name_character (string[0], FALSE, FALSE))) + goto out; + + for (n = 1; string[n] != '\0'; n++) + { + if (G_UNLIKELY (!is_valid_bus_name_character (string[n], FALSE))) + { + goto out; + } + } + + ret = TRUE; + + out: + return ret; +} + +/** + * g_dbus_is_interface_name: + * @string: The string to check. + * + * Checks if @string is a valid D-Bus interface name. + * + * Returns: %TRUE if valid, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_is_interface_name (const gchar *string) +{ + guint len; + gboolean ret; + const gchar *s; + const gchar *end; + + g_return_val_if_fail (string != NULL, FALSE); + + ret = FALSE; + + len = strlen (string); + if (G_UNLIKELY (len == 0 || len > 255)) + goto out; + + s = string; + end = s + len; + if (G_UNLIKELY (*s == '.')) + { + /* can't start with a . */ + goto out; + } + else if (G_UNLIKELY (!is_valid_initial_bus_name_character (*s, FALSE, FALSE))) + goto out; + + ret = is_valid_name (s + 1, len - 1, FALSE, FALSE); + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/* TODO: maybe move to glib? if so, it should conform to http://en.wikipedia.org/wiki/Guid and/or + * http://tools.ietf.org/html/rfc4122 - specifically it should have hyphens then. + */ + +/** + * g_dbus_generate_guid: + * + * Generate a D-Bus GUID that can be used with + * e.g. g_dbus_connection_new(). + * + * See the D-Bus specification regarding what strings are valid D-Bus + * GUID (for example, D-Bus GUIDs are not RFC-4122 compliant). + * + * Returns: A valid D-Bus GUID. Free with g_free(). + * + * Since: 2.26 + */ +gchar * +g_dbus_generate_guid (void) +{ + GString *s; + GTimeVal now; + guint32 r1; + guint32 r2; + guint32 r3; + + s = g_string_new (NULL); + + r1 = g_random_int (); + r2 = g_random_int (); + r3 = g_random_int (); + g_get_current_time (&now); + + g_string_append_printf (s, "%08x", r1); + g_string_append_printf (s, "%08x", r2); + g_string_append_printf (s, "%08x", r3); + g_string_append_printf (s, "%08x", (guint32) now.tv_sec); + + return g_string_free (s, FALSE); +} + +/** + * g_dbus_is_guid: + * @string: The string to check. + * + * Checks if @string is a D-Bus GUID. + * + * See the D-Bus specification regarding what strings are valid D-Bus + * GUID (for example, D-Bus GUIDs are not RFC-4122 compliant). + * + * Returns: %TRUE if @string is a guid, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +g_dbus_is_guid (const gchar *string) +{ + gboolean ret; + guint n; + + g_return_val_if_fail (string != NULL, FALSE); + + ret = FALSE; + + for (n = 0; n < 32; n++) + { + if (!g_ascii_isxdigit (string[n])) + goto out; + } + if (string[32] != '\0') + goto out; + + ret = TRUE; + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +#define __G_DBUS_UTILS_C__ +#include "gioaliasdef.c" diff --git a/gio/gdbusutils.h b/gio/gdbusutils.h new file mode 100644 index 000000000..4d8a7ab26 --- /dev/null +++ b/gio/gdbusutils.h @@ -0,0 +1,40 @@ +/* GDBus - GLib D-Bus Library + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __G_DBUS_UTILS_H__ +#define __G_DBUS_UTILS_H__ + +#include + +G_BEGIN_DECLS + +gboolean g_dbus_is_guid (const gchar *string); +gchar *g_dbus_generate_guid (void); + +gboolean g_dbus_is_name (const gchar *string); +gboolean g_dbus_is_unique_name (const gchar *string); +gboolean g_dbus_is_member_name (const gchar *string); +gboolean g_dbus_is_interface_name (const gchar *string); + +G_END_DECLS + +#endif /* __G_DBUS_UTILS_H__ */ diff --git a/gio/gio-marshal.list b/gio/gio-marshal.list index 8439304bf..7899b9d69 100644 --- a/gio/gio-marshal.list +++ b/gio/gio-marshal.list @@ -6,3 +6,6 @@ BOOLEAN:OBJECT,OBJECT VOID:STRING,BOXED,BOXED BOOL:POINTER,INT BOOL:UINT +VOID:STRING,STRING,BOXED +VOID:BOOL,BOXED +VOID:BOXED,BOXED diff --git a/gio/gio.h b/gio/gio.h index ac0dc7fbf..ca55cd674 100644 --- a/gio/gio.h +++ b/gio/gio.h @@ -97,6 +97,22 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #undef __GIO_GIO_H_INSIDE__ #endif /* __G_IO_H__ */ diff --git a/gio/gio.symbols b/gio/gio.symbols index 8c278fa77..c7adc7ccc 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -987,6 +987,20 @@ g_unix_socket_address_type_get_type G_GNUC_CONST g_resolver_error_get_type G_GNUC_CONST g_zlib_compressor_format_get_type g_settings_bind_flags_get_type +g_dbus_error_get_type G_GNUC_CONST +g_bus_type_get_type G_GNUC_CONST +g_bus_name_owner_flags_get_type G_GNUC_CONST +g_bus_name_watcher_flags_get_type G_GNUC_CONST +g_dbus_proxy_flags_get_type G_GNUC_CONST +g_dbus_connection_flags_get_type G_GNUC_CONST +g_dbus_capability_flags_get_type G_GNUC_CONST +g_dbus_call_flags_get_type G_GNUC_CONST +g_dbus_message_type_get_type G_GNUC_CONST +g_dbus_message_flags_get_type G_GNUC_CONST +g_dbus_message_header_field_get_type G_GNUC_CONST +g_dbus_property_info_flags_get_type G_GNUC_CONST +g_dbus_subtree_flags_get_type G_GNUC_CONST +g_dbus_server_flags_get_type G_GNUC_CONST #endif #endif @@ -1326,6 +1340,8 @@ g_tcp_connection_get_graceful_disconnect g_unix_connection_get_type G_GNUC_CONST g_unix_connection_receive_fd g_unix_connection_send_fd +g_unix_connection_receive_credentials +g_unix_connection_send_credentials #endif #endif #endif @@ -1430,3 +1446,279 @@ g_settings_get_boolean g_settings_set_boolean #endif #endif + +#if IN_HEADER(__G_CREDENTIALS_H__) +#if IN_FILE(__G_CREDENTIALS_C__) +g_credentials_get_type G_GNUC_CONST +g_credentials_new +g_credentials_to_string +g_credentials_get_native +g_credentials_set_native +g_credentials_is_same_user +g_credentials_get_unix_user +g_credentials_set_unix_user +#endif +#endif + +#if IN_HEADER(__G_DBUS_ADDRESS_H__) +#if IN_FILE(__G_DBUS_ADDRESS_C__) +g_dbus_is_address +g_dbus_is_supported_address +g_dbus_address_get_for_bus_sync +g_dbus_address_get_stream +g_dbus_address_get_stream_finish +g_dbus_address_get_stream_sync +#endif +#endif + +#if IN_HEADER(__G_DBUS_AUTH_OBSERVER_H__) +#if IN_FILE(__G_DBUS_AUTH_OBSERVER_C__) +g_dbus_auth_observer_get_type G_GNUC_CONST +g_dbus_auth_observer_new +g_dbus_auth_observer_authorize_authenticated_peer +#endif +#endif + +#if IN_HEADER(__G_DBUS_CONNECTION_H__) +#if IN_FILE(__G_DBUS_CONNECTION_C__) +g_dbus_connection_get_type G_GNUC_CONST +g_bus_get +g_bus_get_finish +g_bus_get_sync +g_dbus_connection_new +g_dbus_connection_new_finish +g_dbus_connection_new_for_address +g_dbus_connection_new_for_address_finish +g_dbus_connection_new_for_address_sync +g_dbus_connection_new_sync +g_dbus_connection_get_capabilities +g_dbus_connection_get_exit_on_close +g_dbus_connection_get_guid +g_dbus_connection_get_peer_credentials +g_dbus_connection_get_stream +g_dbus_connection_get_unique_name +g_dbus_connection_is_closed +g_dbus_connection_set_exit_on_close +g_dbus_connection_close +g_dbus_connection_emit_signal +g_dbus_connection_call +g_dbus_connection_call_finish +g_dbus_connection_call_sync +g_dbus_connection_signal_subscribe +g_dbus_connection_signal_unsubscribe +g_dbus_connection_add_filter +g_dbus_connection_remove_filter +g_dbus_connection_send_message +g_dbus_connection_send_message_with_reply +g_dbus_connection_send_message_with_reply_finish +g_dbus_connection_send_message_with_reply_sync +g_dbus_connection_register_object +g_dbus_connection_unregister_object +g_dbus_connection_register_subtree +g_dbus_connection_unregister_subtree +#endif +#endif + +#if IN_HEADER(__G_DBUS_ERROR_H__) +#if IN_FILE(__G_DBUS_ERROR_C__) +g_dbus_error_quark +g_dbus_error_new_for_dbus_error +g_dbus_error_is_remote_error +g_dbus_error_get_remote_error +g_dbus_error_strip_remote_error +g_dbus_error_encode_gerror +g_dbus_error_register_error +g_dbus_error_register_error_domain +g_dbus_error_set_dbus_error +g_dbus_error_set_dbus_error_valist +g_dbus_error_unregister_error +#endif +#endif + +#if IN_HEADER(__G_DBUS_INTROSPECTION_H__) +#if IN_FILE(__G_DBUS_INTROSPECTION_C__) +g_dbus_annotation_info_get_type G_GNUC_CONST +g_dbus_arg_info_get_type G_GNUC_CONST +g_dbus_property_info_get_type G_GNUC_CONST +g_dbus_interface_info_get_type G_GNUC_CONST +g_dbus_method_info_get_type G_GNUC_CONST +g_dbus_signal_info_get_type G_GNUC_CONST +g_dbus_node_info_get_type G_GNUC_CONST +g_dbus_annotation_info_lookup +g_dbus_annotation_info_ref +g_dbus_annotation_info_unref +g_dbus_interface_info_generate_xml +g_dbus_interface_info_lookup_method +g_dbus_interface_info_lookup_property +g_dbus_interface_info_lookup_signal +g_dbus_node_info_new_for_xml +g_dbus_node_info_generate_xml +g_dbus_node_info_lookup_interface +g_dbus_arg_info_ref +g_dbus_arg_info_unref +g_dbus_property_info_ref +g_dbus_property_info_unref +g_dbus_signal_info_ref +g_dbus_signal_info_unref +g_dbus_method_info_ref +g_dbus_method_info_unref +g_dbus_interface_info_ref +g_dbus_interface_info_unref +g_dbus_node_info_ref +g_dbus_node_info_unref +#endif +#endif + +#if IN_HEADER(__G_DBUS_MESSAGE_H__) +#if IN_FILE(__G_DBUS_MESSAGE_C__) +g_dbus_message_get_type G_GNUC_CONST +g_dbus_message_new +g_dbus_message_new_from_blob +g_dbus_message_new_method_call +g_dbus_message_new_method_error +g_dbus_message_new_method_error_literal +g_dbus_message_new_method_error_valist +g_dbus_message_new_method_reply +g_dbus_message_new_signal +g_dbus_message_bytes_needed +g_dbus_message_get_arg0 +g_dbus_message_get_body +g_dbus_message_get_destination +g_dbus_message_get_error_name +g_dbus_message_get_flags +g_dbus_message_get_header +g_dbus_message_get_header_fields +g_dbus_message_get_interface +g_dbus_message_get_member +g_dbus_message_get_num_unix_fds +g_dbus_message_get_path +g_dbus_message_get_reply_serial +g_dbus_message_get_sender +g_dbus_message_get_serial +g_dbus_message_get_signature +g_dbus_message_get_message_type +g_dbus_message_get_unix_fd_list +g_dbus_message_print +g_dbus_message_set_body +g_dbus_message_set_destination +g_dbus_message_set_error_name +g_dbus_message_set_flags +g_dbus_message_set_header +g_dbus_message_set_interface +g_dbus_message_set_member +g_dbus_message_set_num_unix_fds +g_dbus_message_set_path +g_dbus_message_set_reply_serial +g_dbus_message_set_sender +g_dbus_message_set_serial +g_dbus_message_set_signature +g_dbus_message_set_message_type +g_dbus_message_set_unix_fd_list +g_dbus_message_to_blob +g_dbus_message_to_gerror +#endif +#endif + +#if IN_HEADER(__G_DBUS_METHOD_INVOCATION_H__) +#if IN_FILE(__G_DBUS_METHOD_INVOCATION_C__) +g_dbus_method_invocation_get_type G_GNUC_CONST +g_dbus_method_invocation_new +g_dbus_method_invocation_get_connection +g_dbus_method_invocation_get_interface_name +g_dbus_method_invocation_get_message +g_dbus_method_invocation_get_method_info +g_dbus_method_invocation_get_method_name +g_dbus_method_invocation_get_object_path +g_dbus_method_invocation_get_parameters +g_dbus_method_invocation_get_sender +g_dbus_method_invocation_get_user_data +g_dbus_method_invocation_return_dbus_error +g_dbus_method_invocation_return_error +g_dbus_method_invocation_return_error_literal +g_dbus_method_invocation_return_error_valist +g_dbus_method_invocation_return_gerror +g_dbus_method_invocation_return_value +#endif +#endif + +#if IN_HEADER(__G_DBUS_NAME_OWNING_H__) +#if IN_FILE(__G_DBUS_NAME_OWNING_C__) +g_bus_own_name +g_bus_own_name_on_connection +g_bus_unown_name +#endif +#endif + +#if IN_HEADER(__G_DBUS_NAME_WATCHING_H__) +#if IN_FILE(__G_DBUS_NAME_WATCHING_C__) +g_bus_watch_name +g_bus_watch_name_on_connection +g_bus_unwatch_name +#endif +#endif + +#if IN_HEADER(__G_DBUS_PROXY_H__) +#if IN_FILE(__G_DBUS_PROXY_C__) +g_dbus_proxy_get_type G_GNUC_CONST +g_dbus_proxy_new +g_dbus_proxy_new_finish +g_dbus_proxy_new_sync +g_dbus_proxy_get_cached_property +g_dbus_proxy_set_cached_property +g_dbus_proxy_get_cached_property_names +g_dbus_proxy_get_connection +g_dbus_proxy_get_default_timeout +g_dbus_proxy_get_flags +g_dbus_proxy_get_interface_info +g_dbus_proxy_get_interface_name +g_dbus_proxy_get_object_path +g_dbus_proxy_get_unique_bus_name +g_dbus_proxy_set_default_timeout +g_dbus_proxy_set_interface_info +g_dbus_proxy_call +g_dbus_proxy_call_finish +g_dbus_proxy_call_sync +#endif +#endif + +#if IN_HEADER(__G_DBUS_PROXY_WATCHING_H__) +#if IN_FILE(__G_DBUS_PROXY_WATCHING_C__) +g_bus_watch_proxy +g_bus_watch_proxy_on_connection +g_bus_unwatch_proxy +#endif +#endif + +#if IN_HEADER(__G_DBUS_SERVER_H__) +#if IN_FILE(__G_DBUS_SERVER_C__) +g_dbus_server_get_type G_GNUC_CONST +g_dbus_server_new_sync +g_dbus_server_is_active +g_dbus_server_start +g_dbus_server_stop +g_dbus_server_get_client_address +g_dbus_server_get_flags +g_dbus_server_get_guid +#endif +#endif + +#if IN_HEADER(__G_DBUS_UTILS_H__) +#if IN_FILE(__G_DBUS_UTILS_C__) +g_dbus_generate_guid +g_dbus_is_guid +g_dbus_is_interface_name +g_dbus_is_member_name +g_dbus_is_name +g_dbus_is_unique_name +#endif +#endif + +#if IN_HEADER(__G_UNIX_CREDENTIALS_MESSAGE_H__) +#if IN_FILE(__G_UNIX_CREDENTIALS_MESSAGE_C__) +g_unix_credentials_message_get_type G_GNUC_CONST +g_unix_credentials_message_new +g_unix_credentials_message_new_with_credentials +g_unix_credentials_message_get_credentials +g_unix_credentials_message_is_supported +#endif +#endif diff --git a/gio/gioenums.h b/gio/gioenums.h index 9c98ffd9b..936f99177 100644 --- a/gio/gioenums.h +++ b/gio/gioenums.h @@ -429,6 +429,11 @@ typedef enum { * @G_IO_ERROR_ADDRESS_IN_USE: The requested address is already in use. Since 2.22 * @G_IO_ERROR_PARTIAL_INPUT: Need more input to finish operation. Since 2.24 * @G_IO_ERROR_INVALID_DATA: There input data was invalid. Since 2.24 + * @G_IO_ERROR_DBUS_ERROR: A remote object generated an error that + * doesn't correspond to a locally registered #GError error + * domain. Use g_dbus_error_get_remote_error() to extract the D-Bus + * error name and g_dbus_error_strip_remote_error() to fix up the + * message so it matches what was received on the wire. Since 2.26. * * Error codes returned by GIO functions. * @@ -469,7 +474,8 @@ typedef enum { G_IO_ERROR_NOT_INITIALIZED, G_IO_ERROR_ADDRESS_IN_USE, G_IO_ERROR_PARTIAL_INPUT, - G_IO_ERROR_INVALID_DATA + G_IO_ERROR_INVALID_DATA, + G_IO_ERROR_DBUS_ERROR } GIOErrorEnum; @@ -732,6 +738,396 @@ typedef enum { G_UNIX_SOCKET_ADDRESS_ABSTRACT_PADDED } GUnixSocketAddressType; +/** + * GBusType: + * @G_BUS_TYPE_STARTER: An alias for the message bus that activated the process, if any. + * @G_BUS_TYPE_SYSTEM: The system-wide message bus. + * @G_BUS_TYPE_SESSION: The login session message bus. + * + * An enumeration for well-known message buses. + * + * Since: 2.26 + */ +typedef enum +{ + G_BUS_TYPE_STARTER = 0, + G_BUS_TYPE_SYSTEM = 1, + G_BUS_TYPE_SESSION = 2 +} GBusType; + +/** + * GBusNameOwnerFlags: + * @G_BUS_NAME_OWNER_FLAGS_NONE: No flags set. + * @G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT: Allow another message bus connection to claim the the name. + * @G_BUS_NAME_OWNER_FLAGS_REPLACE: If another message bus connection owns the name and have + * specified #G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, then take the name from the other connection. + * + * Flags used in g_bus_own_name(). + * + * Since: 2.26 + */ +typedef enum +{ + G_BUS_NAME_OWNER_FLAGS_NONE = 0, /*< nick=none >*/ + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT = (1<<0), /*< nick=allow-replacement >*/ + G_BUS_NAME_OWNER_FLAGS_REPLACE = (1<<1), /*< nick=replace >*/ +} GBusNameOwnerFlags; + +/** + * GBusNameWatcherFlags: + * @G_BUS_NAME_WATCHER_FLAGS_NONE: No flags set. + * @G_BUS_NAME_WATCHER_FLAGS_AUTO_START: If no-one owns the name when + * beginning to watch the name, ask the bus to launch an owner for the + * name. + * + * Flags used in g_bus_watch_name(). + * + * Since: 2.26 + */ +typedef enum +{ + G_BUS_NAME_WATCHER_FLAGS_NONE = 0, + G_BUS_NAME_WATCHER_FLAGS_AUTO_START = (1<<0) +} GBusNameWatcherFlags; + +/** + * GDBusProxyFlags: + * @G_DBUS_PROXY_FLAGS_NONE: No flags set. + * @G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES: Don't load properties. + * @G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS: Don't connect to signals on the remote object. + * + * Flags used when constructing an instance of a #GDBusProxy derived class. + * + * Since: 2.26 + */ +typedef enum +{ + G_DBUS_PROXY_FLAGS_NONE = 0, /*< nick=none >*/ + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES = (1<<0), /*< nick=do-not-load-properties >*/ + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = (1<<1), /*< nick=do-not-connect-signals >*/ +} GDBusProxyFlags; + +/** + * GDBusError: + * @G_DBUS_ERROR_FAILED: + * A generic error; "something went wrong" - see the error message for + * more. + * @G_DBUS_ERROR_NO_MEMORY: + * There was not enough memory to complete an operation. + * @G_DBUS_ERROR_SERVICE_UNKNOWN: + * The bus doesn't know how to launch a service to supply the bus name + * you wanted. + * @G_DBUS_ERROR_NAME_HAS_NO_OWNER: + * The bus name you referenced doesn't exist (i.e. no application owns + * it). + * @G_DBUS_ERROR_NO_REPLY: + * No reply to a message expecting one, usually means a timeout occurred. + * @G_DBUS_ERROR_IO_ERROR: + * Something went wrong reading or writing to a socket, for example. + * @G_DBUS_ERROR_BAD_ADDRESS: + * A D-Bus bus address was malformed. + * @G_DBUS_ERROR_NOT_SUPPORTED: + * Requested operation isn't supported (like ENOSYS on UNIX). + * @G_DBUS_ERROR_LIMITS_EXCEEDED: + * Some limited resource is exhausted. + * @G_DBUS_ERROR_ACCESS_DENIED: + * Security restrictions don't allow doing what you're trying to do. + * @G_DBUS_ERROR_AUTH_FAILED: + * Authentication didn't work. + * @G_DBUS_ERROR_NO_SERVER: + * Unable to connect to server (probably caused by ECONNREFUSED on a + * socket). + * @G_DBUS_ERROR_TIMEOUT: + * Certain timeout errors, possibly ETIMEDOUT on a socket. Note that + * %G_DBUS_ERROR_NO_REPLY is used for message reply timeouts. Warning: + * this is confusingly-named given that %G_DBUS_ERROR_TIMED_OUT also + * exists. We can't fix it for compatibility reasons so just be + * careful. + * @G_DBUS_ERROR_NO_NETWORK: + * No network access (probably ENETUNREACH on a socket). + * @G_DBUS_ERROR_ADDRESS_IN_USE: + * Can't bind a socket since its address is in use (i.e. EADDRINUSE). + * @G_DBUS_ERROR_DISCONNECTED: + * The connection is disconnected and you're trying to use it. + * @G_DBUS_ERROR_INVALID_ARGS: + * Invalid arguments passed to a method call. + * @G_DBUS_ERROR_FILE_NOT_FOUND: + * Missing file. + * @G_DBUS_ERROR_FILE_EXISTS: + * Existing file and the operation you're using does not silently overwrite. + * @G_DBUS_ERROR_UNKNOWN_METHOD: + * Method name you invoked isn't known by the object you invoked it on. + * @G_DBUS_ERROR_TIMED_OUT: + * Certain timeout errors, e.g. while starting a service. Warning: this is + * confusingly-named given that %G_DBUS_ERROR_TIMEOUT also exists. We + * can't fix it for compatibility reasons so just be careful. + * @G_DBUS_ERROR_MATCH_RULE_NOT_FOUND: + * Tried to remove or modify a match rule that didn't exist. + * @G_DBUS_ERROR_MATCH_RULE_INVALID: + * The match rule isn't syntactically valid. + * @G_DBUS_ERROR_SPAWN_EXEC_FAILED: + * While starting a new process, the exec() call failed. + * @G_DBUS_ERROR_SPAWN_FORK_FAILED: + * While starting a new process, the fork() call failed. + * @G_DBUS_ERROR_SPAWN_CHILD_EXITED: + * While starting a new process, the child exited with a status code. + * @G_DBUS_ERROR_SPAWN_CHILD_SIGNALED: + * While starting a new process, the child exited on a signal. + * @G_DBUS_ERROR_SPAWN_FAILED: + * While starting a new process, something went wrong. + * @G_DBUS_ERROR_SPAWN_SETUP_FAILED: + * We failed to setup the environment correctly. + * @G_DBUS_ERROR_SPAWN_CONFIG_INVALID: + * We failed to setup the config parser correctly. + * @G_DBUS_ERROR_SPAWN_SERVICE_INVALID: + * Bus name was not valid. + * @G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND: + * Service file not found in system-services directory. + * @G_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID: + * Permissions are incorrect on the setuid helper. + * @G_DBUS_ERROR_SPAWN_FILE_INVALID: + * Service file invalid (Name, User or Exec missing). + * @G_DBUS_ERROR_SPAWN_NO_MEMORY: + * Tried to get a UNIX process ID and it wasn't available. + * @G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN: + * Tried to get a UNIX process ID and it wasn't available. + * @G_DBUS_ERROR_INVALID_SIGNATURE: + * A type signature is not valid. + * @G_DBUS_ERROR_INVALID_FILE_CONTENT: + * A file contains invalid syntax or is otherwise broken. + * @G_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN: + * Asked for SELinux security context and it wasn't available. + * @G_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN: + * Asked for ADT audit data and it wasn't available. + * @G_DBUS_ERROR_OBJECT_PATH_IN_USE: + * There's already an object with the requested object path. + * + * Error codes for the %G_DBUS_ERROR error domain. + * + * Since: 2.26 + */ +typedef enum +{ + /* Well-known errors in the org.freedesktop.DBus.Error namespace */ + G_DBUS_ERROR_FAILED, /* org.freedesktop.DBus.Error.Failed */ + G_DBUS_ERROR_NO_MEMORY, /* org.freedesktop.DBus.Error.NoMemory */ + G_DBUS_ERROR_SERVICE_UNKNOWN, /* org.freedesktop.DBus.Error.ServiceUnknown */ + G_DBUS_ERROR_NAME_HAS_NO_OWNER, /* org.freedesktop.DBus.Error.NameHasNoOwner */ + G_DBUS_ERROR_NO_REPLY, /* org.freedesktop.DBus.Error.NoReply */ + G_DBUS_ERROR_IO_ERROR, /* org.freedesktop.DBus.Error.IOError */ + G_DBUS_ERROR_BAD_ADDRESS, /* org.freedesktop.DBus.Error.BadAddress */ + G_DBUS_ERROR_NOT_SUPPORTED, /* org.freedesktop.DBus.Error.NotSupported */ + G_DBUS_ERROR_LIMITS_EXCEEDED, /* org.freedesktop.DBus.Error.LimitsExceeded */ + G_DBUS_ERROR_ACCESS_DENIED, /* org.freedesktop.DBus.Error.AccessDenied */ + G_DBUS_ERROR_AUTH_FAILED, /* org.freedesktop.DBus.Error.AuthFailed */ + G_DBUS_ERROR_NO_SERVER, /* org.freedesktop.DBus.Error.NoServer */ + G_DBUS_ERROR_TIMEOUT, /* org.freedesktop.DBus.Error.Timeout */ + G_DBUS_ERROR_NO_NETWORK, /* org.freedesktop.DBus.Error.NoNetwork */ + G_DBUS_ERROR_ADDRESS_IN_USE, /* org.freedesktop.DBus.Error.AddressInUse */ + G_DBUS_ERROR_DISCONNECTED, /* org.freedesktop.DBus.Error.Disconnected */ + G_DBUS_ERROR_INVALID_ARGS, /* org.freedesktop.DBus.Error.InvalidArgs */ + G_DBUS_ERROR_FILE_NOT_FOUND, /* org.freedesktop.DBus.Error.FileNotFound */ + G_DBUS_ERROR_FILE_EXISTS, /* org.freedesktop.DBus.Error.FileExists */ + G_DBUS_ERROR_UNKNOWN_METHOD, /* org.freedesktop.DBus.Error.UnknownMethod */ + G_DBUS_ERROR_TIMED_OUT, /* org.freedesktop.DBus.Error.TimedOut */ + G_DBUS_ERROR_MATCH_RULE_NOT_FOUND, /* org.freedesktop.DBus.Error.MatchRuleNotFound */ + G_DBUS_ERROR_MATCH_RULE_INVALID, /* org.freedesktop.DBus.Error.MatchRuleInvalid */ + G_DBUS_ERROR_SPAWN_EXEC_FAILED, /* org.freedesktop.DBus.Error.Spawn.ExecFailed */ + G_DBUS_ERROR_SPAWN_FORK_FAILED, /* org.freedesktop.DBus.Error.Spawn.ForkFailed */ + G_DBUS_ERROR_SPAWN_CHILD_EXITED, /* org.freedesktop.DBus.Error.Spawn.ChildExited */ + G_DBUS_ERROR_SPAWN_CHILD_SIGNALED, /* org.freedesktop.DBus.Error.Spawn.ChildSignaled */ + G_DBUS_ERROR_SPAWN_FAILED, /* org.freedesktop.DBus.Error.Spawn.Failed */ + G_DBUS_ERROR_SPAWN_SETUP_FAILED, /* org.freedesktop.DBus.Error.Spawn.FailedToSetup */ + G_DBUS_ERROR_SPAWN_CONFIG_INVALID, /* org.freedesktop.DBus.Error.Spawn.ConfigInvalid */ + G_DBUS_ERROR_SPAWN_SERVICE_INVALID, /* org.freedesktop.DBus.Error.Spawn.ServiceNotValid */ + G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, /* org.freedesktop.DBus.Error.Spawn.ServiceNotFound */ + G_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, /* org.freedesktop.DBus.Error.Spawn.PermissionsInvalid */ + G_DBUS_ERROR_SPAWN_FILE_INVALID, /* org.freedesktop.DBus.Error.Spawn.FileInvalid */ + G_DBUS_ERROR_SPAWN_NO_MEMORY, /* org.freedesktop.DBus.Error.Spawn.NoMemory */ + G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, /* org.freedesktop.DBus.Error.UnixProcessIdUnknown */ + G_DBUS_ERROR_INVALID_SIGNATURE, /* org.freedesktop.DBus.Error.InvalidSignature */ + G_DBUS_ERROR_INVALID_FILE_CONTENT, /* org.freedesktop.DBus.Error.InvalidFileContent */ + G_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, /* org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown */ + G_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN, /* org.freedesktop.DBus.Error.AdtAuditDataUnknown */ + G_DBUS_ERROR_OBJECT_PATH_IN_USE, /* org.freedesktop.DBus.Error.ObjectPathInUse */ +} GDBusError; +/* Remember to update g_dbus_error_quark() in gdbuserror.c if you extend this enumeration */ + +/** + * GDBusConnectionFlags: + * @G_DBUS_CONNECTION_FLAGS_NONE: No flags set. + * @G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT: Perform authentication against server. + * @G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER: Perform authentication against client. + * @G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS: When + * authenticating as a server, allow the anonymous authentication + * method. + * @G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION: Pass this flag if connecting to a peer that is a + * message bus. This means that the Hello() method will be invoked as part of the connection setup. + * + * Flags used when creating a new #GDBusConnection. + * + * Since: 2.26 + */ +typedef enum { + G_DBUS_CONNECTION_FLAGS_NONE = 0, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT = (1<<0), + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER = (1<<1), + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS = (1<<2), + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION = (1<<3) +} GDBusConnectionFlags; + +/** + * GDBusCapabilityFlags: + * @G_DBUS_CAPABILITY_FLAGS_NONE: No flags set. + * @G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING: The connection + * supports exchanging UNIX file descriptors with the remote peer. + * + * Capabilities negotiated with the remote peer. + * + * Since: 2.26 + */ +typedef enum { + G_DBUS_CAPABILITY_FLAGS_NONE = 0, + G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING = (1<<0), +} GDBusCapabilityFlags; + +/** + * GDBusCallFlags: + * @G_DBUS_CALL_FLAGS_NONE: No flags set. + * @G_DBUS_CALL_FLAGS_NO_AUTO_START: The bus must not launch + * an owner for the destination name in response to this method + * invocation. + * + * Flags used in g_dbus_connection_call() and similar APIs. + * + * Since: 2.26 + */ +typedef enum { + G_DBUS_CALL_FLAGS_NONE = 0, + G_DBUS_CALL_FLAGS_NO_AUTO_START = (1<<0), +} GDBusCallFlags; + +/** + * GDBusMessageType: + * @G_DBUS_MESSAGE_TYPE_INVALID: Message is of invalid type. + * @G_DBUS_MESSAGE_TYPE_METHOD_CALL: Method call. + * @G_DBUS_MESSAGE_TYPE_METHOD_RETURN: Method reply. + * @G_DBUS_MESSAGE_TYPE_ERROR: Error reply. + * @G_DBUS_MESSAGE_TYPE_SIGNAL: Signal emission. + * + * Message types used in #GDBusMessage. + * + * Since: 2.26 + */ +typedef enum { + G_DBUS_MESSAGE_TYPE_INVALID, + G_DBUS_MESSAGE_TYPE_METHOD_CALL, + G_DBUS_MESSAGE_TYPE_METHOD_RETURN, + G_DBUS_MESSAGE_TYPE_ERROR, + G_DBUS_MESSAGE_TYPE_SIGNAL +} GDBusMessageType; + +/** + * GDBusMessageFlags: + * @G_DBUS_MESSAGE_FLAGS_NONE: No flags set. + * @G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED: A reply is not expected. + * @G_DBUS_MESSAGE_FLAGS_NO_AUTO_START: The bus must not launch an + * owner for the destination name in response to this message. + * + * Message flags used in #GDBusMessage. + * + * Since: 2.26 + */ +typedef enum { + G_DBUS_MESSAGE_FLAGS_NONE = 0, + G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED = (1<<0), + G_DBUS_MESSAGE_FLAGS_NO_AUTO_START = (1<<1) +} GDBusMessageFlags; + +/** + * GDBusMessageHeaderField: + * @G_DBUS_MESSAGE_HEADER_FIELD_INVALID: Not a valid header field. + * @G_DBUS_MESSAGE_HEADER_FIELD_PATH: The object path. + * @G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE: The interface name. + * @G_DBUS_MESSAGE_HEADER_FIELD_MEMBER: The method or signal name. + * @G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME: The name of the error that occurred. + * @G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL: The serial number the message is a reply to. + * @G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION: The name the message is intended for. + * @G_DBUS_MESSAGE_HEADER_FIELD_SENDER: Unique name of the sender of the message (filled in by the bus). + * @G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE: The signature of the message body. + * @G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS: The number of UNIX file descriptors that accompany the message. + * + * Header fields used in #GDBusMessage. + * + * Since: 2.26 + */ +typedef enum { + G_DBUS_MESSAGE_HEADER_FIELD_INVALID, + G_DBUS_MESSAGE_HEADER_FIELD_PATH, + G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE, + G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, + G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME, + G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, + G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION, + G_DBUS_MESSAGE_HEADER_FIELD_SENDER, + G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE, + G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS +} GDBusMessageHeaderField; + +/** + * GDBusPropertyInfoFlags: + * @G_DBUS_PROPERTY_INFO_FLAGS_NONE: No flags set. + * @G_DBUS_PROPERTY_INFO_FLAGS_READABLE: Property is readable. + * @G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE: Property is writable. + * + * Flags describing the access control of a D-Bus property. + * + * Since: 2.26 + */ +typedef enum +{ + G_DBUS_PROPERTY_INFO_FLAGS_NONE = 0, + G_DBUS_PROPERTY_INFO_FLAGS_READABLE = (1<<0), + G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE = (1<<1), +} GDBusPropertyInfoFlags; + +/** + * GDBusSubtreeFlags: + * @G_DBUS_SUBTREE_FLAGS_NONE: No flags set. + * @G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES: Method calls to objects not in the enumerated range + * will still be dispatched. This is useful if you want + * to dynamically spawn objects in the subtree. + * + * Flags passed to g_dbus_connection_register_subtree(). + * + * Since: 2.26 + */ +typedef enum +{ + G_DBUS_SUBTREE_FLAGS_NONE = 0, + G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES = (1<<0), +} GDBusSubtreeFlags; + +/** + * GDBusServerFlags: + * @G_DBUS_SERVER_FLAGS_NONE: No flags set. + * @G_DBUS_SERVER_FLAGS_RUN_IN_THREAD: All #GDBusServer::new-connection + * signals will run in separated dedicated threads (see signal for + * details). + * @G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS: Allow the anonymous + * authentication method. + * + * Flags used when creating a #GDBusServer. + * + * Since: 2.26 + */ +typedef enum +{ + G_DBUS_SERVER_FLAGS_NONE = 0, + G_DBUS_SERVER_FLAGS_RUN_IN_THREAD = (1<<0), + G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS = (1<<1) +} GDBusServerFlags; + G_END_DECLS #endif /* __GIO_ENUMS_H__ */ diff --git a/gio/giotypes.h b/gio/giotypes.h index 2abc87dd6..1c635d501 100644 --- a/gio/giotypes.h +++ b/gio/giotypes.h @@ -333,6 +333,26 @@ struct _GOutputVector { gsize size; }; +typedef struct _GCredentials GCredentials; +typedef struct _GUnixCredentialsMessage GUnixCredentialsMessage; +typedef struct _GUnixFDList GUnixFDList; +typedef struct _GDBusMessage GDBusMessage; +typedef struct _GDBusConnection GDBusConnection; +typedef struct _GMessageBusConnection GMessageBusConnection; +typedef struct _GDBusProxy GDBusProxy; +typedef struct _GDBusMethodInvocation GDBusMethodInvocation; +typedef struct _GDBusServer GDBusServer; +typedef struct _GDBusAuthObserver GDBusAuthObserver; +typedef struct _GDBusErrorEntry GDBusErrorEntry; +typedef struct _GDBusInterfaceVTable GDBusInterfaceVTable; +typedef struct _GDBusSubtreeVTable GDBusSubtreeVTable; +typedef struct _GDBusAnnotationInfo GDBusAnnotationInfo; +typedef struct _GDBusArgInfo GDBusArgInfo; +typedef struct _GDBusMethodInfo GDBusMethodInfo; +typedef struct _GDBusSignalInfo GDBusSignalInfo; +typedef struct _GDBusPropertyInfo GDBusPropertyInfo; +typedef struct _GDBusInterfaceInfo GDBusInterfaceInfo; +typedef struct _GDBusNodeInfo GDBusNodeInfo; G_END_DECLS diff --git a/gio/gunixconnection.c b/gio/gunixconnection.c index d56b3f873..3921c92d2 100644 --- a/gio/gunixconnection.c +++ b/gio/gunixconnection.c @@ -41,6 +41,14 @@ #include #include +#ifdef __linux__ +/* for getsockopt() and setsockopt() */ +#include /* See NOTES */ +#include +#include +#include +#endif + #include "gioalias.h" G_DEFINE_TYPE_WITH_CODE (GUnixConnection, g_unix_connection, @@ -292,5 +300,253 @@ gboolean g_unix_connection_create_pair (GUnixCo GError **error); */ + +/** + * g_unix_connection_send_credentials: + * @connection: A #GUnixConnection. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Passes the credentials of the current user the receiving side + * of the connection. The recieving end has to call + * g_unix_connection_receive_credentials() (or similar) to accept the + * credentials. + * + * As well as sending the credentials this also writes a single NUL + * byte to the stream, as this is required for credentials passing to + * work on some implementations. + * + * Note that this function only works on Linux, currently. + * + * Returns: %TRUE on success, %FALSE if @error is set. + * + * Since: 2.26 + */ +gboolean +g_unix_connection_send_credentials (GUnixConnection *connection, + GCancellable *cancellable, + GError **error) +{ + GCredentials *credentials; + GSocketControlMessage *scm; + GSocket *socket; + gboolean ret; + GOutputVector vector; + guchar nul_byte[1] = {'\0'}; + + g_return_val_if_fail (G_IS_UNIX_CONNECTION (connection), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = FALSE; + + credentials = g_credentials_new (); + + vector.buffer = &nul_byte; + vector.size = 1; + scm = g_unix_credentials_message_new_with_credentials (credentials); + g_object_get (connection, "socket", &socket, NULL); + if (g_socket_send_message (socket, + NULL, /* address */ + &vector, + 1, + &scm, + 1, + G_SOCKET_MSG_NONE, + cancellable, + error) != 1) + { + g_prefix_error (error, _("Error sending credentials: ")); + goto out; + } + + ret = TRUE; + + out: + g_object_unref (socket); + g_object_unref (scm); + g_object_unref (credentials); + return ret; +} + +/** + * g_unix_connection_receive_credentials: + * @connection: A #GUnixConnection. + * @cancellable: A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Receives credentials from the sending end of the connection. The + * sending end has to call g_unix_connection_send_credentials() (or + * similar) for this to work. + * + * As well as reading the credentials this also reads (and discards) a + * single byte from the stream, as this is required for credentials + * passing to work on some implementations. + * + * Returns: Received credentials on success (free with + * g_object_unref()), %NULL if @error is set. + * + * Since: 2.26 + */ +GCredentials * +g_unix_connection_receive_credentials (GUnixConnection *connection, + GCancellable *cancellable, + GError **error) +{ + GCredentials *ret; + GSocketControlMessage **scms; + gint nscm; + GSocket *socket; + gint n; + volatile GType credentials_message_gtype; + gssize num_bytes_read; +#ifdef __linux__ + gboolean turn_off_so_passcreds; +#endif + + g_return_val_if_fail (G_IS_UNIX_CONNECTION (connection), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = NULL; + scms = NULL; + + g_object_get (connection, "socket", &socket, NULL); + + /* On Linux, we need to turn on SO_PASSCRED if it isn't enabled + * already. We also need to turn it off when we're done. See + * #617483 for more discussion. + */ +#ifdef __linux__ + { + gint opt_val; + socklen_t opt_len; + + turn_off_so_passcreds = FALSE; + opt_val = 0; + opt_len = sizeof (gint); + if (getsockopt (g_socket_get_fd (socket), + SOL_SOCKET, + SO_PASSCRED, + &opt_val, + &opt_len) != 0) + { + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error checking if SO_PASSCRED is enabled for socket: %s"), + strerror (errno)); + goto out; + } + if (opt_len != sizeof (gint)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Unexpected option length while checking if SO_PASSCRED is enabled for socket. " + "Expected %d bytes, got %d"), + (gint) sizeof (gint), (gint) opt_len); + goto out; + } + if (opt_val == 0) + { + opt_val = 1; + if (setsockopt (g_socket_get_fd (socket), + SOL_SOCKET, + SO_PASSCRED, + &opt_val, + sizeof opt_val) != 0) + { + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error enabling SO_PASSCRED: %s"), + strerror (errno)); + goto out; + } + turn_off_so_passcreds = TRUE; + } + } +#endif + + /* ensure the type of GUnixCredentialsMessage has been registered with the type system */ + credentials_message_gtype = G_TYPE_UNIX_CREDENTIALS_MESSAGE; + num_bytes_read = g_socket_receive_message (socket, + NULL, /* GSocketAddress **address */ + NULL, + 0, + &scms, + &nscm, + NULL, + cancellable, + error); + if (num_bytes_read != 1) + { + /* Handle situation where g_socket_receive_message() returns + * 0 bytes and not setting @error + */ + if (num_bytes_read == 0 && error != NULL && *error == NULL) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Expecting to read a single byte for receiving credentials but read zero bytes")); + } + goto out; + } + + if (nscm != 1) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Expecting 1 control message, got %d"), + nscm); + goto out; + } + + if (!G_IS_UNIX_CREDENTIALS_MESSAGE (scms[0])) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Unexpected type of ancillary data")); + goto out; + } + + ret = g_unix_credentials_message_get_credentials (G_UNIX_CREDENTIALS_MESSAGE (scms[0])); + g_object_ref (ret); + + out: + +#ifdef __linux__ + if (turn_off_so_passcreds) + { + gint opt_val; + opt_val = 0; + if (setsockopt (g_socket_get_fd (socket), + SOL_SOCKET, + SO_PASSCRED, + &opt_val, + sizeof opt_val) != 0) + { + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error while disabling SO_PASSCRED: %s"), + strerror (errno)); + goto out; + } + } +#endif + + if (scms != NULL) + { + for (n = 0; n < nscm; n++) + g_object_unref (scms[n]); + g_free (scms); + } + g_object_unref (socket); + return ret; +} + #define __G_UNIX_CONNECTION_C__ #include "gioaliasdef.c" diff --git a/gio/gunixconnection.h b/gio/gunixconnection.h index 7435e9742..c38b0c9c1 100644 --- a/gio/gunixconnection.h +++ b/gio/gunixconnection.h @@ -71,6 +71,15 @@ gint g_unix_connection_receive_fd (GUnixCo GCancellable *cancellable, GError **error); +gboolean g_unix_connection_send_credentials (GUnixConnection *connection, + GCancellable *cancellable, + GError **error); + +GCredentials *g_unix_connection_receive_credentials (GUnixConnection *connection, + GCancellable *cancellable, + GError **error); + + G_END_DECLS #endif /* __G_UNIX_CONNECTION_H__ */ diff --git a/gio/gunixcredentialsmessage.c b/gio/gunixcredentialsmessage.c new file mode 100644 index 000000000..7285fbc45 --- /dev/null +++ b/gio/gunixcredentialsmessage.c @@ -0,0 +1,341 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2010 Red Hat, Inc. + * Copyright (C) 2009 Codethink Limited + * + * This program 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 of the licence or (at + * your option) any later version. + * + * See the included COPYING file for more information. + * + * Authors: David Zeuthen + */ + +/** + * SECTION: gunixcredentialsmessage + * @title: GUnixCredentialsMessage + * @short_description: A GSocketControlMessage containing credentials + * @see_also: #GUnixConnection, #GSocketControlMessage + * + * This #GSocketControlMessage contains a #GCredentials instance. It + * may be sent using g_socket_send_message() and received using + * g_socket_receive_message() over UNIX sockets (ie: sockets in the + * %G_SOCKET_ADDRESS_UNIX family). + * + * For an easier way to send and receive credentials over + * stream-oriented UNIX sockets, see g_unix_connection_send_credentials() and + * g_unix_connection_receive_credentials(). + **/ + +#include "config.h" + +/* ---------------------------------------------------------------------------------------------------- */ +#ifdef __linux__ + +#define _GNU_SOURCE +#define __USE_GNU +#include +#include +#include +#include +#include +#define G_UNIX_CREDENTIALS_MESSAGE_SUPPORTED 1 + +#else +/* TODO: please add support for your UNIX flavor */ +#define G_UNIX_CREDENTIALS_MESSAGE_SUPPORTED 0 +#endif + +/* ---------------------------------------------------------------------------------------------------- */ + +#include +#include + +#include "gunixcredentialsmessage.h" +#include "gcredentials.h" + +#include "glibintl.h" +#include "gioalias.h" + +struct _GUnixCredentialsMessagePrivate +{ + GCredentials *credentials; +}; + +enum +{ + PROP_0, + PROP_CREDENTIALS +}; + +G_DEFINE_TYPE (GUnixCredentialsMessage, g_unix_credentials_message, G_TYPE_SOCKET_CONTROL_MESSAGE); + +static gsize +g_unix_credentials_message_get_size (GSocketControlMessage *message) +{ +#ifdef __linux__ + return sizeof (struct ucred); +#else + return 0; +#endif +} + +static int +g_unix_credentials_message_get_level (GSocketControlMessage *message) +{ + return SOL_SOCKET; +} + +static int +g_unix_credentials_message_get_msg_type (GSocketControlMessage *message) +{ +#ifdef __linux__ + return SCM_CREDENTIALS; +#else + return 0; +#endif +} + +static GSocketControlMessage * +g_unix_credentials_message_deserialize (gint level, + gint type, + gsize size, + gpointer data) +{ + GSocketControlMessage *message; + + message = NULL; + +#ifdef __linux__ + { + GCredentials *credentials; + struct ucred *ucred; + + if (level != SOL_SOCKET || type != SCM_CREDENTIALS) + goto out; + + if (size != sizeof (struct ucred)) + { + g_warning ("Expected a struct ucred (%" G_GSIZE_FORMAT " bytes) but " + "got %" G_GSIZE_FORMAT " bytes of data", + sizeof (struct ucred), + size); + goto out; + } + + ucred = data; + + credentials = g_credentials_new (); + g_credentials_set_native (credentials, ucred); + message = g_unix_credentials_message_new_with_credentials (credentials); + g_object_unref (credentials); + out: + ; + } +#endif + + return message; +} + +static void +g_unix_credentials_message_serialize (GSocketControlMessage *_message, + gpointer data) +{ + GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (_message); +#ifdef __linux__ + memcpy (data, g_credentials_get_native (message->priv->credentials), sizeof (struct ucred)); +#endif +} + +static void +g_unix_credentials_message_finalize (GObject *object) +{ + GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object); + + if (message->priv->credentials != NULL) + g_object_unref (message->priv->credentials); + + G_OBJECT_CLASS (g_unix_credentials_message_parent_class)->finalize (object); +} + +static void +g_unix_credentials_message_init (GUnixCredentialsMessage *message) +{ + message->priv = G_TYPE_INSTANCE_GET_PRIVATE (message, + G_TYPE_UNIX_CREDENTIALS_MESSAGE, + GUnixCredentialsMessagePrivate); +} + +static void +g_unix_credentials_message_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object); + + switch (prop_id) + { + case PROP_CREDENTIALS: + g_value_set_object (value, message->priv->credentials); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_unix_credentials_message_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object); + + switch (prop_id) + { + case PROP_CREDENTIALS: + message->priv->credentials = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_unix_credentials_message_constructed (GObject *object) +{ + GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object); + + if (message->priv->credentials == NULL) + message->priv->credentials = g_credentials_new (); + + if (G_OBJECT_CLASS (g_unix_credentials_message_parent_class)->constructed != NULL) + G_OBJECT_CLASS (g_unix_credentials_message_parent_class)->constructed (object); +} + +static void +g_unix_credentials_message_class_init (GUnixCredentialsMessageClass *class) +{ + GSocketControlMessageClass *scm_class; + GObjectClass *gobject_class; + + g_type_class_add_private (class, sizeof (GUnixCredentialsMessagePrivate)); + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->get_property = g_unix_credentials_message_get_property; + gobject_class->set_property = g_unix_credentials_message_set_property; + gobject_class->finalize = g_unix_credentials_message_finalize; + gobject_class->constructed = g_unix_credentials_message_constructed; + + scm_class = G_SOCKET_CONTROL_MESSAGE_CLASS (class); + scm_class->get_size = g_unix_credentials_message_get_size; + scm_class->get_level = g_unix_credentials_message_get_level; + scm_class->get_type = g_unix_credentials_message_get_msg_type; + scm_class->serialize = g_unix_credentials_message_serialize; + scm_class->deserialize = g_unix_credentials_message_deserialize; + + /** + * GUnixCredentialsMessage:credentials: + * + * The credentials stored in the message. + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, + PROP_CREDENTIALS, + g_param_spec_object ("credentials", + P_("Credentials"), + P_("The credentials stored in the message"), + G_TYPE_CREDENTIALS, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_unix_credentials_message_is_supported: + * + * Checks if passing a #GCredential on a #GSocket is supported on this platform. + * + * Returns: %TRUE if supported, %FALSE otherwise + * + * Since: 2.26 + */ +gboolean +g_unix_credentials_message_is_supported (void) +{ + return G_UNIX_CREDENTIALS_MESSAGE_SUPPORTED; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * g_unix_credentials_message_new: + * + * Creates a new #GUnixCredentialsMessage with credentials matching the current processes. + * + * Returns: a new #GUnixCredentialsMessage + * + * Since: 2.26 + */ +GSocketControlMessage * +g_unix_credentials_message_new (void) +{ + g_return_val_if_fail (g_unix_credentials_message_is_supported (), NULL); + return g_object_new (G_TYPE_UNIX_CREDENTIALS_MESSAGE, + NULL); +} + +/** + * g_unix_credentials_message_new_with_credentials: + * @credentials: A #GCredentials object. + * + * Creates a new #GUnixCredentialsMessage holding @credentials. + * + * Returns: a new #GUnixCredentialsMessage + * + * Since: 2.26 + */ +GSocketControlMessage * +g_unix_credentials_message_new_with_credentials (GCredentials *credentials) +{ + g_return_val_if_fail (G_IS_CREDENTIALS (credentials), NULL); + g_return_val_if_fail (g_unix_credentials_message_is_supported (), NULL); + return g_object_new (G_TYPE_UNIX_CREDENTIALS_MESSAGE, + "credentials", credentials, + NULL); +} + +/** + * g_unix_credentials_message_get_credentials: + * @message: A #GUnixCredentialsMessage. + * + * Gets the credentials stored in @message. + * + * Returns: A #GCredentials instance. Do not free, it is owned by @message. + * + * Since: 2.26 + */ +GCredentials * +g_unix_credentials_message_get_credentials (GUnixCredentialsMessage *message) +{ + g_return_val_if_fail (G_IS_UNIX_CREDENTIALS_MESSAGE (message), NULL); + return message->priv->credentials; +} + + +#define __G_UNIX_CREDENTIALS_MESSAGE_C__ +#include "gioaliasdef.c" diff --git a/gio/gunixcredentialsmessage.h b/gio/gunixcredentialsmessage.h new file mode 100644 index 000000000..7f444d5bf --- /dev/null +++ b/gio/gunixcredentialsmessage.h @@ -0,0 +1,83 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2010 Red Hat, Inc. + * Copyright (C) 2009 Codethink Limited + * + * This program 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 of the licence 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: David Zeuthen + */ + +#ifndef __G_UNIX_CREDENTIALS_MESSAGE_H__ +#define __G_UNIX_CREDENTIALS_MESSAGE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define G_TYPE_UNIX_CREDENTIALS_MESSAGE (g_unix_credentials_message_get_type ()) +#define G_UNIX_CREDENTIALS_MESSAGE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_UNIX_CREDENTIALS_MESSAGE, GUnixCredentialsMessage)) +#define G_UNIX_CREDENTIALS_MESSAGE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), G_TYPE_UNIX_CREDENTIALS_MESSAGE, GUnixCredentialsMessageClass)) +#define G_IS_UNIX_CREDENTIALS_MESSAGE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_UNIX_CREDENTIALS_MESSAGE)) +#define G_IS_UNIX_CREDENTIALS_MESSAGE_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), G_TYPE_UNIX_CREDENTIALS_MESSAGE)) +#define G_UNIX_CREDENTIALS_MESSAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_UNIX_CREDENTIALS_MESSAGE, GUnixCredentialsMessageClass)) + +typedef struct _GUnixCredentialsMessagePrivate GUnixCredentialsMessagePrivate; +typedef struct _GUnixCredentialsMessageClass GUnixCredentialsMessageClass; + +/** + * GUnixCredentialsMessageClass: + * + * Class structure for #GUnixCredentialsMessage. + * + * Since: 2.26 + */ +struct _GUnixCredentialsMessageClass +{ + GSocketControlMessageClass parent_class; + + /*< private >*/ + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); +}; + +/** + * GUnixCredentialsMessage: + * + * The #GUnixCredentialsMessage structure contains only private data + * and should only be accessed using the provided API. + * + * Since: 2.26 + */ +struct _GUnixCredentialsMessage +{ + GSocketControlMessage parent_instance; + GUnixCredentialsMessagePrivate *priv; +}; + +GType g_unix_credentials_message_get_type (void) G_GNUC_CONST; +GSocketControlMessage *g_unix_credentials_message_new (void); +GSocketControlMessage *g_unix_credentials_message_new_with_credentials (GCredentials *credentials); +GCredentials *g_unix_credentials_message_get_credentials (GUnixCredentialsMessage *message); + +gboolean g_unix_credentials_message_is_supported (void); + +G_END_DECLS + +#endif /* __G_UNIX_CREDENTIALS_MESSAGE_H__ */ diff --git a/gio/gunixfdlist.h b/gio/gunixfdlist.h index 638b685e7..12b6ee830 100644 --- a/gio/gunixfdlist.h +++ b/gio/gunixfdlist.h @@ -23,7 +23,7 @@ #ifndef __G_UNIX_FD_LIST_H__ #define __G_UNIX_FD_LIST_H__ -#include +#include G_BEGIN_DECLS @@ -41,7 +41,6 @@ G_BEGIN_DECLS typedef struct _GUnixFDListPrivate GUnixFDListPrivate; typedef struct _GUnixFDListClass GUnixFDListClass; -typedef struct _GUnixFDList GUnixFDList; struct _GUnixFDListClass { diff --git a/gio/gunixfdmessage.c b/gio/gunixfdmessage.c index 90f87c34b..9d3fa442c 100644 --- a/gio/gunixfdmessage.c +++ b/gio/gunixfdmessage.c @@ -44,6 +44,7 @@ #include #include "gunixfdmessage.h" +#include "gunixfdlist.h" #include "gioerror.h" #include "gioalias.h" diff --git a/gio/gunixfdmessage.h b/gio/gunixfdmessage.h index 44b47c119..3bfa05822 100644 --- a/gio/gunixfdmessage.h +++ b/gio/gunixfdmessage.h @@ -23,8 +23,8 @@ #ifndef __G_UNIX_FD_MESSAGE_H__ #define __G_UNIX_FD_MESSAGE_H__ -#include #include +#include G_BEGIN_DECLS diff --git a/gio/pltcheck.sh b/gio/pltcheck.sh index f0500bf88..a6cbe4825 100755 --- a/gio/pltcheck.sh +++ b/gio/pltcheck.sh @@ -9,7 +9,7 @@ if ! which readelf 2>/dev/null >/dev/null; then exit 0 fi -SKIP='\\|\ + */ + +#include + +#ifdef G_OS_UNIX +#include +#endif + +/* ---------------------------------------------------------------------------------------------------- */ + +#ifdef G_OS_UNIX +static void +test_unix_address (void) +{ + g_assert (!g_dbus_is_supported_address ("some-imaginary-transport:foo=bar", NULL)); + g_assert (g_dbus_is_supported_address ("unix:path=/tmp/dbus-test", NULL)); + g_assert (g_dbus_is_supported_address ("unix:abstract=/tmp/dbus-another-test", NULL)); + g_assert (g_dbus_is_address ("unix:foo=bar")); + g_assert (!g_dbus_is_supported_address ("unix:foo=bar", NULL)); + g_assert (!g_dbus_is_address ("unix:path=/foo;abstract=/bar")); + g_assert (!g_dbus_is_supported_address ("unix:path=/foo;abstract=/bar", NULL)); + g_assert (g_dbus_is_supported_address ("unix:path=/tmp/concrete;unix:abstract=/tmp/abstract", NULL)); + g_assert (g_dbus_is_address ("some-imaginary-transport:foo=bar")); + + g_assert (g_dbus_is_address ("some-imaginary-transport:foo=bar;unix:path=/this/is/valid")); + g_assert (!g_dbus_is_supported_address ("some-imaginary-transport:foo=bar;unix:path=/this/is/valid", NULL)); +} +#endif + +static void +test_nonce_tcp_address (void) +{ + g_assert (g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar", NULL)); + g_assert (g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar,family=ipv6", NULL)); + g_assert (g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar,family=ipv4", NULL)); + + g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar,family=blah", NULL)); + g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=420000,noncefile=/foo/bar,family=ipv4", NULL)); + g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=,port=x42,noncefile=/foo/bar,family=ipv4", NULL)); + g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=,port=42x,noncefile=/foo/bar,family=ipv4", NULL)); + g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=,port=420000,noncefile=/foo/bar,family=ipv4", NULL)); +} + +int +main (int argc, + char *argv[]) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + +#ifdef G_OS_UNIX + g_test_add_func ("/gdbus/unix-address", test_unix_address); +#endif + g_test_add_func ("/gdbus/nonce-tcp-address", test_nonce_tcp_address); + return g_test_run(); +} + diff --git a/gio/tests/gdbus-connection.c b/gio/tests/gdbus-connection.c new file mode 100644 index 000000000..266512f3e --- /dev/null +++ b/gio/tests/gdbus-connection.c @@ -0,0 +1,653 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include +#include +#include + +#include "gdbus-tests.h" + +/* all tests rely on a shared mainloop */ +static GMainLoop *loop = NULL; + +/* ---------------------------------------------------------------------------------------------------- */ +/* Connection life-cycle testing */ +/* ---------------------------------------------------------------------------------------------------- */ + +static void +test_connection_life_cycle (void) +{ + GDBusConnection *c; + GDBusConnection *c2; + GError *error; + + error = NULL; + + /* + * Check for correct behavior when no bus is present + * + */ + c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + _g_assert_error_domain (error, G_IO_ERROR); + g_assert (!g_dbus_error_is_remote_error (error)); + g_assert (c == NULL); + g_error_free (error); + error = NULL; + + /* + * Check for correct behavior when a bus is present + */ + session_bus_up (); + /* case 1 */ + c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert (c != NULL); + g_assert (!g_dbus_connection_is_closed (c)); + + /* + * Check that singleton handling work + */ + c2 = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert (c2 != NULL); + g_assert (c == c2); + g_object_unref (c2); + + /* + * Check that private connections work + */ + c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert (c2 != NULL); + g_assert (c != c2); + g_object_unref (c2); + + c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert (c2 != NULL); + g_assert (!g_dbus_connection_is_closed (c2)); + g_dbus_connection_close (c2); + _g_assert_signal_received (c2, "closed"); + g_assert (g_dbus_connection_is_closed (c2)); + g_object_unref (c2); + + /* + * Check for correct behavior when the bus goes away + * + */ + g_assert (!g_dbus_connection_is_closed (c)); + g_dbus_connection_set_exit_on_close (c, FALSE); + session_bus_down (); + if (!g_dbus_connection_is_closed (c)) + _g_assert_signal_received (c, "closed"); + g_assert (g_dbus_connection_is_closed (c)); + + _g_object_wait_for_single_ref (c); + g_object_unref (c); +} + +/* ---------------------------------------------------------------------------------------------------- */ +/* Test that sending and receiving messages work as expected */ +/* ---------------------------------------------------------------------------------------------------- */ + +static void +msg_cb_expect_error_disconnected (GDBusConnection *connection, + GAsyncResult *res, + gpointer user_data) +{ + GError *error; + GVariant *result; + + error = NULL; + result = g_dbus_connection_call_finish (connection, + res, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED); + g_assert (!g_dbus_error_is_remote_error (error)); + g_error_free (error); + g_assert (result == NULL); + + g_main_loop_quit (loop); +} + +static void +msg_cb_expect_error_unknown_method (GDBusConnection *connection, + GAsyncResult *res, + gpointer user_data) +{ + GError *error; + GVariant *result; + + error = NULL; + result = g_dbus_connection_call_finish (connection, + res, + &error); + g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD); + g_assert (g_dbus_error_is_remote_error (error)); + g_assert (result == NULL); + + g_main_loop_quit (loop); +} + +static void +msg_cb_expect_success (GDBusConnection *connection, + GAsyncResult *res, + gpointer user_data) +{ + GError *error; + GVariant *result; + + error = NULL; + result = g_dbus_connection_call_finish (connection, + res, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_variant_unref (result); + + g_main_loop_quit (loop); +} + +static void +msg_cb_expect_error_cancelled (GDBusConnection *connection, + GAsyncResult *res, + gpointer user_data) +{ + GError *error; + GVariant *result; + + error = NULL; + result = g_dbus_connection_call_finish (connection, + res, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_assert (!g_dbus_error_is_remote_error (error)); + g_error_free (error); + g_assert (result == NULL); + + g_main_loop_quit (loop); +} + +static void +msg_cb_expect_error_cancelled_2 (GDBusConnection *connection, + GAsyncResult *res, + gpointer user_data) +{ + GError *error; + GVariant *result; + + error = NULL; + result = g_dbus_connection_call_finish (connection, + res, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_assert (!g_dbus_error_is_remote_error (error)); + g_error_free (error); + g_assert (result == NULL); + + g_main_loop_quit (loop); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +test_connection_send (void) +{ + GDBusConnection *c; + GCancellable *ca; + + session_bus_up (); + + /* First, get an unopened connection */ + c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + g_assert (c != NULL); + g_assert (!g_dbus_connection_is_closed (c)); + + /* + * Check that we never actually send a message if the GCancellable + * is already cancelled - i.e. we should get #G_IO_ERROR_CANCELLED + * when the actual connection is not up. + */ + ca = g_cancellable_new (); + g_cancellable_cancel (ca); + g_dbus_connection_call (c, + "org.freedesktop.DBus", /* bus_name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "GetId", /* method name */ + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + ca, + (GAsyncReadyCallback) msg_cb_expect_error_cancelled, + NULL); + g_main_loop_run (loop); + g_object_unref (ca); + + /* + * Check that we get a reply to the GetId() method call. + */ + g_dbus_connection_call (c, + "org.freedesktop.DBus", /* bus_name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "GetId", /* method name */ + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) msg_cb_expect_success, + NULL); + g_main_loop_run (loop); + + /* + * Check that we get an error reply to the NonExistantMethod() method call. + */ + g_dbus_connection_call (c, + "org.freedesktop.DBus", /* bus_name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "NonExistantMethod", /* method name */ + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) msg_cb_expect_error_unknown_method, + NULL); + g_main_loop_run (loop); + + /* + * Check that cancellation works when the message is already in flight. + */ + ca = g_cancellable_new (); + g_dbus_connection_call (c, + "org.freedesktop.DBus", /* bus_name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "GetId", /* method name */ + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + ca, + (GAsyncReadyCallback) msg_cb_expect_error_cancelled_2, + NULL); + g_cancellable_cancel (ca); + g_main_loop_run (loop); + g_object_unref (ca); + + /* + * Check that we get an error when sending to a connection that is disconnected. + */ + g_dbus_connection_set_exit_on_close (c, FALSE); + session_bus_down (); + _g_assert_signal_received (c, "closed"); + g_assert (g_dbus_connection_is_closed (c)); + + g_dbus_connection_call (c, + "org.freedesktop.DBus", /* bus_name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "GetId", /* method name */ + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) msg_cb_expect_error_disconnected, + NULL); + g_main_loop_run (loop); + + _g_object_wait_for_single_ref (c); + g_object_unref (c); +} + +/* ---------------------------------------------------------------------------------------------------- */ +/* Connection signal tests */ +/* ---------------------------------------------------------------------------------------------------- */ + +static void +test_connection_signal_handler (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + gint *counter = user_data; + *counter += 1; + + /*g_debug ("in test_connection_signal_handler (sender=%s path=%s interface=%s member=%s)", + sender_name, + object_path, + interface_name, + signal_name);*/ + + g_main_loop_quit (loop); +} + +static gboolean +test_connection_signal_quit_mainloop (gpointer user_data) +{ + gboolean *quit_mainloop_fired = user_data; + *quit_mainloop_fired = TRUE; + g_main_loop_quit (loop); + return TRUE; +} + +static void +test_connection_signals (void) +{ + GDBusConnection *c1; + GDBusConnection *c2; + GDBusConnection *c3; + guint s1; + guint s2; + guint s3; + gint count_s1; + gint count_s2; + gint count_name_owner_changed; + GError *error; + gboolean ret; + GVariant *result; + + error = NULL; + + /* + * Bring up first separate connections + */ + session_bus_up (); + /* if running with dbus-monitor, it claims the name :1.0 - so if we don't run with the monitor + * emulate this + */ + if (g_getenv ("G_DBUS_MONITOR") == NULL) + { + c1 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL); + g_assert (c1 != NULL); + g_assert (!g_dbus_connection_is_closed (c1)); + g_object_unref (c1); + } + c1 = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + g_assert (c1 != NULL); + g_assert (!g_dbus_connection_is_closed (c1)); + g_assert_cmpstr (g_dbus_connection_get_unique_name (c1), ==, ":1.1"); + + /* + * Install two signal handlers for the first connection + * + * - Listen to the signal "Foo" from :1.2 (e.g. c2) + * - Listen to the signal "Foo" from anyone (e.g. both c2 and c3) + * + * and then count how many times this signal handler was invoked. + */ + s1 = g_dbus_connection_signal_subscribe (c1, + ":1.2", + "org.gtk.GDBus.ExampleInterface", + "Foo", + "/org/gtk/GDBus/ExampleInterface", + NULL, + test_connection_signal_handler, + &count_s1, + NULL); + s2 = g_dbus_connection_signal_subscribe (c1, + NULL, /* match any sender */ + "org.gtk.GDBus.ExampleInterface", + "Foo", + "/org/gtk/GDBus/ExampleInterface", + NULL, + test_connection_signal_handler, + &count_s2, + NULL); + s3 = g_dbus_connection_signal_subscribe (c1, + "org.freedesktop.DBus", /* sender */ + "org.freedesktop.DBus", /* interface */ + "NameOwnerChanged", /* member */ + "/org/freedesktop/DBus", /* path */ + NULL, + test_connection_signal_handler, + &count_name_owner_changed, + NULL); + g_assert (s1 != 0); + g_assert (s2 != 0); + g_assert (s3 != 0); + + count_s1 = 0; + count_s2 = 0; + count_name_owner_changed = 0; + + /* + * Bring up two other connections + */ + c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL); + g_assert (c2 != NULL); + g_assert (!g_dbus_connection_is_closed (c2)); + g_assert_cmpstr (g_dbus_connection_get_unique_name (c2), ==, ":1.2"); + c3 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL); + g_assert (c3 != NULL); + g_assert (!g_dbus_connection_is_closed (c3)); + g_assert_cmpstr (g_dbus_connection_get_unique_name (c3), ==, ":1.3"); + + /* + * Make c2 emit "Foo" - we should catch it twice + * + * Note that there is no way to be sure that the signal subscriptions + * on c1 are effective yet - for all we know, the AddMatch() messages + * could sit waiting in a buffer somewhere between this process and + * the message bus. And emitting signals on c2 (a completely other + * socket!) will not necessarily change this. + * + * To ensure this is not the case, do a synchronous call on c1. + */ + result = g_dbus_connection_call_sync (c1, + "org.freedesktop.DBus", /* bus name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "GetId", /* method name */ + NULL, /* parameters */ + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_variant_unref (result); + /* now, emit the signal on c2 */ + ret = g_dbus_connection_emit_signal (c2, + NULL, /* destination bus name */ + "/org/gtk/GDBus/ExampleInterface", + "org.gtk.GDBus.ExampleInterface", + "Foo", + NULL, + &error); + g_assert_no_error (error); + g_assert (ret); + while (!(count_s1 == 1 && count_s2 == 1)) + g_main_loop_run (loop); + g_assert_cmpint (count_s1, ==, 1); + g_assert_cmpint (count_s2, ==, 1); + + /* + * Make c3 emit "Foo" - we should catch it only once + */ + ret = g_dbus_connection_emit_signal (c3, + NULL, /* destination bus name */ + "/org/gtk/GDBus/ExampleInterface", + "org.gtk.GDBus.ExampleInterface", + "Foo", + NULL, + &error); + g_assert_no_error (error); + g_assert (ret); + while (!(count_s1 == 1 && count_s2 == 2)) + g_main_loop_run (loop); + g_assert_cmpint (count_s1, ==, 1); + g_assert_cmpint (count_s2, ==, 2); + + /* + * Also to check the total amount of NameOwnerChanged signals - use a 5 second ceiling + * to avoid spinning forever + */ + gboolean quit_mainloop_fired; + guint quit_mainloop_id; + quit_mainloop_fired = FALSE; + quit_mainloop_id = g_timeout_add (5000, test_connection_signal_quit_mainloop, &quit_mainloop_fired); + while (count_name_owner_changed != 2 && !quit_mainloop_fired) + g_main_loop_run (loop); + g_source_remove (quit_mainloop_id); + g_assert_cmpint (count_s1, ==, 1); + g_assert_cmpint (count_s2, ==, 2); + g_assert_cmpint (count_name_owner_changed, ==, 2); + + g_dbus_connection_signal_unsubscribe (c1, s1); + g_dbus_connection_signal_unsubscribe (c1, s2); + g_dbus_connection_signal_unsubscribe (c1, s3); + + _g_object_wait_for_single_ref (c1); + _g_object_wait_for_single_ref (c2); + _g_object_wait_for_single_ref (c3); + + g_object_unref (c1); + g_object_unref (c2); + g_object_unref (c3); + + session_bus_down (); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + guint num_handled; + guint32 serial; +} FilterData; + +static gboolean +filter_func (GDBusConnection *connection, + GDBusMessage *message, + gpointer user_data) +{ + FilterData *data = user_data; + guint32 reply_serial; + + reply_serial = g_dbus_message_get_reply_serial (message); + if (reply_serial == data->serial) + data->num_handled += 1; + + return FALSE; +} + +static void +test_connection_filter (void) +{ + GDBusConnection *c; + FilterData data; + GDBusMessage *m; + GDBusMessage *r; + GError *error; + guint filter_id; + + memset (&data, '\0', sizeof (FilterData)); + + session_bus_up (); + + error = NULL; + c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert (c != NULL); + + filter_id = g_dbus_connection_add_filter (c, + filter_func, + &data, + NULL); + + m = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */ + "/org/freedesktop/DBus", /* path */ + "org.freedesktop.DBus", /* interface */ + "GetNameOwner"); + g_dbus_message_set_body (m, g_variant_new ("(s)", "org.freedesktop.DBus")); + error = NULL; + g_dbus_connection_send_message (c, m, &data.serial, &error); + g_assert_no_error (error); + + while (data.num_handled == 0) + g_thread_yield (); + + g_dbus_connection_send_message (c, m, &data.serial, &error); + g_assert_no_error (error); + + while (data.num_handled == 1) + g_thread_yield (); + + r = g_dbus_connection_send_message_with_reply_sync (c, + m, + -1, + &data.serial, + NULL, /* GCancellable */ + &error); + g_assert_no_error (error); + g_assert (r != NULL); + g_object_unref (r); + g_assert_cmpint (data.num_handled, ==, 3); + + g_dbus_connection_remove_filter (c, filter_id); + + r = g_dbus_connection_send_message_with_reply_sync (c, + m, + -1, + &data.serial, + NULL, /* GCancellable */ + &error); + g_assert_no_error (error); + g_assert (r != NULL); + g_object_unref (r); + g_assert_cmpint (data.num_handled, ==, 3); + + _g_object_wait_for_single_ref (c); + g_object_unref (c); + g_object_unref (m); + + session_bus_down (); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +int +main (int argc, + char *argv[]) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + + /* all the tests rely on a shared main loop */ + loop = g_main_loop_new (NULL, FALSE); + + /* all the tests use a session bus with a well-known address that we can bring up and down + * using session_bus_up() and session_bus_down(). + */ + g_unsetenv ("DISPLAY"); + g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE); + + g_test_add_func ("/gdbus/connection-life-cycle", test_connection_life_cycle); + g_test_add_func ("/gdbus/connection-send", test_connection_send); + g_test_add_func ("/gdbus/connection-signals", test_connection_signals); + g_test_add_func ("/gdbus/connection-filter", test_connection_filter); + return g_test_run(); +} diff --git a/gio/tests/gdbus-error.c b/gio/tests/gdbus-error.c new file mode 100644 index 000000000..2231ca863 --- /dev/null +++ b/gio/tests/gdbus-error.c @@ -0,0 +1,198 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include +#include +#include + +/* ---------------------------------------------------------------------------------------------------- */ +/* Test that registered errors are properly mapped */ +/* ---------------------------------------------------------------------------------------------------- */ + +static void +check_registered_error (const gchar *given_dbus_error_name, + GQuark error_domain, + gint error_code) +{ + GError *error; + gchar *dbus_error_name; + + error = g_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message"); + g_assert_error (error, error_domain, error_code); + g_assert (g_dbus_error_is_remote_error (error)); + g_assert (g_dbus_error_strip_remote_error (error)); + g_assert_cmpstr (error->message, ==, "test message"); + dbus_error_name = g_dbus_error_get_remote_error (error); + g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name); + g_free (dbus_error_name); + g_error_free (error); +} + +static void +test_registered_errors (void) +{ + /* Here we check that we are able to map to GError and back for registered + * errors. + * + * For example, if "org.freedesktop.DBus.Error.AddressInUse" is + * associated with (G_DBUS_ERROR, G_DBUS_ERROR_DBUS_FAILED), check + * that + * + * - Creating a GError for e.g. "org.freedesktop.DBus.Error.AddressInUse" + * has (error_domain, code) == (G_DBUS_ERROR, G_DBUS_ERROR_DBUS_FAILED) + * + * - That it is possible to recover e.g. "org.freedesktop.DBus.Error.AddressInUse" + * as the D-Bus error name when dealing with an error with (error_domain, code) == + * (G_DBUS_ERROR, G_DBUS_ERROR_DBUS_FAILED) + * + * We just check a couple of well-known errors. + */ + check_registered_error ("org.freedesktop.DBus.Error.Failed", + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED); + check_registered_error ("org.freedesktop.DBus.Error.AddressInUse", + G_DBUS_ERROR, + G_DBUS_ERROR_ADDRESS_IN_USE); + check_registered_error ("org.freedesktop.DBus.Error.UnknownMethod", + G_DBUS_ERROR, + G_DBUS_ERROR_UNKNOWN_METHOD); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +check_unregistered_error (const gchar *given_dbus_error_name) +{ + GError *error; + gchar *dbus_error_name; + + error = g_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message"); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR); + g_assert (g_dbus_error_is_remote_error (error)); + dbus_error_name = g_dbus_error_get_remote_error (error); + g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name); + g_free (dbus_error_name); + + /* strip the message */ + g_assert (g_dbus_error_strip_remote_error (error)); + g_assert_cmpstr (error->message, ==, "test message"); + + /* check that we can no longer recover the D-Bus error name */ + g_assert (g_dbus_error_get_remote_error (error) == NULL); + + g_error_free (error); + +} + +static void +test_unregistered_errors (void) +{ + /* Here we check that we are able to map to GError and back for unregistered + * errors. + * + * For example, if "com.example.Error.Failed" is not registered, then check + * + * - Creating a GError for e.g. "com.example.Error.Failed" has (error_domain, code) == + * (G_IO_ERROR, G_IO_ERROR_DBUS_ERROR) + * + * - That it is possible to recover e.g. "com.example.Error.Failed" from that + * GError. + * + * We just check a couple of random errors. + */ + + check_unregistered_error ("com.example.Error.Failed"); + check_unregistered_error ("foobar.buh"); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +check_transparent_gerror (GQuark error_domain, + gint error_code) +{ + GError *error; + gchar *given_dbus_error_name; + gchar *dbus_error_name; + + error = g_error_new (error_domain, error_code, "test message"); + given_dbus_error_name = g_dbus_error_encode_gerror (error); + g_assert (g_str_has_prefix (given_dbus_error_name, "org.gtk.GDBus.UnmappedGError.Quark")); + g_error_free (error); + + error = g_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message"); + g_assert_error (error, error_domain, error_code); + g_assert (g_dbus_error_is_remote_error (error)); + dbus_error_name = g_dbus_error_get_remote_error (error); + g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name); + g_free (dbus_error_name); + g_free (given_dbus_error_name); + + /* strip the message */ + g_assert (g_dbus_error_strip_remote_error (error)); + g_assert_cmpstr (error->message, ==, "test message"); + + /* check that we can no longer recover the D-Bus error name */ + g_assert (g_dbus_error_get_remote_error (error) == NULL); + + g_error_free (error); +} + +static void +test_transparent_gerror (void) +{ + /* Here we check that we are able to transparent pass unregistered GError's + * over the wire. + * + * For example, if G_IO_ERROR_FAILED is not registered, then check + * + * - g_dbus_error_encode_gerror() returns something of the form + * org.gtk.GDBus.UnmappedGError.Quark_HEXENCODED_QUARK_NAME_.Code_ERROR_CODE + * + * - mapping back the D-Bus error name gives us G_IO_ERROR_FAILED + * + * - That it is possible to recover the D-Bus error name from the + * GError. + * + * We just check a couple of random errors. + */ + + check_transparent_gerror (G_IO_ERROR, G_IO_ERROR_FAILED); + check_transparent_gerror (G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE); +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +int +main (int argc, + char *argv[]) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/gdbus/registered-errors", test_registered_errors); + g_test_add_func ("/gdbus/unregistered-errors", test_unregistered_errors); + g_test_add_func ("/gdbus/transparent-gerror", test_transparent_gerror); + + return g_test_run(); +} diff --git a/gio/tests/gdbus-example-export.c b/gio/tests/gdbus-example-export.c new file mode 100644 index 000000000..5dda58da0 --- /dev/null +++ b/gio/tests/gdbus-example-export.c @@ -0,0 +1,335 @@ +#include +#include + +/* ---------------------------------------------------------------------------------------------------- */ + +/* The object we want to export */ +typedef struct _MyObjectClass MyObjectClass; +typedef struct _MyObject MyObject; + +struct _MyObjectClass +{ + GObjectClass parent_class; +}; + +struct _MyObject +{ + GObject parent_instance; + + gint count; + gchar *name; +}; + +enum +{ + PROP_0, + PROP_COUNT, + PROP_NAME +}; + +G_DEFINE_TYPE (MyObject, my_object, G_TYPE_OBJECT); + +static void +my_object_finalize (GObject *object) +{ + MyObject *myobj = (MyObject*)object; + + g_free (myobj->name); + + G_OBJECT_CLASS (my_object_parent_class)->finalize (object); +} + +static void +my_object_init (MyObject *object) +{ + object->count = 0; + object->name = NULL; +} + +static void +my_object_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MyObject *myobj = (MyObject*)object; + + switch (prop_id) + { + case PROP_COUNT: + g_value_set_int (value, myobj->count); + break; + + case PROP_NAME: + g_value_set_string (value, myobj->name); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +my_object_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MyObject *myobj = (MyObject*)object; + + switch (prop_id) + { + case PROP_COUNT: + myobj->count = g_value_get_int (value); + break; + + case PROP_NAME: + g_free (myobj->name); + myobj->name = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +my_object_class_init (MyObjectClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->finalize = my_object_finalize; + gobject_class->set_property = my_object_set_property; + gobject_class->get_property = my_object_get_property; + + g_object_class_install_property (gobject_class, + PROP_COUNT, + g_param_spec_int ("count", + "Count", + "Count", + 0, 99999, 0, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name", + NULL, + G_PARAM_READWRITE)); +} + +/* A method that we want to export */ +void +my_object_change_count (MyObject *myobj, + gint change) +{ + myobj->count = 2 * myobj->count + change; + + g_object_notify (G_OBJECT (myobj), "count"); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusNodeInfo *introspection_data = NULL; + +/* Introspection data for the service we are exporting */ +static const gchar introspection_xml[] = + "" + " " + " " + " " + " " + " " + " " + " " + ""; + + +static void +handle_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + MyObject *myobj = user_data; + + if (g_strcmp0 (method_name, "ChangeCount") == 0) + { + gint change; + g_variant_get (parameters, "(i)", &change); + + my_object_change_count (myobj, change); + + g_dbus_method_invocation_return_value (invocation, NULL); + } +} + +static GVariant * +handle_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GVariant *ret; + MyObject *myobj = user_data; + + ret = NULL; + if (g_strcmp0 (property_name, "Count") == 0) + { + ret = g_variant_new_int32 (myobj->count); + } + else if (g_strcmp0 (property_name, "Name") == 0) + { + ret = g_variant_new_string (myobj->name ? myobj->name : ""); + } + + return ret; +} + +static gboolean +handle_set_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, + gpointer user_data) +{ + MyObject *myobj = user_data; + + if (g_strcmp0 (property_name, "Count") == 0) + { + g_object_set (myobj, "count", g_variant_get_int32 (value), NULL); + } + else if (g_strcmp0 (property_name, "Name") == 0) + { + g_object_set (myobj, "name", g_variant_get_string (value, NULL), NULL); + } + + return TRUE; +} + + +/* for now */ +static const GDBusInterfaceVTable interface_vtable = +{ + handle_method_call, + handle_get_property, + handle_set_property +}; + +static void +send_property_change (GObject *obj, + GParamSpec *pspec, + GDBusConnection *connection) +{ + GVariantBuilder *builder; + GVariantBuilder *invalidated_builder; + MyObject *myobj = (MyObject *)obj; + + builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); + invalidated_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); + + if (g_strcmp0 (pspec->name, "count") == 0) + g_variant_builder_add (builder, + "{sv}", + "Count", g_variant_new_int32 (myobj->count)); + else if (g_strcmp0 (pspec->name, "name") == 0) + g_variant_builder_add (builder, + "{sv}", + "Name", g_variant_new_string (myobj->name ? myobj->name : "")); + + g_dbus_connection_emit_signal (connection, + NULL, + "/org/myorg/MyObject", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new ("(sa{sv}as)", + "org.myorg.MyObject", + builder, + invalidated_builder), + NULL); +} + +static void +on_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + MyObject *myobj = user_data; + guint registration_id; + + g_signal_connect (myobj, "notify", + G_CALLBACK (send_property_change), connection); + registration_id = g_dbus_connection_register_object (connection, + "/org/myorg/MyObject", + introspection_data->interfaces[0], + &interface_vtable, + myobj, + NULL, /* user_data_free_func */ + NULL); /* GError** */ + g_assert (registration_id > 0); +} + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ +} + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + exit (1); +} + +int +main (int argc, char *argv[]) +{ + guint owner_id; + GMainLoop *loop; + MyObject *myobj; + + g_type_init (); + + /* We are lazy here - we don't want to manually provide + * the introspection data structures - so we just build + * them from XML. + */ + introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + g_assert (introspection_data != NULL); + + myobj = g_object_new (my_object_get_type (), NULL); + + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.myorg.MyObject", + G_BUS_NAME_OWNER_FLAGS_NONE, + on_bus_acquired, + on_name_acquired, + on_name_lost, + myobj, + NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_bus_unown_name (owner_id); + + g_dbus_node_info_unref (introspection_data); + + g_object_unref (myobj); + + return 0; +} diff --git a/gio/tests/gdbus-example-own-name.c b/gio/tests/gdbus-example-own-name.c new file mode 100644 index 000000000..0466cd4c6 --- /dev/null +++ b/gio/tests/gdbus-example-own-name.c @@ -0,0 +1,86 @@ +#include + +static void +on_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + /* This is where we'd export some objects on the bus */ +} + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_print ("Acquired the name %s on the session bus\n", name); +} + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_print ("Lost the name %s on the session bus\n", name); +} + +int +main (int argc, char *argv[]) +{ + guint owner_id; + GMainLoop *loop; + GBusNameOwnerFlags flags; + gboolean opt_replace; + gboolean opt_allow_replacement; + gchar *opt_name; + GOptionContext *opt_context; + GError *error; + GOptionEntry opt_entries[] = + { + { "replace", 'r', 0, G_OPTION_ARG_NONE, &opt_replace, "Replace existing name if possible", NULL }, + { "allow-replacement", 'a', 0, G_OPTION_ARG_NONE, &opt_allow_replacement, "Allow replacement", NULL }, + { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name to acquire", NULL }, + { NULL} + }; + + g_type_init (); + + error = NULL; + opt_name = NULL; + opt_replace = FALSE; + opt_allow_replacement = FALSE; + opt_context = g_option_context_new ("g_bus_own_name() example"); + g_option_context_add_main_entries (opt_context, opt_entries, NULL); + if (!g_option_context_parse (opt_context, &argc, &argv, &error)) + { + g_printerr ("Error parsing options: %s", error->message); + return 1; + } + if (opt_name == NULL) + { + g_printerr ("Incorrect usage, try --help.\n"); + return 1; + } + + flags = G_BUS_NAME_OWNER_FLAGS_NONE; + if (opt_replace) + flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; + if (opt_allow_replacement) + flags |= G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; + + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + opt_name, + flags, + on_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_bus_unown_name (owner_id); + + return 0; +} diff --git a/gio/tests/gdbus-example-peer.c b/gio/tests/gdbus-example-peer.c new file mode 100644 index 000000000..649b0476b --- /dev/null +++ b/gio/tests/gdbus-example-peer.c @@ -0,0 +1,305 @@ +/* + +Usage examples (modulo addresses / credentials). + +UNIX domain socket transport: + + Server: + $ ./gdbus-example-peer --server --address unix:abstract=myaddr + Server is listening at: unix:abstract=myaddr + Client connected. + Peer credentials: GCredentials:unix-user=500,unix-group=500,unix-process=13378 + Negotiated capabilities: unix-fd-passing=1 + Client said: Hey, it's 1273093080 already! + + Client: + $ ./gdbus-example-peer --address unix:abstract=myaddr + Connected. + Negotiated capabilities: unix-fd-passing=1 + Server said: You said 'Hey, it's 1273093080 already!'. KTHXBYE! + +Nonce-secured TCP transport on the same host: + + Server: + $ ./gdbus-example-peer --server --address nonce-tcp: + Server is listening at: nonce-tcp:host=localhost,port=43077,noncefile=/tmp/gdbus-nonce-file-X1ZNCV + Client connected. + Peer credentials: (no credentials received) + Negotiated capabilities: unix-fd-passing=0 + Client said: Hey, it's 1273093206 already! + + Client: + $ ./gdbus-example-peer -address nonce-tcp:host=localhost,port=43077,noncefile=/tmp/gdbus-nonce-file-X1ZNCV + Connected. + Negotiated capabilities: unix-fd-passing=0 + Server said: You said 'Hey, it's 1273093206 already!'. KTHXBYE! + +TCP transport on two different hosts with a shared home directory: + + Server: + host1 $ ./gdbus-example-peer --server --address tcp:host=0.0.0.0 + Server is listening at: tcp:host=0.0.0.0,port=46314 + Client connected. + Peer credentials: (no credentials received) + Negotiated capabilities: unix-fd-passing=0 + Client said: Hey, it's 1273093337 already! + + Client: + host2 $ ./gdbus-example-peer -a tcp:host=host1,port=46314 + Connected. + Negotiated capabilities: unix-fd-passing=0 + Server said: You said 'Hey, it's 1273093337 already!'. KTHXBYE! + +TCP transport on two different hosts without authentication: + + Server: + host1 $ ./gdbus-example-peer --server --address tcp:host=0.0.0.0 --allow-anonymous + Server is listening at: tcp:host=0.0.0.0,port=59556 + Client connected. + Peer credentials: (no credentials received) + Negotiated capabilities: unix-fd-passing=0 + Client said: Hey, it's 1273093652 already! + + Client: + host2 $ ./gdbus-example-peer -a tcp:host=host1,port=59556 + Connected. + Negotiated capabilities: unix-fd-passing=0 + Server said: You said 'Hey, it's 1273093652 already!'. KTHXBYE! + + */ + +#include +#include + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusNodeInfo *introspection_data = NULL; + +/* Introspection data for the service we are exporting */ +static const gchar introspection_xml[] = + "" + " " + " " + " " + " " + " " + " " + ""; + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +handle_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + if (g_strcmp0 (method_name, "HelloWorld") == 0) + { + const gchar *greeting; + gchar *response; + + g_variant_get (parameters, "(s)", &greeting); + response = g_strdup_printf ("You said '%s'. KTHXBYE!", greeting); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(s)", response)); + g_free (response); + g_print ("Client said: %s\n", greeting); + } +} + +static const GDBusInterfaceVTable interface_vtable = +{ + handle_method_call, + NULL, + NULL, +}; + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +on_new_connection (GDBusServer *server, + GDBusConnection *connection, + gpointer user_data) +{ + guint registration_id; + GCredentials *credentials; + gchar *s; + + credentials = g_dbus_connection_get_peer_credentials (connection); + if (credentials == NULL) + s = g_strdup ("(no credentials received)"); + else + s = g_credentials_to_string (credentials); + + + g_print ("Client connected.\n" + "Peer credentials: %s\n" + "Negotiated capabilities: unix-fd-passing=%d\n", + s, + g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING); + + g_object_ref (connection); + registration_id = g_dbus_connection_register_object (connection, + "/org/gtk/GDBus/TestObject", + introspection_data->interfaces[0], + &interface_vtable, + NULL, /* user_data */ + NULL, /* user_data_free_func */ + NULL); /* GError** */ + g_assert (registration_id > 0); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +int +main (int argc, char *argv[]) +{ + gint ret; + gboolean opt_server; + gchar *opt_address; + GOptionContext *opt_context; + gboolean opt_allow_anonymous; + GError *error; + GOptionEntry opt_entries[] = + { + { "server", 's', 0, G_OPTION_ARG_NONE, &opt_server, "Start a server instead of a client", NULL }, + { "address", 'a', 0, G_OPTION_ARG_STRING, &opt_address, "D-Bus address to use", NULL }, + { "allow-anonymous", 'n', 0, G_OPTION_ARG_NONE, &opt_allow_anonymous, "Allow anonymous authentication", NULL }, + { NULL} + }; + + ret = 1; + + g_type_init (); + + opt_address = NULL; + opt_server = FALSE; + opt_allow_anonymous = FALSE; + + opt_context = g_option_context_new ("peer-to-peer example"); + error = NULL; + g_option_context_add_main_entries (opt_context, opt_entries, NULL); + if (!g_option_context_parse (opt_context, &argc, &argv, &error)) + { + g_printerr ("Error parsing options: %s\n", error->message); + g_error_free (error); + goto out; + } + if (opt_address == NULL) + { + g_printerr ("Incorrect usage, try --help.\n"); + goto out; + } + if (!opt_server && opt_allow_anonymous) + { + g_printerr ("The --allow-anonymous option only makes sense when used with --server.\n"); + goto out; + } + + /* We are lazy here - we don't want to manually provide + * the introspection data structures - so we just build + * them from XML. + */ + introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + g_assert (introspection_data != NULL); + + if (opt_server) + { + GDBusServer *server; + gchar *guid; + GMainLoop *loop; + GDBusServerFlags server_flags; + + guid = g_dbus_generate_guid (); + + server_flags = G_DBUS_SERVER_FLAGS_NONE; + if (opt_allow_anonymous) + server_flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS; + + error = NULL; + server = g_dbus_server_new_sync (opt_address, + server_flags, + guid, + NULL, /* GDBusAuthObserver */ + NULL, /* GCancellable */ + &error); + g_dbus_server_start (server); + g_free (guid); + + if (server == NULL) + { + g_printerr ("Error creating server at address %s: %s\n", opt_address, error->message); + g_error_free (error); + goto out; + } + g_print ("Server is listening at: %s\n", g_dbus_server_get_client_address (server)); + g_signal_connect (server, + "new-connection", + G_CALLBACK (on_new_connection), + NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_object_unref (server); + g_main_loop_unref (loop); + } + else + { + GDBusConnection *connection; + const gchar *greeting_response; + GVariant *value; + gchar *greeting; + + error = NULL; + connection = g_dbus_connection_new_for_address_sync (opt_address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, /* GDBusAuthObserver */ + NULL, /* GCancellable */ + &error); + if (connection == NULL) + { + g_printerr ("Error connecting to D-Bus address %s: %s\n", opt_address, error->message); + g_error_free (error); + goto out; + } + + g_print ("Connected.\n" + "Negotiated capabilities: unix-fd-passing=%d\n", + g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING); + + greeting = g_strdup_printf ("Hey, it's %" G_GUINT64_FORMAT " already!", (guint64) time (NULL)); + value = g_dbus_connection_call_sync (connection, + NULL, /* bus_name */ + "/org/gtk/GDBus/TestObject", + "org.gtk.GDBus.TestPeerInterface", + "HelloWorld", + g_variant_new ("(s)", greeting), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (value == NULL) + { + g_printerr ("Error invoking HelloWorld(): %s\n", error->message); + g_error_free (error); + goto out; + } + g_variant_get (value, "(s)", &greeting_response); + g_print ("Server said: %s\n", greeting_response); + g_variant_unref (value); + + g_object_unref (connection); + } + g_dbus_node_info_unref (introspection_data); + + ret = 0; + + out: + return ret; +} diff --git a/gio/tests/gdbus-example-proxy-subclass.c b/gio/tests/gdbus-example-proxy-subclass.c new file mode 100644 index 000000000..19e313ce5 --- /dev/null +++ b/gio/tests/gdbus-example-proxy-subclass.c @@ -0,0 +1,447 @@ + +#include + +/* ---------------------------------------------------------------------------------------------------- */ +/* The D-Bus interface definition we want to create a GDBusProxy-derived type for: */ +/* ---------------------------------------------------------------------------------------------------- */ + +static const gchar introspection_xml[] = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +/* ---------------------------------------------------------------------------------------------------- */ +/* Definition of the AccountsUser type */ +/* ---------------------------------------------------------------------------------------------------- */ + +#define ACCOUNTS_TYPE_USER (accounts_user_get_type ()) +#define ACCOUNTS_USER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ACCOUNTS_TYPE_USER, AccountsUser)) +#define ACCOUNTS_USER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), ACCOUNTS_TYPE_USER, AccountsUserClass)) +#define ACCOUNTS_USER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ACCOUNTS_TYPE_USER, AccountsUserClass)) +#define ACCOUNTS_IS_USER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ACCOUNTS_TYPE_USER)) +#define ACCOUNTS_IS_USER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), ACCOUNTS_TYPE_USER)) + +typedef struct _AccountsUser AccountsUser; +typedef struct _AccountsUserClass AccountsUserClass; +typedef struct _AccountsUserPrivate AccountsUserPrivate; + +struct _AccountsUser +{ + /*< private >*/ + GDBusProxy parent_instance; + AccountsUserPrivate *priv; +}; + +struct _AccountsUserClass +{ + /*< private >*/ + GDBusProxyClass parent_class; + void (*changed) (AccountsUser *user); +}; + +GType accounts_user_get_type (void) G_GNUC_CONST; + +const gchar *accounts_user_get_user_name (AccountsUser *user); +const gchar *accounts_user_get_real_name (AccountsUser *user); +gboolean accounts_user_get_automatic_login (AccountsUser *user); + +void accounts_user_frobnicate (AccountsUser *user, + const gchar *flux, + gint baz, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gchar *accounts_user_frobnicate_finish (AccountsUser *user, + GAsyncResult *res, + GError **error); +gchar *accounts_user_frobnicate_sync (AccountsUser *user, + const gchar *flux, + gint baz, + GCancellable *cancellable, + GError **error); + +/* ---------------------------------------------------------------------------------------------------- */ +/* Implementation of the AccountsUser type */ +/* ---------------------------------------------------------------------------------------------------- */ + +/* A more efficient approach than parsing XML is to use const static + * GDBusInterfaceInfo, GDBusMethodInfo, ... structures + */ +static GDBusInterfaceInfo * +accounts_user_get_interface_info (void) +{ + static gsize has_info = 0; + static GDBusInterfaceInfo *info = NULL; + if (g_once_init_enter (&has_info)) + { + GDBusNodeInfo *introspection_data; + introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + info = introspection_data->interfaces[0]; + g_once_init_leave (&has_info, 1); + } + return info; +} + +enum +{ + PROP_0, + PROP_USER_NAME, + PROP_REAL_NAME, + PROP_AUTOMATIC_LOGIN, +}; + +enum +{ + CHANGED_SIGNAL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +G_DEFINE_TYPE (AccountsUser, accounts_user, G_TYPE_DBUS_PROXY); + +static void +accounts_user_finalize (GObject *object) +{ + G_GNUC_UNUSED AccountsUser *user = ACCOUNTS_USER (object); + + if (G_OBJECT_CLASS (accounts_user_parent_class)->finalize != NULL) + G_OBJECT_CLASS (accounts_user_parent_class)->finalize (object); +} + +static void +accounts_user_init (AccountsUser *user) +{ + /* Sets the expected interface */ + g_dbus_proxy_set_interface_info (G_DBUS_PROXY (user), accounts_user_get_interface_info ()); +} + +static void +accounts_user_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AccountsUser *user = ACCOUNTS_USER (object); + + switch (prop_id) + { + case PROP_USER_NAME: + g_value_set_string (value, accounts_user_get_user_name (user)); + break; + + case PROP_REAL_NAME: + g_value_set_string (value, accounts_user_get_real_name (user)); + break; + + case PROP_AUTOMATIC_LOGIN: + g_value_set_boolean (value, accounts_user_get_automatic_login (user)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +const gchar * +accounts_user_get_user_name (AccountsUser *user) +{ + GVariant *value; + const gchar *ret; + g_return_val_if_fail (ACCOUNTS_IS_USER (user), NULL); + value = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (user), "UserName"); + ret = g_variant_get_string (value, NULL); + g_variant_unref (value); + return ret; +} + +const gchar * +accounts_user_get_real_name (AccountsUser *user) +{ + GVariant *value; + const gchar *ret; + g_return_val_if_fail (ACCOUNTS_IS_USER (user), NULL); + value = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (user), "RealName"); + ret = g_variant_get_string (value, NULL); + g_variant_unref (value); + return ret; +} + +gboolean +accounts_user_get_automatic_login (AccountsUser *user) +{ + GVariant *value; + gboolean ret; + g_return_val_if_fail (ACCOUNTS_IS_USER (user), FALSE); + value = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (user), "AutomaticLogin"); + ret = g_variant_get_boolean (value); + g_variant_unref (value); + return ret; +} + +static void +accounts_user_g_signal (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters) +{ + AccountsUser *user = ACCOUNTS_USER (proxy); + if (g_strcmp0 (signal_name, "Changed") == 0) + g_signal_emit (user, signals[CHANGED_SIGNAL], 0); +} + +static void +accounts_user_g_properties_changed (GDBusProxy *proxy, + GVariant *changed_properties, + const gchar* const *invalidated_properties) +{ + AccountsUser *user = ACCOUNTS_USER (proxy); + GVariantIter *iter; + GVariant *item; + + if (changed_properties != NULL) + { + g_variant_get (changed_properties, "a{sv}", &iter); + while ((item = g_variant_iter_next_value (iter)) != NULL) + { + const gchar *key; + g_variant_get (item, + "{sv}", + &key, + NULL); + if (g_strcmp0 (key, "AutomaticLogin") == 0) + g_object_notify (G_OBJECT (user), "automatic-login"); + else if (g_strcmp0 (key, "RealName") == 0) + g_object_notify (G_OBJECT (user), "real-name"); + else if (g_strcmp0 (key, "UserName") == 0) + g_object_notify (G_OBJECT (user), "user-name"); + } + } +} + +static void +accounts_user_class_init (AccountsUserClass *klass) +{ + GObjectClass *gobject_class; + GDBusProxyClass *proxy_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->get_property = accounts_user_get_property; + gobject_class->finalize = accounts_user_finalize; + + proxy_class = G_DBUS_PROXY_CLASS (klass); + proxy_class->g_signal = accounts_user_g_signal; + proxy_class->g_properties_changed = accounts_user_g_properties_changed; + + g_object_class_install_property (gobject_class, + PROP_USER_NAME, + g_param_spec_string ("user-name", + "User Name", + "The user name of the user", + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_REAL_NAME, + g_param_spec_string ("real-name", + "Real Name", + "The real name of the user", + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_AUTOMATIC_LOGIN, + g_param_spec_boolean ("automatic-login", + "Automatic Login", + "Whether the user is automatically logged in", + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + signals[CHANGED_SIGNAL] = g_signal_new ("changed", + ACCOUNTS_TYPE_USER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (AccountsUserClass, changed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +gchar * +accounts_user_frobnicate_sync (AccountsUser *user, + const gchar *flux, + gint baz, + GCancellable *cancellable, + GError **error) +{ + gchar *ret; + GVariant *value; + + g_return_val_if_fail (ACCOUNTS_IS_USER (user), NULL); + + ret = NULL; + + value = g_dbus_proxy_call_sync (G_DBUS_PROXY (user), + "Frobnicate", + g_variant_new ("(si)", + flux, + baz), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (value != NULL) + { + g_variant_get (value, "(s)", &ret); + ret = g_strdup (ret); + g_variant_unref (value); + } + return ret; +} + +void +accounts_user_frobnicate (AccountsUser *user, + const gchar *flux, + gint baz, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (ACCOUNTS_IS_USER (user)); + g_dbus_proxy_call (G_DBUS_PROXY (user), + "Frobnicate", + g_variant_new ("(si)", + flux, + baz), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + + +gchar * +accounts_user_frobnicate_finish (AccountsUser *user, + GAsyncResult *res, + GError **error) +{ + gchar *ret; + GVariant *value; + + ret = NULL; + value = g_dbus_proxy_call_finish (G_DBUS_PROXY (user), res, error); + if (value != NULL) + { + g_variant_get (value, "(s)", &ret); + ret = g_strdup (ret); + g_variant_unref (value); + } + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ +/* Example usage of the AccountsUser type */ +/* ---------------------------------------------------------------------------------------------------- */ + +static void +print_user (AccountsUser *user) +{ + g_print (" user-name = `%s'\n", accounts_user_get_user_name (user)); + g_print (" real-name = `%s'\n", accounts_user_get_real_name (user)); + g_print (" automatic-login = %s\n", accounts_user_get_automatic_login (user) ? "true" : "false"); +} + +static void +on_changed (AccountsUser *user, + gpointer user_data) +{ + g_print ("+++ Received the AccountsUser::changed signal\n"); + print_user (user); +} + +static void +on_notify (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + AccountsUser *user = ACCOUNTS_USER (object); + g_print ("+++ Received the GObject::notify signal for property `%s'\n", + pspec->name); + print_user (user); +} + +static void +on_proxy_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + GDBusProxy *proxy, + gpointer user_data) +{ + AccountsUser *user = ACCOUNTS_USER (proxy); + + g_print ("+++ Acquired proxy for user\n"); + print_user (user); + + g_signal_connect (proxy, + "notify", + G_CALLBACK (on_notify), + NULL); + g_signal_connect (user, + "changed", + G_CALLBACK (on_changed), + NULL); +} + +static void +on_proxy_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_print ("--- Cannot create proxy for user: no remote object\n"); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +gint +main (gint argc, gchar *argv[]) +{ + guint watcher_id; + GMainLoop *loop; + + g_type_init (); + + watcher_id = g_bus_watch_proxy (G_BUS_TYPE_SYSTEM, + "org.freedesktop.Accounts", + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + "/org/freedesktop/Accounts/User500", + "org.freedesktop.Accounts.User", + ACCOUNTS_TYPE_USER, + G_DBUS_PROXY_FLAGS_NONE, + on_proxy_appeared, + on_proxy_vanished, + NULL, + NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + g_main_loop_unref (loop); + g_bus_unwatch_proxy (watcher_id); + + return 0; +} diff --git a/gio/tests/gdbus-example-server.c b/gio/tests/gdbus-example-server.c new file mode 100644 index 000000000..93dd53a7b --- /dev/null +++ b/gio/tests/gdbus-example-server.c @@ -0,0 +1,390 @@ +#include +#include + +#ifdef G_OS_UNIX +/* For STDOUT_FILENO */ +#include +#endif + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusNodeInfo *introspection_data = NULL; + +/* Introspection data for the service we are exporting */ +static const gchar introspection_xml[] = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +handle_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + if (g_strcmp0 (method_name, "HelloWorld") == 0) + { + const gchar *greeting; + + g_variant_get (parameters, "(s)", &greeting); + + if (g_strcmp0 (greeting, "Return Unregistered") == 0) + { + g_dbus_method_invocation_return_error (invocation, + G_IO_ERROR, + G_IO_ERROR_FAILED_HANDLED, + "As requested, here's a GError not registered (G_IO_ERROR_FAILED_HANDLED)"); + } + else if (g_strcmp0 (greeting, "Return Registered") == 0) + { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_MATCH_RULE_NOT_FOUND, + "As requested, here's a GError that is registered (G_DBUS_ERROR_MATCH_RULE_NOT_FOUND)"); + } + else if (g_strcmp0 (greeting, "Return Raw") == 0) + { + g_dbus_method_invocation_return_dbus_error (invocation, + "org.gtk.GDBus.SomeErrorName", + "As requested, here's a raw D-Bus error"); + } + else + { + gchar *response; + response = g_strdup_printf ("You greeted me with '%s'. Thanks!", greeting); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(s)", response)); + g_free (response); + } + } + else if (g_strcmp0 (method_name, "EmitSignal") == 0) + { + GError *local_error; + gdouble speed_in_mph; + gchar *speed_as_string; + + g_variant_get (parameters, "(d)", &speed_in_mph); + speed_as_string = g_strdup_printf ("%g mph!", speed_in_mph); + + local_error = NULL; + g_dbus_connection_emit_signal (connection, + NULL, + object_path, + interface_name, + "VelocityChanged", + g_variant_new ("(ds)", + speed_in_mph, + speed_as_string), + &local_error); + g_assert_no_error (local_error); + g_free (speed_as_string); + + g_dbus_method_invocation_return_value (invocation, NULL); + } + else if (g_strcmp0 (method_name, "GimmeStdout") == 0) + { +#ifdef G_OS_UNIX + if (g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING) + { + GDBusMessage *reply; + GUnixFDList *fd_list; + GError *error; + + fd_list = g_unix_fd_list_new (); + error = NULL; + g_unix_fd_list_append (fd_list, STDOUT_FILENO, &error); + g_assert_no_error (error); + + reply = g_dbus_message_new_method_reply (g_dbus_method_invocation_get_message (invocation)); + g_dbus_message_set_unix_fd_list (reply, fd_list); + + error = NULL; + g_dbus_connection_send_message (connection, + reply, + NULL, /* out_serial */ + &error); + g_assert_no_error (error); + + g_object_unref (invocation); + g_object_unref (fd_list); + g_object_unref (reply); + } + else + { + g_dbus_method_invocation_return_dbus_error (invocation, + "org.gtk.GDBus.Failed", + "Your message bus daemon does not support file descriptor passing (need D-Bus >= 1.3.0)"); + } +#else + g_dbus_method_invocation_return_dbus_error (invocation, + "org.gtk.GDBus.NotOnUnix", + "Your OS does not support file descriptor passing"); +#endif + } +} + +static gchar *_global_title = NULL; + +static gboolean swap_a_and_b = FALSE; + +static GVariant * +handle_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GVariant *ret; + + ret = NULL; + if (g_strcmp0 (property_name, "FluxCapicitorName") == 0) + { + ret = g_variant_new_string ("DeLorean"); + } + else if (g_strcmp0 (property_name, "Title") == 0) + { + if (_global_title == NULL) + _global_title = g_strdup ("Back To C!"); + ret = g_variant_new_string (_global_title); + } + else if (g_strcmp0 (property_name, "ReadingAlwaysThrowsError") == 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Hello %s. I thought I said reading this property " + "always results in an error. kthxbye", + sender); + } + else if (g_strcmp0 (property_name, "WritingAlwaysThrowsError") == 0) + { + ret = g_variant_new_string ("There's no home like home"); + } + else if (g_strcmp0 (property_name, "Foo") == 0) + { + ret = g_variant_new_string (swap_a_and_b ? "Tock" : "Tick"); + } + else if (g_strcmp0 (property_name, "Bar") == 0) + { + ret = g_variant_new_string (swap_a_and_b ? "Tick" : "Tock"); + } + + return ret; +} + +static gboolean +handle_set_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, + gpointer user_data) +{ + if (g_strcmp0 (property_name, "Title") == 0) + { + if (g_strcmp0 (_global_title, g_variant_get_string (value, NULL)) != 0) + { + GVariantBuilder *builder; + GError *local_error; + + g_free (_global_title); + _global_title = g_variant_dup_string (value, NULL); + + local_error = NULL; + builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); + g_variant_builder_add (builder, + "{sv}", + "Title", + g_variant_new_string (_global_title)); + g_dbus_connection_emit_signal (connection, + NULL, + object_path, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new ("(sa{sv}as)", + interface_name, + builder, + NULL), + &local_error); + g_assert_no_error (local_error); + } + } + else if (g_strcmp0 (property_name, "ReadingAlwaysThrowsError") == 0) + { + /* do nothing - they can't read it after all! */ + } + else if (g_strcmp0 (property_name, "WritingAlwaysThrowsError") == 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Hello AGAIN %s. I thought I said writing this property " + "always results in an error. kthxbye", + sender); + } + + return *error == NULL; +} + + +/* for now */ +static const GDBusInterfaceVTable interface_vtable = +{ + handle_method_call, + handle_get_property, + handle_set_property +}; + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +on_timeout_cb (gpointer user_data) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (user_data); + GVariantBuilder *builder; + GVariantBuilder *invalidated_builder; + GError *error; + + swap_a_and_b = !swap_a_and_b; + + error = NULL; + builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); + invalidated_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); + g_variant_builder_add (builder, + "{sv}", + "Foo", + g_variant_new_string (swap_a_and_b ? "Tock" : "Tick")); + g_variant_builder_add (builder, + "{sv}", + "Bar", + g_variant_new_string (swap_a_and_b ? "Tick" : "Tock")); + g_dbus_connection_emit_signal (connection, + NULL, + "/org/gtk/GDBus/TestObject", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new ("(sa{sv}as)", + "org.gtk.GDBus.TestInterface", + builder, + invalidated_builder), + &error); + g_assert_no_error (error); + + + return TRUE; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +on_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + guint registration_id; + + registration_id = g_dbus_connection_register_object (connection, + "/org/gtk/GDBus/TestObject", + introspection_data->interfaces[0], + &interface_vtable, + NULL, /* user_data */ + NULL, /* user_data_free_func */ + NULL); /* GError** */ + g_assert (registration_id > 0); + + /* swap value of properties Foo and Bar every two seconds */ + g_timeout_add_seconds (2, + on_timeout_cb, + connection); +} + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ +} + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + exit (1); +} + +int +main (int argc, char *argv[]) +{ + guint owner_id; + GMainLoop *loop; + + g_type_init (); + + /* We are lazy here - we don't want to manually provide + * the introspection data structures - so we just build + * them from XML. + */ + introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + g_assert (introspection_data != NULL); + + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.gtk.GDBus.TestServer", + G_BUS_NAME_OWNER_FLAGS_NONE, + on_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_bus_unown_name (owner_id); + + g_dbus_node_info_unref (introspection_data); + + return 0; +} diff --git a/gio/tests/gdbus-example-subtree.c b/gio/tests/gdbus-example-subtree.c new file mode 100644 index 000000000..1633accde --- /dev/null +++ b/gio/tests/gdbus-example-subtree.c @@ -0,0 +1,397 @@ +#include +#include +#include + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusNodeInfo *introspection_data = NULL; +static const GDBusInterfaceInfo *manager_interface_info = NULL; +static const GDBusInterfaceInfo *block_interface_info = NULL; +static const GDBusInterfaceInfo *partition_interface_info = NULL; + +/* Introspection data for the service we are exporting */ +static const gchar introspection_xml[] = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +manager_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + const gchar *greeting; + gchar *response; + + g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.Example.Manager"); + g_assert_cmpstr (method_name, ==, "Hello"); + + g_variant_get (parameters, "(s)", &greeting); + + response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'", + interface_name, + method_name, + (const gchar *) user_data, + object_path, + greeting); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(s)", response)); + g_free (response); +} + +const GDBusInterfaceVTable manager_vtable = +{ + manager_method_call, + NULL, /* get_property */ + NULL /* set_property */ +}; + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +block_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.Example.Block"); + + if (g_strcmp0 (method_name, "Hello") == 0) + { + const gchar *greeting; + gchar *response; + + g_variant_get (parameters, "(s)", &greeting); + + response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'", + interface_name, + method_name, + (const gchar *) user_data, + object_path, + greeting); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(s)", response)); + g_free (response); + } + else if (g_strcmp0 (method_name, "DoStuff") == 0) + { + g_dbus_method_invocation_return_dbus_error (invocation, + "org.gtk.GDBus.TestSubtree.Error.Failed", + "This method intentionally always fails"); + } + else + { + g_assert_not_reached (); + } +} + +static GVariant * +block_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GVariant *ret; + const gchar *node; + gint major; + gint minor; + + node = strrchr (object_path, '/') + 1; + if (g_str_has_prefix (node, "sda")) + major = 8; + else + major = 9; + if (strlen (node) == 4) + minor = node[3] - '0'; + else + minor = 0; + + ret = NULL; + if (g_strcmp0 (property_name, "Major") == 0) + { + ret = g_variant_new_int32 (major); + } + else if (g_strcmp0 (property_name, "Minor") == 0) + { + ret = g_variant_new_int32 (minor); + } + else if (g_strcmp0 (property_name, "Notes") == 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Hello %s. I thought I said reading this property " + "always results in an error. kthxbye", + sender); + } + else + { + g_assert_not_reached (); + } + + return ret; +} + +static gboolean +block_set_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, + gpointer user_data) +{ + /* TODO */ + g_assert_not_reached (); +} + +const GDBusInterfaceVTable block_vtable = +{ + block_method_call, + block_get_property, + block_set_property, +}; + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +partition_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + const gchar *greeting; + gchar *response; + + g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.Example.Partition"); + g_assert_cmpstr (method_name, ==, "Hello"); + + g_variant_get (parameters, "(s)", &greeting); + + response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'", + interface_name, + method_name, + (const gchar *) user_data, + object_path, + greeting); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(s)", response)); + g_free (response); +} + +const GDBusInterfaceVTable partition_vtable = +{ + partition_method_call, + //partition_get_property, + //partition_set_property +}; + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar ** +subtree_enumerate (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + gpointer user_data) +{ + gchar **nodes; + GPtrArray *p; + + p = g_ptr_array_new (); + g_ptr_array_add (p, g_strdup ("sda")); + g_ptr_array_add (p, g_strdup ("sda1")); + g_ptr_array_add (p, g_strdup ("sda2")); + g_ptr_array_add (p, g_strdup ("sda3")); + g_ptr_array_add (p, g_strdup ("sdb")); + g_ptr_array_add (p, g_strdup ("sdb1")); + g_ptr_array_add (p, g_strdup ("sdc")); + g_ptr_array_add (p, g_strdup ("sdc1")); + g_ptr_array_add (p, NULL); + nodes = (gchar **) g_ptr_array_free (p, FALSE); + + return nodes; +} + +static GPtrArray * +subtree_introspect (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *node, + gpointer user_data) +{ + GPtrArray *p; + + p = g_ptr_array_new (); + if (g_strcmp0 (node, "/") == 0) + { + g_ptr_array_add (p, (gpointer) manager_interface_info); + } + else + { + g_ptr_array_add (p, (gpointer) block_interface_info); + if (strlen (node) == 4) + g_ptr_array_add (p, (gpointer) partition_interface_info); + } + + return p; +} + +static const GDBusInterfaceVTable * +subtree_dispatch (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *node, + gpointer *out_user_data, + gpointer user_data) +{ + const GDBusInterfaceVTable *vtable_to_return; + gpointer user_data_to_return; + + if (g_strcmp0 (interface_name, "org.gtk.GDBus.Example.Manager") == 0) + { + user_data_to_return = "The Root"; + vtable_to_return = &manager_vtable; + } + else + { + if (strlen (node) == 4) + user_data_to_return = "A partition"; + else + user_data_to_return = "A block device"; + + if (g_strcmp0 (interface_name, "org.gtk.GDBus.Example.Block") == 0) + vtable_to_return = &block_vtable; + else if (g_strcmp0 (interface_name, "org.gtk.GDBus.Example.Partition") == 0) + vtable_to_return = &partition_vtable; + else + g_assert_not_reached (); + } + + *out_user_data = user_data_to_return; + + return vtable_to_return; +} + +const GDBusSubtreeVTable subtree_vtable = +{ + subtree_enumerate, + subtree_introspect, + subtree_dispatch +}; + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +on_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + guint registration_id; + + registration_id = g_dbus_connection_register_subtree (connection, + "/org/gtk/GDBus/TestSubtree/Devices", + &subtree_vtable, + G_DBUS_SUBTREE_FLAGS_NONE, + NULL, /* user_data */ + NULL, /* user_data_free_func */ + NULL); /* GError** */ + g_assert (registration_id > 0); +} + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ +} + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + exit (1); +} + +int +main (int argc, char *argv[]) +{ + guint owner_id; + GMainLoop *loop; + + g_type_init (); + + /* We are lazy here - we don't want to manually provide + * the introspection data structures - so we just build + * them from XML. + */ + introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + g_assert (introspection_data != NULL); + + manager_interface_info = g_dbus_node_info_lookup_interface (introspection_data, "org.gtk.GDBus.Example.Manager"); + block_interface_info = g_dbus_node_info_lookup_interface (introspection_data, "org.gtk.GDBus.Example.Block"); + partition_interface_info = g_dbus_node_info_lookup_interface (introspection_data, "org.gtk.GDBus.Example.Partition"); + g_assert (manager_interface_info != NULL); + g_assert (block_interface_info != NULL); + g_assert (partition_interface_info != NULL); + + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.gtk.GDBus.TestSubtree", + G_BUS_NAME_OWNER_FLAGS_NONE, + on_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_bus_unown_name (owner_id); + + g_dbus_node_info_unref (introspection_data); + + return 0; +} diff --git a/gio/tests/gdbus-example-unix-fd-client.c b/gio/tests/gdbus-example-unix-fd-client.c new file mode 100644 index 000000000..500058b47 --- /dev/null +++ b/gio/tests/gdbus-example-unix-fd-client.c @@ -0,0 +1,133 @@ +#include +#include + +#include +#include + +#include + +#include +#include + +/* see gdbus-example-server.c for the server implementation */ +static gint +get_server_stdout (GDBusConnection *connection, + const gchar *name_owner, + GError **error) +{ + GDBusMessage *method_call_message; + GDBusMessage *method_reply_message; + GUnixFDList *fd_list; + gint fd; + + fd = -1; + method_call_message = NULL; + method_reply_message = NULL; + + method_call_message = g_dbus_message_new_method_call (name_owner, + "/org/gtk/GDBus/TestObject", + "org.gtk.GDBus.TestInterface", + "GimmeStdout"); + method_reply_message = g_dbus_connection_send_message_with_reply_sync (connection, + method_call_message, + -1, + NULL, /* out_serial */ + NULL, /* cancellable */ + error); + if (method_reply_message == NULL) + goto out; + + if (g_dbus_message_get_message_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_ERROR) + { + g_dbus_message_to_gerror (method_reply_message, error); + goto out; + } + + fd_list = g_dbus_message_get_unix_fd_list (method_reply_message); + fd = g_unix_fd_list_get (fd_list, 0, error); + + out: + g_object_unref (method_call_message); + g_object_unref (method_reply_message); + + return fd; +} + +static void +on_name_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + gint fd; + GError *error; + + error = NULL; + fd = get_server_stdout (connection, name_owner, &error); + if (fd == -1) + { + g_printerr ("Error invoking GimmeStdout(): %s\n", + error->message); + g_error_free (error); + exit (1); + } + else + { + gchar now_buf[256]; + time_t now; + gssize len; + gchar *str; + + now = time (NULL); + strftime (now_buf, + sizeof now_buf, + "%c", + localtime (&now)); + + str = g_strdup_printf ("On %s, gdbus-example-unix-fd-client with pid %d was here!\n", + now_buf, + (gint) getpid ()); + len = strlen (str); + g_warn_if_fail (write (fd, str, len) == len); + close (fd); + + g_print ("Wrote the following on server's stdout:\n%s", str); + + g_free (str); + exit (0); + } +} + +static void +on_name_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_printerr ("Failed to get name owner for %s\n" + "Is ./gdbus-example-server running?\n", + name); + exit (1); +} + +int +main (int argc, char *argv[]) +{ + guint watcher_id; + GMainLoop *loop; + + g_type_init (); + + watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION, + "org.gtk.GDBus.TestServer", + G_BUS_NAME_WATCHER_FLAGS_NONE, + on_name_appeared, + on_name_vanished, + NULL, + NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_bus_unwatch_name (watcher_id); + return 0; +} diff --git a/gio/tests/gdbus-example-watch-name.c b/gio/tests/gdbus-example-watch-name.c new file mode 100644 index 000000000..769419b22 --- /dev/null +++ b/gio/tests/gdbus-example-watch-name.c @@ -0,0 +1,88 @@ +#include + +static gchar *opt_name = NULL; +static gboolean opt_system_bus = FALSE; +static gboolean opt_auto_start = FALSE; + +static GOptionEntry opt_entries[] = +{ + { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name to watch", NULL }, + { "system-bus", 's', 0, G_OPTION_ARG_NONE, &opt_system_bus, "Use the system-bus instead of the session-bus", NULL }, + { "auto-start", 'a', 0, G_OPTION_ARG_NONE, &opt_auto_start, "Instruct the bus to launch an owner for the name", NULL}, + { NULL} +}; + +static void +on_name_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + g_print ("Name %s on %s is owned by %s\n", + name, + opt_system_bus ? "the system bus" : "the session bus", + name_owner); +} + +static void +on_name_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_print ("Name %s does not exist on %s\n", + name, + opt_system_bus ? "the system bus" : "the session bus"); +} + +int +main (int argc, char *argv[]) +{ + guint watcher_id; + GMainLoop *loop; + GOptionContext *opt_context; + GError *error; + GBusNameWatcherFlags flags; + + g_type_init (); + + error = NULL; + opt_context = g_option_context_new ("g_bus_watch_name() example"); + g_option_context_set_summary (opt_context, + "Example: to watch the power manager on the session bus, use:\n" + "\n" + " ./example-watch-name -n org.gnome.PowerManager"); + g_option_context_add_main_entries (opt_context, opt_entries, NULL); + if (!g_option_context_parse (opt_context, &argc, &argv, &error)) + { + g_printerr ("Error parsing options: %s", error->message); + goto out; + } + if (opt_name == NULL) + { + g_printerr ("Incorrect usage, try --help.\n"); + goto out; + } + + flags = G_BUS_NAME_WATCHER_FLAGS_NONE; + if (opt_auto_start) + flags |= G_BUS_NAME_WATCHER_FLAGS_AUTO_START; + + watcher_id = g_bus_watch_name (opt_system_bus ? G_BUS_TYPE_SYSTEM : G_BUS_TYPE_SESSION, + opt_name, + flags, + on_name_appeared, + on_name_vanished, + NULL, + NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_bus_unwatch_name (watcher_id); + + out: + g_option_context_free (opt_context); + g_free (opt_name); + + return 0; +} diff --git a/gio/tests/gdbus-example-watch-proxy.c b/gio/tests/gdbus-example-watch-proxy.c new file mode 100644 index 000000000..4f4195fd0 --- /dev/null +++ b/gio/tests/gdbus-example-watch-proxy.c @@ -0,0 +1,217 @@ +#include + +static gchar *opt_name = NULL; +static gchar *opt_object_path = NULL; +static gchar *opt_interface = NULL; +static gboolean opt_system_bus = FALSE; +static gboolean opt_auto_start = FALSE; +static gboolean opt_no_properties = FALSE; + +static GOptionEntry opt_entries[] = +{ + { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name of the remote object to watch", NULL }, + { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_object_path, "Object path of the remote object", NULL }, + { "interface", 'i', 0, G_OPTION_ARG_STRING, &opt_interface, "D-Bus interface of remote object", NULL }, + { "system-bus", 's', 0, G_OPTION_ARG_NONE, &opt_system_bus, "Use the system-bus instead of the session-bus", NULL }, + { "auto-start", 'a', 0, G_OPTION_ARG_NONE, &opt_auto_start, "Instruct the bus to launch an owner for the name", NULL}, + { "no-properties", 'p', 0, G_OPTION_ARG_NONE, &opt_no_properties, "Do not load properties", NULL}, + { NULL} +}; + +static void +print_properties (GDBusProxy *proxy) +{ + gchar **property_names; + guint n; + + g_print (" properties:\n"); + + property_names = g_dbus_proxy_get_cached_property_names (proxy); + for (n = 0; property_names != NULL && property_names[n] != NULL; n++) + { + const gchar *key = property_names[n]; + GVariant *value; + gchar *value_str; + value = g_dbus_proxy_get_cached_property (proxy, key); + value_str = g_variant_print (value, TRUE); + g_print (" %s -> %s\n", key, value_str); + g_variant_unref (value); + g_free (value_str); + } + g_strfreev (property_names); +} + +static void +on_properties_changed (GDBusProxy *proxy, + GVariant *changed_properties, + const gchar* const *invalidated_properties, + gpointer user_data) +{ + /* Note that we are guaranteed that changed_properties and + * invalidated_properties are never NULL + */ + + if (g_variant_n_children (changed_properties) > 0) + { + GVariantIter *iter; + GVariant *item; + + g_print (" *** Properties Changed:\n"); + g_variant_get (changed_properties, + "a{sv}", + &iter); + while ((item = g_variant_iter_next_value (iter))) + { + const gchar *key; + GVariant *value; + gchar *value_str; + g_variant_get (item, + "{sv}", + &key, + &value); + value_str = g_variant_print (value, TRUE); + g_print (" %s -> %s\n", key, value_str); + g_free (value_str); + } + } + + if (g_strv_length ((GStrv) invalidated_properties) > 0) + { + guint n; + g_print (" *** Properties Invalidated:\n"); + for (n = 0; invalidated_properties[n] != NULL; n++) + { + const gchar *key = invalidated_properties[n]; + g_print (" %s\n", key); + } + } +} + +static void +on_signal (GDBusProxy *proxy, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + gchar *parameters_str; + + parameters_str = g_variant_print (parameters, TRUE); + g_print (" *** Received Signal: %s: %s\n", + signal_name, + parameters_str); + g_free (parameters_str); +} + +static void +on_proxy_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + GDBusProxy *proxy, + gpointer user_data) +{ + g_print ("+++ Acquired proxy object for remote object owned by %s\n" + " bus: %s\n" + " name: %s\n" + " object path: %s\n" + " interface: %s\n", + name_owner, + opt_system_bus ? "System Bus" : "Session Bus", + opt_name, + opt_object_path, + opt_interface); + + print_properties (proxy); + + g_signal_connect (proxy, + "g-properties-changed", + G_CALLBACK (on_properties_changed), + NULL); + + g_signal_connect (proxy, + "g-signal", + G_CALLBACK (on_signal), + NULL); +} + +static void +on_proxy_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_print ("--- Cannot create proxy object for\n" + " bus: %s\n" + " name: %s\n" + " object path: %s\n" + " interface: %s\n", + opt_system_bus ? "System Bus" : "Session Bus", + opt_name, + opt_object_path, + opt_interface); +} + +int +main (int argc, char *argv[]) +{ + guint watcher_id; + GMainLoop *loop; + GOptionContext *opt_context; + GError *error; + GBusNameWatcherFlags flags; + GDBusProxyFlags proxy_flags; + + g_type_init (); + + opt_context = g_option_context_new ("g_bus_watch_proxy() example"); + g_option_context_set_summary (opt_context, + "Example: to watch the object of gdbus-example-server, use:\n" + "\n" + " ./gdbus-example-watch-proxy -n org.gtk.GDBus.TestServer \\\n" + " -o /org/gtk/GDBus/TestObject \\\n" + " -i org.gtk.GDBus.TestInterface"); + g_option_context_add_main_entries (opt_context, opt_entries, NULL); + error = NULL; + if (!g_option_context_parse (opt_context, &argc, &argv, &error)) + { + g_printerr ("Error parsing options: %s", error->message); + goto out; + } + if (opt_name == NULL || opt_object_path == NULL || opt_interface == NULL) + { + g_printerr ("Incorrect usage, try --help.\n"); + goto out; + } + + flags = G_BUS_NAME_WATCHER_FLAGS_NONE; + if (opt_auto_start) + flags |= G_BUS_NAME_WATCHER_FLAGS_AUTO_START; + + proxy_flags = G_DBUS_PROXY_FLAGS_NONE; + if (opt_no_properties) + proxy_flags |= G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES; + + watcher_id = g_bus_watch_proxy (opt_system_bus ? G_BUS_TYPE_SYSTEM : G_BUS_TYPE_SESSION, + opt_name, + flags, + opt_object_path, + opt_interface, + G_TYPE_DBUS_PROXY, + proxy_flags, + on_proxy_appeared, + on_proxy_vanished, + NULL, + NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_bus_unwatch_proxy (watcher_id); + + out: + g_option_context_free (opt_context); + g_free (opt_name); + g_free (opt_object_path); + g_free (opt_interface); + + return 0; +} diff --git a/gio/tests/gdbus-exit-on-close.c b/gio/tests/gdbus-exit-on-close.c new file mode 100644 index 000000000..6120fa5d4 --- /dev/null +++ b/gio/tests/gdbus-exit-on-close.c @@ -0,0 +1,82 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include +#include +#include + +#include "gdbus-tests.h" + +/* all tests rely on a shared mainloop */ +static GMainLoop *loop = NULL; + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +nuke_session_bus_cb (gpointer data) +{ + g_main_loop_quit (loop); + return FALSE; +} + +static void +test_exit_on_close (void) +{ + if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDOUT | G_TEST_TRAP_SILENCE_STDERR)) + { + GDBusConnection *c; + session_bus_up (); + c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + g_assert (c != NULL); + g_assert (!g_dbus_connection_is_closed (c)); + g_timeout_add (50, + nuke_session_bus_cb, + NULL); + g_main_loop_run (loop); + session_bus_down (); + g_main_loop_run (loop); + } + g_test_trap_assert_stdout ("*Remote peer vanished. Exiting.*"); + g_test_trap_assert_failed(); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +int +main (int argc, + char *argv[]) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + + /* all the tests rely on a shared main loop */ + loop = g_main_loop_new (NULL, FALSE); + + /* all the tests use a session bus with a well-known address that we can bring up and down + * using session_bus_up() and session_bus_down(). + */ + g_unsetenv ("DISPLAY"); + g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE); + + g_test_add_func ("/gdbus/exit-on-close", test_exit_on_close); + return g_test_run(); +} diff --git a/gio/tests/gdbus-export.c b/gio/tests/gdbus-export.c new file mode 100644 index 000000000..ff6cc28f2 --- /dev/null +++ b/gio/tests/gdbus-export.c @@ -0,0 +1,1398 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include +#include +#include + +#include "gdbus-tests.h" + +/* all tests rely on a shared mainloop */ +static GMainLoop *loop = NULL; + +static GDBusConnection *c = NULL; + +/* ---------------------------------------------------------------------------------------------------- */ +/* Test that we can export objects, the hierarchy is correct and the right handlers are invoked */ +/* ---------------------------------------------------------------------------------------------------- */ + +static const GDBusArgInfo foo_method1_in_args = +{ + -1, + "an_input_string", + "s", + NULL +}; +static const GDBusArgInfo * const foo_method1_in_arg_pointers[] = {&foo_method1_in_args, NULL}; + +static const GDBusArgInfo foo_method1_out_args = +{ + -1, + "an_output_string", + "s", + NULL +}; +static const GDBusArgInfo * const foo_method1_out_arg_pointers[] = {&foo_method1_out_args, NULL}; + +static const GDBusMethodInfo foo_method_info_method1 = +{ + -1, + "Method1", + (GDBusArgInfo **) &foo_method1_in_arg_pointers, + (GDBusArgInfo **) &foo_method1_out_arg_pointers, + NULL +}; +static const GDBusMethodInfo foo_method_info_method2 = +{ + -1, + "Method2", + NULL, + NULL, + NULL +}; +static const GDBusMethodInfo * const foo_method_info_pointers[] = {&foo_method_info_method1, &foo_method_info_method2, NULL}; + +static const GDBusSignalInfo foo_signal_info = +{ + -1, + "SignalAlpha", + NULL, + NULL +}; +static const GDBusSignalInfo * const foo_signal_info_pointers[] = {&foo_signal_info, NULL}; + +static const GDBusPropertyInfo foo_property_info[] = +{ + { + -1, + "PropertyUno", + "s", + G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, + NULL + }, + { + -1, + "NotWritable", + "s", + G_DBUS_PROPERTY_INFO_FLAGS_READABLE, + NULL + }, + { + -1, + "NotReadable", + "s", + G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, + NULL + } +}; +static const GDBusPropertyInfo * const foo_property_info_pointers[] = +{ + &foo_property_info[0], + &foo_property_info[1], + &foo_property_info[2], + NULL +}; + +static const GDBusInterfaceInfo foo_interface_info = +{ + -1, + "org.example.Foo", + (GDBusMethodInfo **) &foo_method_info_pointers, + (GDBusSignalInfo **) &foo_signal_info_pointers, + (GDBusPropertyInfo **)&foo_property_info_pointers, + NULL, +}; + +static void +foo_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + if (g_strcmp0 (method_name, "Method1") == 0) + { + const gchar *input; + gchar *output; + g_variant_get (parameters, "(s)", &input); + output = g_strdup_printf ("You passed the string `%s'. Jolly good!", input); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", output)); + g_free (output); + } + else + { + g_dbus_method_invocation_return_dbus_error (invocation, + "org.example.SomeError", + "How do you like them apples, buddy!"); + } +} + +static GVariant * +foo_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GVariant *ret; + gchar *s; + s = g_strdup_printf ("Property `%s' Is What It Is!", property_name); + ret = g_variant_new_string (s); + g_free (s); + return ret; +} + +static gboolean +foo_set_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, + gpointer user_data) +{ + gchar *s; + s = g_variant_print (value, TRUE); + g_set_error (error, + G_DBUS_ERROR, + G_DBUS_ERROR_SPAWN_FILE_INVALID, + "Returning some error instead of writing the value `%s' to the property `%s'", + property_name, s); + g_free (s); + return FALSE; +} + +static const GDBusInterfaceVTable foo_vtable = +{ + foo_method_call, + foo_get_property, + foo_set_property +}; + +/* -------------------- */ + +static const GDBusMethodInfo bar_method_info[] = +{ + { + -1, + "MethodA", + NULL, + NULL, + NULL + }, + { + -1, + "MethodB", + NULL, + NULL, + NULL + } +}; +static const GDBusMethodInfo * const bar_method_info_pointers[] = {&bar_method_info[0], &bar_method_info[1], NULL}; + +static const GDBusSignalInfo bar_signal_info[] = +{ + { + -1, + "SignalMars", + NULL, + NULL + } +}; +static const GDBusSignalInfo * const bar_signal_info_pointers[] = {&bar_signal_info[0], NULL}; + +static const GDBusPropertyInfo bar_property_info[] = +{ + { + -1, + "PropertyDuo", + "s", + G_DBUS_PROPERTY_INFO_FLAGS_READABLE, + NULL + } +}; +static const GDBusPropertyInfo * const bar_property_info_pointers[] = {&bar_property_info[0], NULL}; + +static const GDBusInterfaceInfo bar_interface_info = +{ + -1, + "org.example.Bar", + (GDBusMethodInfo **) bar_method_info_pointers, + (GDBusSignalInfo **) bar_signal_info_pointers, + (GDBusPropertyInfo **) bar_property_info_pointers, + NULL, +}; + +/* -------------------- */ + +static const GDBusMethodInfo dyna_method_info[] = +{ + { + -1, + "DynaCyber", + NULL, + NULL, + NULL + } +}; +static const GDBusMethodInfo * const dyna_method_info_pointers[] = {&dyna_method_info[0], NULL}; + +static const GDBusInterfaceInfo dyna_interface_info = +{ + -1, + "org.example.Dyna", + (GDBusMethodInfo **) dyna_method_info_pointers, + NULL, /* no signals */ + NULL, /* no properties */ + NULL, +}; + +static void +dyna_cyber (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GPtrArray *data = user_data; + gchar *node_name; + guint n; + + node_name = strrchr (object_path, '/') + 1; + + /* Add new node if it is not already known */ + for (n = 0; n < data->len ; n++) + { + if (g_strcmp0 (g_ptr_array_index (data, n), node_name) == 0) + goto out; + } + g_ptr_array_add (data, g_strdup(node_name)); + + out: + g_dbus_method_invocation_return_value (invocation, NULL); +} + +static const GDBusInterfaceVTable dyna_interface_vtable = +{ + dyna_cyber, + NULL, + NULL +}; + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +introspect_callback (GDBusProxy *proxy, + GAsyncResult *res, + gpointer user_data) +{ + const gchar *s; + gchar **xml_data = user_data; + GVariant *result; + GError *error; + + error = NULL; + result = g_dbus_proxy_call_finish (proxy, + res, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_variant_get (result, "(s)", &s); + *xml_data = g_strdup (s); + g_variant_unref (result); + + g_main_loop_quit (loop); +} + +static gchar ** +get_nodes_at (GDBusConnection *c, + const gchar *object_path) +{ + GError *error; + GDBusProxy *proxy; + gchar *xml_data; + GPtrArray *p; + GDBusNodeInfo *node_info; + guint n; + + error = NULL; + proxy = g_dbus_proxy_new_sync (c, + G_TYPE_DBUS_PROXY, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + g_dbus_connection_get_unique_name (c), + object_path, + "org.freedesktop.DBus.Introspectable", + NULL, + &error); + g_assert_no_error (error); + g_assert (proxy != NULL); + + /* do this async to avoid libdbus-1 deadlocks */ + xml_data = NULL; + g_dbus_proxy_call (proxy, + "Introspect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) introspect_callback, + &xml_data); + g_main_loop_run (loop); + g_assert (xml_data != NULL); + + node_info = g_dbus_node_info_new_for_xml (xml_data, &error); + g_assert_no_error (error); + g_assert (node_info != NULL); + + p = g_ptr_array_new (); + for (n = 0; node_info->nodes != NULL && node_info->nodes[n] != NULL; n++) + { + const GDBusNodeInfo *sub_node_info = node_info->nodes[n]; + g_ptr_array_add (p, g_strdup (sub_node_info->path)); + } + g_ptr_array_add (p, NULL); + + g_object_unref (proxy); + g_free (xml_data); + g_dbus_node_info_unref (node_info); + + return (gchar **) g_ptr_array_free (p, FALSE); +} + +static gboolean +has_interface (GDBusConnection *c, + const gchar *object_path, + const gchar *interface_name) +{ + GError *error; + GDBusProxy *proxy; + gchar *xml_data; + GDBusNodeInfo *node_info; + gboolean ret; + + error = NULL; + proxy = g_dbus_proxy_new_sync (c, + G_TYPE_DBUS_PROXY, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + g_dbus_connection_get_unique_name (c), + object_path, + "org.freedesktop.DBus.Introspectable", + NULL, + &error); + g_assert_no_error (error); + g_assert (proxy != NULL); + + /* do this async to avoid libdbus-1 deadlocks */ + xml_data = NULL; + g_dbus_proxy_call (proxy, + "Introspect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) introspect_callback, + &xml_data); + g_main_loop_run (loop); + g_assert (xml_data != NULL); + + node_info = g_dbus_node_info_new_for_xml (xml_data, &error); + g_assert_no_error (error); + g_assert (node_info != NULL); + + ret = (g_dbus_node_info_lookup_interface (node_info, interface_name) != NULL); + + g_object_unref (proxy); + g_free (xml_data); + g_dbus_node_info_unref (node_info); + + return ret; +} + +static guint +count_interfaces (GDBusConnection *c, + const gchar *object_path) +{ + GError *error; + GDBusProxy *proxy; + gchar *xml_data; + GDBusNodeInfo *node_info; + guint ret; + + error = NULL; + proxy = g_dbus_proxy_new_sync (c, + G_TYPE_DBUS_PROXY, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + g_dbus_connection_get_unique_name (c), + object_path, + "org.freedesktop.DBus.Introspectable", + NULL, + &error); + g_assert_no_error (error); + g_assert (proxy != NULL); + + /* do this async to avoid libdbus-1 deadlocks */ + xml_data = NULL; + g_dbus_proxy_call (proxy, + "Introspect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) introspect_callback, + &xml_data); + g_main_loop_run (loop); + g_assert (xml_data != NULL); + + node_info = g_dbus_node_info_new_for_xml (xml_data, &error); + g_assert_no_error (error); + g_assert (node_info != NULL); + + ret = 0; + while (node_info->interfaces != NULL && node_info->interfaces[ret] != NULL) + ret++; + + g_object_unref (proxy); + g_free (xml_data); + g_dbus_node_info_unref (node_info); + + return ret; +} + +static void +dyna_create_callback (GDBusProxy *proxy, + GAsyncResult *res, + gpointer user_data) +{ + GVariant *result; + GError *error; + + error = NULL; + result = g_dbus_proxy_call_finish (proxy, + res, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_variant_unref (result); + + g_main_loop_quit (loop); +} + +/* Dynamically create @object_name under /foo/dyna */ +static void +dyna_create (GDBusConnection *c, + const gchar *object_name) +{ + GError *error; + GDBusProxy *proxy; + gchar *object_path; + + object_path = g_strconcat ("/foo/dyna/", object_name, NULL); + + error = NULL; + proxy = g_dbus_proxy_new_sync (c, + G_TYPE_DBUS_PROXY, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + g_dbus_connection_get_unique_name (c), + object_path, + "org.example.Dyna", + NULL, + &error); + g_assert_no_error (error); + g_assert (proxy != NULL); + + /* do this async to avoid libdbus-1 deadlocks */ + g_dbus_proxy_call (proxy, + "DynaCyber", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) dyna_create_callback, + NULL); + g_main_loop_run (loop); + + g_assert_no_error (error); + + g_object_unref (proxy); + g_free (object_path); + + return; +} + +typedef struct +{ + guint num_unregistered_calls; + guint num_unregistered_subtree_calls; + guint num_subtree_nodes; +} ObjectRegistrationData; + +static void +on_object_unregistered (gpointer user_data) +{ + ObjectRegistrationData *data = user_data; + + data->num_unregistered_calls++; +} + +static void +on_subtree_unregistered (gpointer user_data) +{ + ObjectRegistrationData *data = user_data; + + data->num_unregistered_subtree_calls++; +} + +static gboolean +_g_strv_has_string (const gchar* const * haystack, + const gchar *needle) +{ + guint n; + + for (n = 0; haystack != NULL && haystack[n] != NULL; n++) + { + if (g_strcmp0 (haystack[n], needle) == 0) + return TRUE; + } + return FALSE; +} + +/* -------------------- */ + +static gchar ** +subtree_enumerate (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + gpointer user_data) +{ + ObjectRegistrationData *data = user_data; + GPtrArray *p; + gchar **nodes; + guint n; + + p = g_ptr_array_new (); + + for (n = 0; n < data->num_subtree_nodes; n++) + { + g_ptr_array_add (p, g_strdup_printf ("vp%d", n)); + g_ptr_array_add (p, g_strdup_printf ("evp%d", n)); + } + g_ptr_array_add (p, NULL); + + nodes = (gchar **) g_ptr_array_free (p, FALSE); + + return nodes; +} + +/* Only allows certain objects, and aborts on unknowns */ +static GPtrArray * +subtree_introspect (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *node, + gpointer user_data) +{ + GPtrArray *interfaces; + + /* VPs implement the Foo interface, EVPs implement the Bar interface. The root + * does not implement any interfaces + */ + interfaces = g_ptr_array_new (); + if (g_str_has_prefix (node, "vp")) + { + g_ptr_array_add (interfaces, (gpointer) &foo_interface_info); + } + else if (g_str_has_prefix (node, "evp")) + { + g_ptr_array_add (interfaces, (gpointer) &bar_interface_info); + } + else if (g_strcmp0 (node, "/") == 0) + { + /* do nothing */ + } + else + { + g_assert_not_reached (); + } + + return interfaces; +} + +static const GDBusInterfaceVTable * +subtree_dispatch (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *node, + gpointer *out_user_data, + gpointer user_data) +{ + if (g_strcmp0 (interface_name, "org.example.Foo") == 0) + return &foo_vtable; + else + return NULL; +} + +static const GDBusSubtreeVTable subtree_vtable = +{ + subtree_enumerate, + subtree_introspect, + subtree_dispatch +}; + +/* -------------------- */ + +static gchar ** +dynamic_subtree_enumerate (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + gpointer user_data) +{ + GPtrArray *data = user_data; + gchar **nodes = g_new (gchar*, data->len + 1); + guint n; + + for (n = 0; n < data->len; n++) + { + nodes[n] = g_strdup (g_ptr_array_index (data, n)); + } + nodes[data->len] = NULL; + + return nodes; +} + +/* Allow all objects to be introspected */ +static GPtrArray * +dynamic_subtree_introspect (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *node, + gpointer user_data) +{ + GPtrArray *interfaces; + + /* All nodes (including the root node) implements the Dyna interface */ + interfaces = g_ptr_array_new (); + g_ptr_array_add (interfaces, (gpointer) &dyna_interface_info); + + return interfaces; +} + +static const GDBusInterfaceVTable * +dynamic_subtree_dispatch (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *node, + gpointer *out_user_data, + gpointer user_data) +{ + *out_user_data = user_data; + return &dyna_interface_vtable; +} + +static const GDBusSubtreeVTable dynamic_subtree_vtable = +{ + dynamic_subtree_enumerate, + dynamic_subtree_introspect, + dynamic_subtree_dispatch +}; + +/* -------------------- */ + +static gpointer +test_dispatch_thread_func (gpointer user_data) +{ + const gchar *object_path = user_data; + GDBusProxy *foo_proxy; + GVariant *value; + GVariant *inner; + GError *error; + gchar *s; + const gchar *value_str; + + foo_proxy = g_dbus_proxy_new_sync (c, + G_TYPE_DBUS_PROXY, + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + g_dbus_connection_get_unique_name (c), + object_path, + "org.example.Foo", + NULL, + &error); + g_assert (foo_proxy != NULL); + + /* generic interfaces */ + error = NULL; + value = g_dbus_proxy_call_sync (foo_proxy, + "org.freedesktop.DBus.Peer.Ping", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_no_error (error); + g_assert (value != NULL); + g_variant_unref (value); + + /* user methods */ + error = NULL; + value = g_dbus_proxy_call_sync (foo_proxy, + "Method1", + g_variant_new ("(s)", "winwinwin"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_no_error (error); + g_assert (value != NULL); + g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(s)"))); + g_variant_get (value, "(s)", &value_str); + g_assert_cmpstr (value_str, ==, "You passed the string `winwinwin'. Jolly good!"); + g_variant_unref (value); + + error = NULL; + value = g_dbus_proxy_call_sync (foo_proxy, + "Method2", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR); + g_assert_cmpstr (error->message, ==, "GDBus.Error:org.example.SomeError: How do you like them apples, buddy!"); + g_error_free (error); + g_assert (value == NULL); + + error = NULL; + value = g_dbus_proxy_call_sync (foo_proxy, + "Method2", + g_variant_new ("(s)", "failfailfail"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); + g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Signature of message, `s', does not match expected signature `'"); + g_error_free (error); + g_assert (value == NULL); + + error = NULL; + value = g_dbus_proxy_call_sync (foo_proxy, + "NonExistantMethod", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD); + g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such method `NonExistantMethod'"); + g_error_free (error); + g_assert (value == NULL); + + /* user properties */ + error = NULL; + value = g_dbus_proxy_call_sync (foo_proxy, + "org.freedesktop.DBus.Properties.Get", + g_variant_new ("(ss)", + "org.example.Foo", + "PropertyUno"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_no_error (error); + g_assert (value != NULL); + g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(v)"))); + g_variant_get (value, "(v)", &inner); + g_assert (g_variant_is_of_type (inner, G_VARIANT_TYPE_STRING)); + g_assert_cmpstr (g_variant_get_string (inner, NULL), ==, "Property `PropertyUno' Is What It Is!"); + g_variant_unref (value); + + error = NULL; + value = g_dbus_proxy_call_sync (foo_proxy, + "org.freedesktop.DBus.Properties.Get", + g_variant_new ("(ss)", + "org.example.Foo", + "ThisDoesntExist"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert (value == NULL); + g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); + g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: No such property `ThisDoesntExist'"); + g_error_free (error); + + error = NULL; + value = g_dbus_proxy_call_sync (foo_proxy, + "org.freedesktop.DBus.Properties.Get", + g_variant_new ("(ss)", + "org.example.Foo", + "NotReadable"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert (value == NULL); + g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); + g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Property `NotReadable' is not readable"); + g_error_free (error); + + error = NULL; + value = g_dbus_proxy_call_sync (foo_proxy, + "org.freedesktop.DBus.Properties.Set", + g_variant_new ("(ssv)", + "org.example.Foo", + "NotReadable", + g_variant_new_string ("But Writable you are!")), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert (value == NULL); + g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FILE_INVALID); + g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.Spawn.FileInvalid: Returning some error instead of writing the value `NotReadable' to the property `'But Writable you are!''"); + g_error_free (error); + + error = NULL; + value = g_dbus_proxy_call_sync (foo_proxy, + "org.freedesktop.DBus.Properties.Set", + g_variant_new ("(ssv)", + "org.example.Foo", + "NotWritable", + g_variant_new_uint32 (42)), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert (value == NULL); + g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); + g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Property `NotWritable' is not writable"); + g_error_free (error); + + error = NULL; + value = g_dbus_proxy_call_sync (foo_proxy, + "org.freedesktop.DBus.Properties.GetAll", + g_variant_new ("(s)", + "org.example.Foo"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_no_error (error); + g_assert (value != NULL); + g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(a{sv})"))); + s = g_variant_print (value, TRUE); + g_assert_cmpstr (s, ==, "({'PropertyUno': <\"Property `PropertyUno' Is What It Is!\">, 'NotWritable': <\"Property `NotWritable' Is What It Is!\">},)"); + g_free (s); + g_variant_unref (value); + + g_object_unref (foo_proxy); + + g_main_loop_quit (loop); + return NULL; +} + +static void +test_dispatch (const gchar *object_path) +{ + GThread *thread; + GError *error; + + /* run this in a thread to avoid deadlocks */ + error = NULL; + thread = g_thread_create (test_dispatch_thread_func, + (gpointer) object_path, + TRUE, + &error); + g_assert_no_error (error); + g_assert (thread != NULL); + g_main_loop_run (loop); + g_thread_join (thread); +} + +static void +test_object_registration (void) +{ + GError *error; + ObjectRegistrationData data; + GPtrArray *dyna_data; + gchar **nodes; + guint boss_foo_reg_id; + guint boss_bar_reg_id; + guint worker1_foo_reg_id; + guint worker2_bar_reg_id; + guint intern1_foo_reg_id; + guint intern2_bar_reg_id; + guint intern2_foo_reg_id; + guint intern3_bar_reg_id; + guint registration_id; + guint subtree_registration_id; + guint non_subtree_object_path_foo_reg_id; + guint non_subtree_object_path_bar_reg_id; + guint dyna_subtree_registration_id; + guint num_successful_registrations; + + data.num_unregistered_calls = 0; + data.num_unregistered_subtree_calls = 0; + data.num_subtree_nodes = 0; + + num_successful_registrations = 0; + + error = NULL; + c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert (c != NULL); + + registration_id = g_dbus_connection_register_object (c, + "/foo/boss", + &foo_interface_info, + &foo_vtable, + &data, + on_object_unregistered, + &error); + g_assert_no_error (error); + g_assert (registration_id > 0); + boss_foo_reg_id = registration_id; + num_successful_registrations++; + + registration_id = g_dbus_connection_register_object (c, + "/foo/boss", + &bar_interface_info, + NULL, + &data, + on_object_unregistered, + &error); + g_assert_no_error (error); + g_assert (registration_id > 0); + boss_bar_reg_id = registration_id; + num_successful_registrations++; + + registration_id = g_dbus_connection_register_object (c, + "/foo/boss/worker1", + &foo_interface_info, + NULL, + &data, + on_object_unregistered, + &error); + g_assert_no_error (error); + g_assert (registration_id > 0); + worker1_foo_reg_id = registration_id; + num_successful_registrations++; + + registration_id = g_dbus_connection_register_object (c, + "/foo/boss/worker2", + &bar_interface_info, + NULL, + &data, + on_object_unregistered, + &error); + g_assert_no_error (error); + g_assert (registration_id > 0); + worker2_bar_reg_id = registration_id; + num_successful_registrations++; + + registration_id = g_dbus_connection_register_object (c, + "/foo/boss/interns/intern1", + &foo_interface_info, + NULL, + &data, + on_object_unregistered, + &error); + g_assert_no_error (error); + g_assert (registration_id > 0); + intern1_foo_reg_id = registration_id; + num_successful_registrations++; + + /* ... and try again at another path */ + registration_id = g_dbus_connection_register_object (c, + "/foo/boss/interns/intern2", + &bar_interface_info, + NULL, + &data, + on_object_unregistered, + &error); + g_assert_no_error (error); + g_assert (registration_id > 0); + intern2_bar_reg_id = registration_id; + num_successful_registrations++; + + /* register at the same path/interface - this should fail */ + registration_id = g_dbus_connection_register_object (c, + "/foo/boss/interns/intern2", + &bar_interface_info, + NULL, + &data, + on_object_unregistered, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS); + g_assert (!g_dbus_error_is_remote_error (error)); + g_error_free (error); + error = NULL; + g_assert (registration_id == 0); + + /* register at different interface - shouldn't fail */ + registration_id = g_dbus_connection_register_object (c, + "/foo/boss/interns/intern2", + &foo_interface_info, + NULL, + &data, + on_object_unregistered, + &error); + g_assert_no_error (error); + g_assert (registration_id > 0); + intern2_foo_reg_id = registration_id; + num_successful_registrations++; + + /* unregister it via the id */ + g_assert (g_dbus_connection_unregister_object (c, registration_id)); + g_assert_cmpint (data.num_unregistered_calls, ==, 1); + intern2_foo_reg_id = 0; + + /* register it back */ + registration_id = g_dbus_connection_register_object (c, + "/foo/boss/interns/intern2", + &foo_interface_info, + NULL, + &data, + on_object_unregistered, + &error); + g_assert_no_error (error); + g_assert (registration_id > 0); + intern2_foo_reg_id = registration_id; + num_successful_registrations++; + + registration_id = g_dbus_connection_register_object (c, + "/foo/boss/interns/intern3", + &bar_interface_info, + NULL, + &data, + on_object_unregistered, + &error); + g_assert_no_error (error); + g_assert (registration_id > 0); + intern3_bar_reg_id = registration_id; + num_successful_registrations++; + + /* now register a whole subtree at /foo/boss/executives */ + subtree_registration_id = g_dbus_connection_register_subtree (c, + "/foo/boss/executives", + &subtree_vtable, + G_DBUS_SUBTREE_FLAGS_NONE, + &data, + on_subtree_unregistered, + &error); + g_assert_no_error (error); + g_assert (subtree_registration_id > 0); + /* try registering it again.. this should fail */ + registration_id = g_dbus_connection_register_subtree (c, + "/foo/boss/executives", + &subtree_vtable, + G_DBUS_SUBTREE_FLAGS_NONE, + &data, + on_subtree_unregistered, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS); + g_assert (!g_dbus_error_is_remote_error (error)); + g_error_free (error); + error = NULL; + g_assert (registration_id == 0); + + /* unregister it, then register it again */ + g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 0); + g_assert (g_dbus_connection_unregister_subtree (c, subtree_registration_id)); + g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 1); + subtree_registration_id = g_dbus_connection_register_subtree (c, + "/foo/boss/executives", + &subtree_vtable, + G_DBUS_SUBTREE_FLAGS_NONE, + &data, + on_subtree_unregistered, + &error); + g_assert_no_error (error); + g_assert (subtree_registration_id > 0); + + /* try to register something under /foo/boss/executives - this should work + * because registered subtrees and registered objects can coexist. + * + * Make the exported object implement *two* interfaces so we can check + * that the right introspection handler is invoked. + */ + registration_id = g_dbus_connection_register_object (c, + "/foo/boss/executives/non_subtree_object", + &bar_interface_info, + NULL, + &data, + on_object_unregistered, + &error); + g_assert_no_error (error); + g_assert (registration_id > 0); + non_subtree_object_path_bar_reg_id = registration_id; + num_successful_registrations++; + registration_id = g_dbus_connection_register_object (c, + "/foo/boss/executives/non_subtree_object", + &foo_interface_info, + NULL, + &data, + on_object_unregistered, + &error); + g_assert_no_error (error); + g_assert (registration_id > 0); + non_subtree_object_path_foo_reg_id = registration_id; + num_successful_registrations++; + + /* now register a dynamic subtree, spawning objects as they are called */ + dyna_data = g_ptr_array_new (); + dyna_subtree_registration_id = g_dbus_connection_register_subtree (c, + "/foo/dyna", + &dynamic_subtree_vtable, + G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES, + dyna_data, + (GDestroyNotify)g_ptr_array_unref, + &error); + g_assert_no_error (error); + g_assert (dyna_subtree_registration_id > 0); + + /* First assert that we have no nodes in the dynamic subtree */ + nodes = get_nodes_at (c, "/foo/dyna"); + g_assert (nodes != NULL); + g_assert_cmpint (g_strv_length (nodes), ==, 0); + g_strfreev (nodes); + g_assert_cmpint (count_interfaces (c, "/foo/dyna"), ==, 4); + + /* Install three nodes in the dynamic subtree via the dyna_data backdoor and + * assert that they show up correctly in the introspection data */ + g_ptr_array_add (dyna_data, "lol"); + g_ptr_array_add (dyna_data, "cat"); + g_ptr_array_add (dyna_data, "cheezburger"); + nodes = get_nodes_at (c, "/foo/dyna"); + g_assert (nodes != NULL); + g_assert_cmpint (g_strv_length (nodes), ==, 3); + g_assert_cmpstr (nodes[0], ==, "lol"); + g_assert_cmpstr (nodes[1], ==, "cat"); + g_assert_cmpstr (nodes[2], ==, "cheezburger"); + g_strfreev (nodes); + g_assert_cmpint (count_interfaces (c, "/foo/dyna/lol"), ==, 4); + g_assert_cmpint (count_interfaces (c, "/foo/dyna/cat"), ==, 4); + g_assert_cmpint (count_interfaces (c, "/foo/dyna/cheezburger"), ==, 4); + + /* Call a non-existing object path and assert that it has been created */ + dyna_create (c, "dynamicallycreated"); + nodes = get_nodes_at (c, "/foo/dyna"); + g_assert (nodes != NULL); + g_assert_cmpint (g_strv_length (nodes), ==, 4); + g_assert_cmpstr (nodes[0], ==, "lol"); + g_assert_cmpstr (nodes[1], ==, "cat"); + g_assert_cmpstr (nodes[2], ==, "cheezburger"); + g_assert_cmpstr (nodes[3], ==, "dynamicallycreated"); + g_strfreev (nodes); + g_assert_cmpint (count_interfaces (c, "/foo/dyna/dynamicallycreated"), ==, 4); + + /* now check that the object hierarachy is properly generated... yes, it's a bit + * perverse that we round-trip to the bus to introspect ourselves ;-) + */ + nodes = get_nodes_at (c, "/"); + g_assert (nodes != NULL); + g_assert_cmpint (g_strv_length (nodes), ==, 1); + g_assert_cmpstr (nodes[0], ==, "foo"); + g_strfreev (nodes); + g_assert_cmpint (count_interfaces (c, "/"), ==, 0); + + nodes = get_nodes_at (c, "/foo"); + g_assert (nodes != NULL); + g_assert_cmpint (g_strv_length (nodes), ==, 2); + g_assert_cmpstr (nodes[0], ==, "boss"); + g_assert_cmpstr (nodes[1], ==, "dyna"); + g_strfreev (nodes); + g_assert_cmpint (count_interfaces (c, "/foo"), ==, 0); + + nodes = get_nodes_at (c, "/foo/boss"); + g_assert (nodes != NULL); + g_assert_cmpint (g_strv_length (nodes), ==, 4); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker1")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker2")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "interns")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "executives")); + g_strfreev (nodes); + /* any registered object always implement org.freedesktop.DBus.[Peer,Introspectable,Properties] */ + g_assert_cmpint (count_interfaces (c, "/foo/boss"), ==, 5); + g_assert (has_interface (c, "/foo/boss", foo_interface_info.name)); + g_assert (has_interface (c, "/foo/boss", bar_interface_info.name)); + + /* check subtree nodes - we should have only non_subtree_object in /foo/boss/executives + * because data.num_subtree_nodes is 0 + */ + nodes = get_nodes_at (c, "/foo/boss/executives"); + g_assert (nodes != NULL); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object")); + g_assert_cmpint (g_strv_length (nodes), ==, 1); + g_strfreev (nodes); + g_assert_cmpint (count_interfaces (c, "/foo/boss/executives"), ==, 0); + + /* now change data.num_subtree_nodes and check */ + data.num_subtree_nodes = 2; + nodes = get_nodes_at (c, "/foo/boss/executives"); + g_assert (nodes != NULL); + g_assert_cmpint (g_strv_length (nodes), ==, 5); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1")); + /* check that /foo/boss/executives/non_subtree_object is not handled by the + * subtree handlers - we can do this because objects from subtree handlers + * has exactly one interface and non_subtree_object has two + */ + g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/non_subtree_object"), ==, 5); + g_assert (has_interface (c, "/foo/boss/executives/non_subtree_object", foo_interface_info.name)); + g_assert (has_interface (c, "/foo/boss/executives/non_subtree_object", bar_interface_info.name)); + /* check that the vp and evp objects are handled by the subtree handlers */ + g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/vp0"), ==, 4); + g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/vp1"), ==, 4); + g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/evp0"), ==, 4); + g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/evp1"), ==, 4); + g_assert (has_interface (c, "/foo/boss/executives/vp0", foo_interface_info.name)); + g_assert (has_interface (c, "/foo/boss/executives/vp1", foo_interface_info.name)); + g_assert (has_interface (c, "/foo/boss/executives/evp0", bar_interface_info.name)); + g_assert (has_interface (c, "/foo/boss/executives/evp1", bar_interface_info.name)); + g_strfreev (nodes); + data.num_subtree_nodes = 3; + nodes = get_nodes_at (c, "/foo/boss/executives"); + g_assert (nodes != NULL); + g_assert_cmpint (g_strv_length (nodes), ==, 7); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp2")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1")); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp2")); + g_strfreev (nodes); + + /* check that calls are properly dispatched to the functions in foo_vtable for objects + * implementing the org.example.Foo interface + * + * We do this for both a regular registered object (/foo/boss) and also for an object + * registered through the subtree mechanism. + */ + test_dispatch ("/foo/boss"); + test_dispatch ("/foo/boss/executives/vp0"); + + /* To prevent from exiting and attaching a D-Bus tool like D-Feet; uncomment: */ +#if 0 + g_debug ("Point D-feet or other tool at: %s", session_bus_get_temporary_address()); + g_main_loop_run (loop); +#endif + + /* check that unregistering the subtree handler works */ + g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 1); + g_assert (g_dbus_connection_unregister_subtree (c, subtree_registration_id)); + g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 2); + nodes = get_nodes_at (c, "/foo/boss/executives"); + g_assert (nodes != NULL); + g_assert_cmpint (g_strv_length (nodes), ==, 1); + g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object")); + g_strfreev (nodes); + + g_assert (g_dbus_connection_unregister_object (c, boss_foo_reg_id)); + g_assert (g_dbus_connection_unregister_object (c, boss_bar_reg_id)); + g_assert (g_dbus_connection_unregister_object (c, worker1_foo_reg_id)); + g_assert (g_dbus_connection_unregister_object (c, worker2_bar_reg_id)); + g_assert (g_dbus_connection_unregister_object (c, intern1_foo_reg_id)); + g_assert (g_dbus_connection_unregister_object (c, intern2_bar_reg_id)); + g_assert (g_dbus_connection_unregister_object (c, intern2_foo_reg_id)); + g_assert (g_dbus_connection_unregister_object (c, intern3_bar_reg_id)); + g_assert (g_dbus_connection_unregister_object (c, non_subtree_object_path_bar_reg_id)); + g_assert (g_dbus_connection_unregister_object (c, non_subtree_object_path_foo_reg_id)); + + g_assert_cmpint (data.num_unregistered_calls, ==, num_successful_registrations); + + /* check that we no longer export any objects - TODO: it looks like there's a bug in + * libdbus-1 here: libdbus still reports the '/foo' object; so disable the test for now + */ +#if 0 + nodes = get_nodes_at (c, "/"); + g_assert (nodes != NULL); + g_assert_cmpint (g_strv_length (nodes), ==, 0); + g_strfreev (nodes); +#endif + + g_object_unref (c); +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +int +main (int argc, + char *argv[]) +{ + gint ret; + + g_type_init (); + g_test_init (&argc, &argv, NULL); + + /* all the tests rely on a shared main loop */ + loop = g_main_loop_new (NULL, FALSE); + + /* all the tests use a session bus with a well-known address that we can bring up and down + * using session_bus_up() and session_bus_down(). + */ + g_unsetenv ("DISPLAY"); + g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE); + + session_bus_up (); + + /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return + * until one can connect to the bus but that's not how things work right now + */ + usleep (500 * 1000); + + g_test_add_func ("/gdbus/object-registration", test_object_registration); + /* TODO: check that we spit out correct introspection data */ + /* TODO: check that registering a whole subtree works */ + + ret = g_test_run(); + + /* tear down bus */ + session_bus_down (); + + return ret; +} diff --git a/gio/tests/gdbus-introspection.c b/gio/tests/gdbus-introspection.c new file mode 100644 index 000000000..a93c776de --- /dev/null +++ b/gio/tests/gdbus-introspection.c @@ -0,0 +1,169 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include +#include +#include + +#include "gdbus-tests.h" + +/* all tests rely on a shared mainloop */ +static GMainLoop *loop = NULL; + +/* ---------------------------------------------------------------------------------------------------- */ +/* Test introspection parser */ +/* ---------------------------------------------------------------------------------------------------- */ + +static void +introspection_on_proxy_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + GDBusProxy *proxy, + gpointer user_data) +{ + GError *error; + const gchar *xml_data; + GDBusNodeInfo *node_info; + const GDBusInterfaceInfo *interface_info; + const GDBusMethodInfo *method_info; + const GDBusSignalInfo *signal_info; + GVariant *result; + + error = NULL; + + /* + * Invoke Introspect(), then parse the output. + */ + result = g_dbus_proxy_call_sync (proxy, + "org.freedesktop.DBus.Introspectable.Introspect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_variant_get (result, "(s)", &xml_data); + + node_info = g_dbus_node_info_new_for_xml (xml_data, &error); + g_assert_no_error (error); + g_assert (node_info != NULL); + + /* for now we only check a couple of things. TODO: check more things */ + + interface_info = g_dbus_node_info_lookup_interface (node_info, "com.example.NonExistantInterface"); + g_assert (interface_info == NULL); + + interface_info = g_dbus_node_info_lookup_interface (node_info, "org.freedesktop.DBus.Introspectable"); + g_assert (interface_info != NULL); + method_info = g_dbus_interface_info_lookup_method (interface_info, "NonExistantMethod"); + g_assert (method_info == NULL); + method_info = g_dbus_interface_info_lookup_method (interface_info, "Introspect"); + g_assert (method_info != NULL); + g_assert (method_info->in_args == NULL); + g_assert (method_info->out_args != NULL); + g_assert (method_info->out_args[0] != NULL); + g_assert (method_info->out_args[1] == NULL); + g_assert_cmpstr (method_info->out_args[0]->signature, ==, "s"); + + interface_info = g_dbus_node_info_lookup_interface (node_info, "com.example.Frob"); + g_assert (interface_info != NULL); + signal_info = g_dbus_interface_info_lookup_signal (interface_info, "TestSignal"); + g_assert (signal_info != NULL); + g_assert (signal_info->args != NULL); + g_assert (signal_info->args[0] != NULL); + g_assert_cmpstr (signal_info->args[0]->signature, ==, "s"); + g_assert (signal_info->args[1] != NULL); + g_assert_cmpstr (signal_info->args[1]->signature, ==, "o"); + g_assert (signal_info->args[2] != NULL); + g_assert_cmpstr (signal_info->args[2]->signature, ==, "v"); + g_assert (signal_info->args[3] == NULL); + + g_dbus_node_info_unref (node_info); + g_variant_unref (result); + + g_main_loop_quit (loop); +} + +static void +introspection_on_proxy_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ +} + +static void +test_introspection_parser (void) +{ + guint watcher_id; + + session_bus_up (); + + watcher_id = g_bus_watch_proxy (G_BUS_TYPE_SESSION, + "com.example.TestService", + G_BUS_NAME_WATCHER_FLAGS_NONE, + "/com/example/TestObject", + "com.example.Frob", + G_TYPE_DBUS_PROXY, + G_DBUS_PROXY_FLAGS_NONE, + introspection_on_proxy_appeared, + introspection_on_proxy_vanished, + NULL, + NULL); + + /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return + * until one can connect to the bus but that's not how things work right now + */ + usleep (500 * 1000); + /* this is safe; testserver will exit once the bus goes away */ + g_assert (g_spawn_command_line_async ("./gdbus-testserver.py", NULL)); + + g_main_loop_run (loop); + + g_bus_unwatch_proxy (watcher_id); + + /* tear down bus */ + session_bus_down (); +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +int +main (int argc, + char *argv[]) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + + /* all the tests rely on a shared main loop */ + loop = g_main_loop_new (NULL, FALSE); + + /* all the tests use a session bus with a well-known address that we can bring up and down + * using session_bus_up() and session_bus_down(). + */ + g_unsetenv ("DISPLAY"); + g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE); + + g_test_add_func ("/gdbus/introspection-parser", test_introspection_parser); + return g_test_run(); +} diff --git a/gio/tests/gdbus-names.c b/gio/tests/gdbus-names.c new file mode 100644 index 000000000..b48d695ad --- /dev/null +++ b/gio/tests/gdbus-names.c @@ -0,0 +1,749 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include +#include + +#include "gdbus-tests.h" + +/* all tests rely on a shared mainloop */ +static GMainLoop *loop; + +/* ---------------------------------------------------------------------------------------------------- */ +/* Test that g_bus_own_name() works correctly */ +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GMainLoop *loop; + gboolean expect_null_connection; + guint num_bus_acquired; + guint num_acquired; + guint num_lost; + guint num_free_func; +} OwnNameData; + +static void +own_name_data_free_func (OwnNameData *data) +{ + data->num_free_func++; + g_main_loop_quit (loop); +} + +static void +bus_acquired_handler (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + OwnNameData *data = user_data; + g_dbus_connection_set_exit_on_close (connection, FALSE); + data->num_bus_acquired += 1; + g_main_loop_quit (loop); +} + +static void +name_acquired_handler (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + OwnNameData *data = user_data; + data->num_acquired += 1; + g_main_loop_quit (loop); +} + +static void +name_lost_handler (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + OwnNameData *data = user_data; + if (data->expect_null_connection) + { + g_assert (connection == NULL); + } + else + { + g_assert (connection != NULL); + g_dbus_connection_set_exit_on_close (connection, FALSE); + } + data->num_lost += 1; + g_main_loop_quit (loop); +} + +static void +test_bus_own_name (void) +{ + guint id; + guint id2; + OwnNameData data; + OwnNameData data2; + const gchar *name; + GDBusConnection *c; + GError *error; + gboolean name_has_owner_reply; + GDBusConnection *c2; + GVariant *result; + + error = NULL; + name = "org.gtk.GDBus.Name1"; + + /* + * First check that name_lost_handler() is invoked if there is no bus. + * + * Also make sure name_lost_handler() isn't invoked when unowning the name. + */ + data.num_bus_acquired = 0; + data.num_free_func = 0; + data.num_acquired = 0; + data.num_lost = 0; + data.expect_null_connection = TRUE; + id = g_bus_own_name (G_BUS_TYPE_SESSION, + name, + G_BUS_NAME_OWNER_FLAGS_NONE, + bus_acquired_handler, + name_acquired_handler, + name_lost_handler, + &data, + (GDestroyNotify) own_name_data_free_func); + g_assert_cmpint (data.num_bus_acquired, ==, 0); + g_assert_cmpint (data.num_acquired, ==, 0); + g_assert_cmpint (data.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data.num_bus_acquired, ==, 0); + g_assert_cmpint (data.num_acquired, ==, 0); + g_assert_cmpint (data.num_lost, ==, 1); + g_bus_unown_name (id); + g_assert_cmpint (data.num_acquired, ==, 0); + g_assert_cmpint (data.num_lost, ==, 1); + g_assert_cmpint (data.num_free_func, ==, 1); + + /* + * Bring up a bus, then own a name and check bus_acquired_handler() then name_acquired_handler() is invoked. + */ + session_bus_up (); + data.num_bus_acquired = 0; + data.num_acquired = 0; + data.num_lost = 0; + data.expect_null_connection = FALSE; + id = g_bus_own_name (G_BUS_TYPE_SESSION, + name, + G_BUS_NAME_OWNER_FLAGS_NONE, + bus_acquired_handler, + name_acquired_handler, + name_lost_handler, + &data, + (GDestroyNotify) own_name_data_free_func); + g_assert_cmpint (data.num_bus_acquired, ==, 0); + g_assert_cmpint (data.num_acquired, ==, 0); + g_assert_cmpint (data.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data.num_bus_acquired, ==, 1); + g_assert_cmpint (data.num_acquired, ==, 0); + g_assert_cmpint (data.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data.num_bus_acquired, ==, 1); + g_assert_cmpint (data.num_acquired, ==, 1); + g_assert_cmpint (data.num_lost, ==, 0); + + /* + * Check that the name was actually acquired. + */ + c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + g_assert (c != NULL); + g_assert (!g_dbus_connection_is_closed (c)); + result = g_dbus_connection_call_sync (c, + "org.freedesktop.DBus", /* bus name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "NameHasOwner", /* method name */ + g_variant_new ("(s)", name), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_variant_get (result, "(b)", &name_has_owner_reply); + g_assert (name_has_owner_reply); + g_variant_unref (result); + + /* + * Stop owning the name - this should invoke our free func + */ + g_bus_unown_name (id); + g_assert_cmpint (data.num_free_func, ==, 2); + + /* + * Check that the name was actually released. + */ + result = g_dbus_connection_call_sync (c, + "org.freedesktop.DBus", /* bus name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "NameHasOwner", /* method name */ + g_variant_new ("(s)", name), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_variant_get (result, "(b)", &name_has_owner_reply); + g_assert (!name_has_owner_reply); + g_variant_unref (result); + + /* + * Own the name again. + */ + data.num_bus_acquired = 0; + data.num_acquired = 0; + data.num_lost = 0; + data.expect_null_connection = FALSE; + id = g_bus_own_name (G_BUS_TYPE_SESSION, + name, + G_BUS_NAME_OWNER_FLAGS_NONE, + bus_acquired_handler, + name_acquired_handler, + name_lost_handler, + &data, + (GDestroyNotify) own_name_data_free_func); + g_assert_cmpint (data.num_bus_acquired, ==, 0); + g_assert_cmpint (data.num_acquired, ==, 0); + g_assert_cmpint (data.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data.num_bus_acquired, ==, 1); + g_assert_cmpint (data.num_acquired, ==, 0); + g_assert_cmpint (data.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data.num_bus_acquired, ==, 1); + g_assert_cmpint (data.num_acquired, ==, 1); + g_assert_cmpint (data.num_lost, ==, 0); + + /* + * Try owning the name with another object on the same connection - this should + * fail because we already own the name. + */ + data2.num_free_func = 0; + data2.num_bus_acquired = 0; + data2.num_acquired = 0; + data2.num_lost = 0; + data2.expect_null_connection = FALSE; + id2 = g_bus_own_name (G_BUS_TYPE_SESSION, + name, + G_BUS_NAME_OWNER_FLAGS_NONE, + bus_acquired_handler, + name_acquired_handler, + name_lost_handler, + &data2, + (GDestroyNotify) own_name_data_free_func); + g_assert_cmpint (data2.num_bus_acquired, ==, 0); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data2.num_bus_acquired, ==, 1); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data2.num_bus_acquired, ==, 1); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 1); + g_bus_unown_name (id2); + g_assert_cmpint (data2.num_bus_acquired, ==, 1); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 1); + g_assert_cmpint (data2.num_free_func, ==, 1); + + /* + * Create a secondary (e.g. private) connection and try owning the name on that + * connection. This should fail both with and without _REPLACE because we + * didn't specify ALLOW_REPLACEMENT. + */ + c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL); + g_assert (c2 != NULL); + g_assert (!g_dbus_connection_is_closed (c2)); + /* first without _REPLACE */ + data2.num_bus_acquired = 0; + data2.num_acquired = 0; + data2.num_lost = 0; + data2.expect_null_connection = FALSE; + data2.num_free_func = 0; + id2 = g_bus_own_name_on_connection (c2, + name, + G_BUS_NAME_OWNER_FLAGS_NONE, + name_acquired_handler, + name_lost_handler, + &data2, + (GDestroyNotify) own_name_data_free_func); + g_assert_cmpint (data2.num_bus_acquired, ==, 0); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data2.num_bus_acquired, ==, 0); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 1); + g_bus_unown_name (id2); + g_assert_cmpint (data2.num_bus_acquired, ==, 0); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 1); + g_assert_cmpint (data2.num_free_func, ==, 1); + /* then with _REPLACE */ + data2.num_bus_acquired = 0; + data2.num_acquired = 0; + data2.num_lost = 0; + data2.expect_null_connection = FALSE; + data2.num_free_func = 0; + id2 = g_bus_own_name_on_connection (c2, + name, + G_BUS_NAME_OWNER_FLAGS_REPLACE, + name_acquired_handler, + name_lost_handler, + &data2, + (GDestroyNotify) own_name_data_free_func); + g_assert_cmpint (data2.num_bus_acquired, ==, 0); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data2.num_bus_acquired, ==, 0); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 1); + g_bus_unown_name (id2); + g_assert_cmpint (data2.num_bus_acquired, ==, 0); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 1); + g_assert_cmpint (data2.num_free_func, ==, 1); + + /* + * Stop owning the name and grab it again with _ALLOW_REPLACEMENT. + */ + data.expect_null_connection = FALSE; + g_bus_unown_name (id); + g_assert_cmpint (data.num_bus_acquired, ==, 1); + g_assert_cmpint (data.num_acquired, ==, 1); + g_assert_cmpint (data.num_free_func, ==, 3); + /* grab it again */ + data.num_bus_acquired = 0; + data.num_acquired = 0; + data.num_lost = 0; + data.expect_null_connection = FALSE; + id = g_bus_own_name (G_BUS_TYPE_SESSION, + name, + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, + bus_acquired_handler, + name_acquired_handler, + name_lost_handler, + &data, + (GDestroyNotify) own_name_data_free_func); + g_assert_cmpint (data.num_bus_acquired, ==, 0); + g_assert_cmpint (data.num_acquired, ==, 0); + g_assert_cmpint (data.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data.num_bus_acquired, ==, 1); + g_assert_cmpint (data.num_acquired, ==, 0); + g_assert_cmpint (data.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data.num_bus_acquired, ==, 1); + g_assert_cmpint (data.num_acquired, ==, 1); + g_assert_cmpint (data.num_lost, ==, 0); + + /* + * Now try to grab the name from the secondary connection. + * + */ + /* first without _REPLACE - this won't make us acquire the name */ + data2.num_bus_acquired = 0; + data2.num_acquired = 0; + data2.num_lost = 0; + data2.expect_null_connection = FALSE; + data2.num_free_func = 0; + id2 = g_bus_own_name_on_connection (c2, + name, + G_BUS_NAME_OWNER_FLAGS_NONE, + name_acquired_handler, + name_lost_handler, + &data2, + (GDestroyNotify) own_name_data_free_func); + g_assert_cmpint (data2.num_bus_acquired, ==, 0); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data2.num_bus_acquired, ==, 0); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 1); + g_bus_unown_name (id2); + g_assert_cmpint (data2.num_bus_acquired, ==, 0); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 1); + g_assert_cmpint (data2.num_free_func, ==, 1); + /* then with _REPLACE - here we should acquire the name - e.g. owner should lose it + * and owner2 should acquire it */ + data2.num_bus_acquired = 0; + data2.num_acquired = 0; + data2.num_lost = 0; + data2.expect_null_connection = FALSE; + data2.num_free_func = 0; + id2 = g_bus_own_name_on_connection (c2, + name, + G_BUS_NAME_OWNER_FLAGS_REPLACE, + name_acquired_handler, + name_lost_handler, + &data2, + (GDestroyNotify) own_name_data_free_func); + g_assert_cmpint (data.num_acquired, ==, 1); + g_assert_cmpint (data.num_lost, ==, 0); + g_assert_cmpint (data2.num_acquired, ==, 0); + g_assert_cmpint (data2.num_lost, ==, 0); + /* wait for handlers for both owner and owner2 to fire */ + while (data.num_lost == 0 || data2.num_acquired == 0) + g_main_loop_run (loop); + g_assert_cmpint (data.num_acquired, ==, 1); + g_assert_cmpint (data.num_lost, ==, 1); + g_assert_cmpint (data2.num_acquired, ==, 1); + g_assert_cmpint (data2.num_lost, ==, 0); + g_assert_cmpint (data2.num_bus_acquired, ==, 0); + /* ok, make owner2 release the name - then wait for owner to automagically reacquire it */ + g_bus_unown_name (id2); + g_assert_cmpint (data2.num_free_func, ==, 1); + g_main_loop_run (loop); + g_assert_cmpint (data.num_acquired, ==, 2); + g_assert_cmpint (data.num_lost, ==, 1); + + /* + * Finally, nuke the bus and check name_lost_handler() is invoked. + * + */ + data.expect_null_connection = TRUE; + session_bus_down (); + while (data.num_lost != 2) + g_main_loop_run (loop); + g_assert_cmpint (data.num_acquired, ==, 2); + g_assert_cmpint (data.num_lost, ==, 2); + g_bus_unown_name (id); + g_assert_cmpint (data.num_free_func, ==, 4); + + g_object_unref (c); + g_object_unref (c2); +} + +/* ---------------------------------------------------------------------------------------------------- */ +/* Test that g_bus_watch_name() works correctly */ +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + gboolean expect_null_connection; + guint num_acquired; + guint num_lost; + guint num_appeared; + guint num_vanished; + guint num_free_func; +} WatchNameData; + +static void +watch_name_data_free_func (WatchNameData *data) +{ + data->num_free_func++; + g_main_loop_quit (loop); +} + +static void +w_bus_acquired_handler (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ +} + +static void +w_name_acquired_handler (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + WatchNameData *data = user_data; + data->num_acquired += 1; + g_main_loop_quit (loop); +} + +static void +w_name_lost_handler (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + WatchNameData *data = user_data; + data->num_lost += 1; + g_main_loop_quit (loop); +} + +static void +name_appeared_handler (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + WatchNameData *data = user_data; + if (data->expect_null_connection) + { + g_assert (connection == NULL); + } + else + { + g_assert (connection != NULL); + g_dbus_connection_set_exit_on_close (connection, FALSE); + } + data->num_appeared += 1; + g_main_loop_quit (loop); +} + +static void +name_vanished_handler (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + WatchNameData *data = user_data; + if (data->expect_null_connection) + { + g_assert (connection == NULL); + } + else + { + g_assert (connection != NULL); + g_dbus_connection_set_exit_on_close (connection, FALSE); + } + data->num_vanished += 1; + g_main_loop_quit (loop); +} + +static void +test_bus_watch_name (void) +{ + WatchNameData data; + guint id; + guint owner_id; + + /* + * First check that name_vanished_handler() is invoked if there is no bus. + * + * Also make sure name_vanished_handler() isn't invoked when unwatching the name. + */ + data.num_free_func = 0; + data.num_appeared = 0; + data.num_vanished = 0; + data.expect_null_connection = TRUE; + id = g_bus_watch_name (G_BUS_TYPE_SESSION, + "org.gtk.GDBus.Name1", + G_BUS_NAME_WATCHER_FLAGS_NONE, + name_appeared_handler, + name_vanished_handler, + &data, + (GDestroyNotify) watch_name_data_free_func); + g_assert_cmpint (data.num_appeared, ==, 0); + g_assert_cmpint (data.num_vanished, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data.num_appeared, ==, 0); + g_assert_cmpint (data.num_vanished, ==, 1); + g_bus_unwatch_name (id); + g_assert_cmpint (data.num_appeared, ==, 0); + g_assert_cmpint (data.num_vanished, ==, 1); + g_assert_cmpint (data.num_free_func, ==, 1); + + /* + * Now bring up a bus, own a name, and then start watching it. + */ + session_bus_up (); + /* own the name */ + data.num_free_func = 0; + data.num_acquired = 0; + data.num_lost = 0; + data.expect_null_connection = FALSE; + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.gtk.GDBus.Name1", + G_BUS_NAME_OWNER_FLAGS_NONE, + w_bus_acquired_handler, + w_name_acquired_handler, + w_name_lost_handler, + &data, + (GDestroyNotify) watch_name_data_free_func); + g_main_loop_run (loop); + g_assert_cmpint (data.num_acquired, ==, 1); + g_assert_cmpint (data.num_lost, ==, 0); + /* now watch the name */ + data.num_appeared = 0; + data.num_vanished = 0; + id = g_bus_watch_name (G_BUS_TYPE_SESSION, + "org.gtk.GDBus.Name1", + G_BUS_NAME_WATCHER_FLAGS_NONE, + name_appeared_handler, + name_vanished_handler, + &data, + (GDestroyNotify) watch_name_data_free_func); + g_assert_cmpint (data.num_appeared, ==, 0); + g_assert_cmpint (data.num_vanished, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data.num_appeared, ==, 1); + g_assert_cmpint (data.num_vanished, ==, 0); + + /* + * Unwatch the name. + */ + g_bus_unwatch_name (id); + g_assert_cmpint (data.num_free_func, ==, 1); + + /* unown the name */ + g_bus_unown_name (owner_id); + g_assert_cmpint (data.num_acquired, ==, 1); + g_assert_cmpint (data.num_free_func, ==, 2); + + /* + * Create a watcher and then make a name be owned. + * + * This should trigger name_appeared_handler() ... + */ + /* watch the name */ + data.num_appeared = 0; + data.num_vanished = 0; + data.num_free_func = 0; + id = g_bus_watch_name (G_BUS_TYPE_SESSION, + "org.gtk.GDBus.Name1", + G_BUS_NAME_WATCHER_FLAGS_NONE, + name_appeared_handler, + name_vanished_handler, + &data, + (GDestroyNotify) watch_name_data_free_func); + g_assert_cmpint (data.num_appeared, ==, 0); + g_assert_cmpint (data.num_vanished, ==, 0); + g_main_loop_run (loop); + g_assert_cmpint (data.num_appeared, ==, 0); + g_assert_cmpint (data.num_vanished, ==, 1); + + /* own the name */ + data.num_acquired = 0; + data.num_lost = 0; + data.expect_null_connection = FALSE; + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.gtk.GDBus.Name1", + G_BUS_NAME_OWNER_FLAGS_NONE, + w_bus_acquired_handler, + w_name_acquired_handler, + w_name_lost_handler, + &data, + (GDestroyNotify) watch_name_data_free_func); + while (data.num_acquired == 0 || data.num_appeared == 0) + g_main_loop_run (loop); + g_assert_cmpint (data.num_acquired, ==, 1); + g_assert_cmpint (data.num_lost, ==, 0); + g_assert_cmpint (data.num_appeared, ==, 1); + g_assert_cmpint (data.num_vanished, ==, 1); + + /* + * Nuke the bus and check that the name vanishes and is lost. + */ + data.expect_null_connection = TRUE; + session_bus_down (); + g_main_loop_run (loop); + g_assert_cmpint (data.num_lost, ==, 1); + g_assert_cmpint (data.num_vanished, ==, 2); + + g_bus_unwatch_name (id); + g_assert_cmpint (data.num_free_func, ==, 1); + + g_bus_unown_name (owner_id); + g_assert_cmpint (data.num_free_func, ==, 2); + +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +test_validate_names (void) +{ + guint n; + static const struct + { + gboolean name; + gboolean unique; + gboolean interface; + const gchar *string; + } names[] = { + { 1, 0, 1, "valid.well_known.name"}, + { 1, 0, 0, "valid.well-known.name"}, + { 1, 1, 0, ":valid.unique.name"}, + { 0, 0, 0, "invalid.5well_known.name"}, + { 0, 0, 0, "4invalid.5well_known.name"}, + { 1, 1, 0, ":4valid.5unique.name"}, + { 0, 0, 0, ""}, + { 1, 0, 1, "very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.name1"}, /* 255 */ + { 0, 0, 0, "very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.name12"}, /* 256 - too long! */ + { 0, 0, 0, ".starts.with.a.dot"}, + { 0, 0, 0, "contains.invalid;.characters"}, + { 0, 0, 0, "contains.inva/lid.characters"}, + { 0, 0, 0, "contains.inva[lid.characters"}, + { 0, 0, 0, "contains.inva]lid.characters"}, + { 0, 0, 0, "contains.inva_æøå_lid.characters"}, + { 1, 1, 0, ":1.1"}, + }; + + for (n = 0; n < G_N_ELEMENTS (names); n++) + { + if (names[n].name) + g_assert (g_dbus_is_name (names[n].string)); + else + g_assert (!g_dbus_is_name (names[n].string)); + + if (names[n].unique) + g_assert (g_dbus_is_unique_name (names[n].string)); + else + g_assert (!g_dbus_is_unique_name (names[n].string)); + + if (names[n].interface) + g_assert (g_dbus_is_interface_name (names[n].string)); + else + g_assert (!g_dbus_is_interface_name (names[n].string)); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +int +main (int argc, + char *argv[]) +{ + gint ret; + + g_type_init (); + g_test_init (&argc, &argv, NULL); + + loop = g_main_loop_new (NULL, FALSE); + + /* all the tests use a session bus with a well-known address that we can bring up and down + * using session_bus_up() and session_bus_down(). + */ + g_unsetenv ("DISPLAY"); + g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE); + + g_test_add_func ("/gdbus/validate-names", test_validate_names); + g_test_add_func ("/gdbus/bus-own-name", test_bus_own_name); + g_test_add_func ("/gdbus/bus-watch-name", test_bus_watch_name); + + ret = g_test_run(); + + g_main_loop_unref (loop); + + return ret; +} diff --git a/gio/tests/gdbus-peer.c b/gio/tests/gdbus-peer.c new file mode 100644 index 000000000..929d5c2a1 --- /dev/null +++ b/gio/tests/gdbus-peer.c @@ -0,0 +1,747 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include +#include +#include + +/* for open(2) */ +#include +#include +#include + +#include +#include + +#include "gdbus-tests.h" + + +#ifdef G_OS_UNIX +static gboolean is_unix = TRUE; +#else +static gboolean is_unix = FALSE; +#endif + +static gchar *test_guid = NULL; +static GMainLoop *service_loop = NULL; +static GDBusServer *server = NULL; +static GMainLoop *loop = NULL; + +/* ---------------------------------------------------------------------------------------------------- */ +/* Test that peer-to-peer connections work */ +/* ---------------------------------------------------------------------------------------------------- */ + + +typedef struct +{ + gboolean accept_connection; + gint num_connection_attempts; + GPtrArray *current_connections; + guint num_method_calls; + gboolean signal_received; +} PeerData; + +static const gchar *test_interface_introspection_xml = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; +static const GDBusInterfaceInfo *test_interface_introspection_data = NULL; + +static void +test_interface_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + PeerData *data = user_data; + + data->num_method_calls++; + + g_assert_cmpstr (object_path, ==, "/org/gtk/GDBus/PeerTestObject"); + g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.PeerTestInterface"); + + if (g_strcmp0 (method_name, "HelloPeer") == 0) + { + const gchar *greeting; + gchar *response; + + g_variant_get (parameters, "(s)", &greeting); + + response = g_strdup_printf ("You greeted me with '%s'.", + greeting); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(s)", response)); + g_free (response); + } + else if (g_strcmp0 (method_name, "EmitSignal") == 0) + { + GError *error; + + error = NULL; + g_dbus_connection_emit_signal (connection, + NULL, + "/org/gtk/GDBus/PeerTestObject", + "org.gtk.GDBus.PeerTestInterface", + "PeerSignal", + NULL, + &error); + g_assert_no_error (error); + g_dbus_method_invocation_return_value (invocation, NULL); + } + else if (g_strcmp0 (method_name, "OpenFile") == 0) + { + const gchar *path; + GDBusMessage *reply; + GError *error; + gint fd; + GUnixFDList *fd_list; + + g_variant_get (parameters, "(s)", &path); + + fd_list = g_unix_fd_list_new (); + + error = NULL; + + fd = open (path, O_RDONLY); + g_unix_fd_list_append (fd_list, fd, &error); + g_assert_no_error (error); + close (fd); + + reply = g_dbus_message_new_method_reply (g_dbus_method_invocation_get_message (invocation)); + g_dbus_message_set_unix_fd_list (reply, fd_list); + g_object_unref (invocation); + + error = NULL; + g_dbus_connection_send_message (connection, + reply, + NULL, /* out_serial */ + &error); + g_assert_no_error (error); + g_object_unref (reply); + } + else + { + g_assert_not_reached (); + } +} + +static GVariant * +test_interface_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + g_assert_cmpstr (object_path, ==, "/org/gtk/GDBus/PeerTestObject"); + g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.PeerTestInterface"); + g_assert_cmpstr (property_name, ==, "PeerProperty"); + + return g_variant_new_string ("ThePropertyValue"); +} + + +static const GDBusInterfaceVTable test_interface_vtable = +{ + test_interface_method_call, + test_interface_get_property, + NULL /* set_property */ +}; + +static void +on_proxy_signal_received (GDBusProxy *proxy, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + PeerData *data = user_data; + + data->signal_received = TRUE; + + g_assert (sender_name == NULL); + g_assert_cmpstr (signal_name, ==, "PeerSignal"); + g_main_loop_quit (loop); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +on_authorize_authenticated_peer (GDBusAuthObserver *observer, + GIOStream *stream, + GCredentials *credentials, + gpointer user_data) +{ + PeerData *data = user_data; + gboolean authorized; + + data->num_connection_attempts++; + + authorized = TRUE; + if (!data->accept_connection) + { + authorized = FALSE; + g_main_loop_quit (loop); + } + + return authorized; +} + +/* Runs in thread we created GDBusServer in (since we didn't pass G_DBUS_SERVER_FLAGS_RUN_IN_THREAD) */ +static void +on_new_connection (GDBusServer *server, + GDBusConnection *connection, + gpointer user_data) +{ + PeerData *data = user_data; + GError *error; + guint reg_id; + + //g_print ("Client connected.\n" + // "Negotiated capabilities: unix-fd-passing=%d\n", + // g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING); + + g_ptr_array_add (data->current_connections, g_object_ref (connection)); + + /* export object on the newly established connection */ + error = NULL; + reg_id = g_dbus_connection_register_object (connection, + "/org/gtk/GDBus/PeerTestObject", + test_interface_introspection_data, + &test_interface_vtable, + data, + NULL, /* GDestroyNotify for data */ + &error); + g_assert_no_error (error); + g_assert (reg_id > 0); + + g_main_loop_quit (loop); +} + +static gpointer +service_thread_func (gpointer user_data) +{ + PeerData *data = user_data; + GMainContext *service_context; + GDBusAuthObserver *observer; + GError *error; + + service_context = g_main_context_new (); + g_main_context_push_thread_default (service_context); + + error = NULL; + observer = g_dbus_auth_observer_new (); + server = g_dbus_server_new_sync (is_unix ? "unix:tmpdir=/tmp/gdbus-test-" : "nonce-tcp:", + G_DBUS_SERVER_FLAGS_NONE, + test_guid, + observer, + NULL, /* cancellable */ + &error); + g_assert_no_error (error); + + g_signal_connect (server, + "new-connection", + G_CALLBACK (on_new_connection), + data); + g_signal_connect (observer, + "authorize-authenticated-peer", + G_CALLBACK (on_authorize_authenticated_peer), + data); + g_object_unref (observer); + + g_dbus_server_start (server); + + service_loop = g_main_loop_new (service_context, FALSE); + g_main_loop_run (service_loop); + + g_main_context_pop_thread_default (service_context); + + g_main_loop_unref (service_loop); + g_main_context_unref (service_context); + + /* test code specifically unrefs the server - see below */ + g_assert (server == NULL); + + return NULL; +} + +#if 0 +static gboolean +on_incoming_connection (GSocketService *service, + GSocketConnection *socket_connection, + GObject *source_object, + gpointer user_data) +{ + PeerData *data = user_data; + + if (data->accept_connection) + { + GError *error; + guint reg_id; + GDBusConnection *connection; + + error = NULL; + connection = g_dbus_connection_new_sync (G_IO_STREAM (socket_connection), + test_guid, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER, + NULL, /* cancellable */ + &error); + g_assert_no_error (error); + + g_ptr_array_add (data->current_connections, connection); + + /* export object on the newly established connection */ + error = NULL; + reg_id = g_dbus_connection_register_object (connection, + "/org/gtk/GDBus/PeerTestObject", + &test_interface_introspection_data, + &test_interface_vtable, + data, + NULL, /* GDestroyNotify for data */ + &error); + g_assert_no_error (error); + g_assert (reg_id > 0); + + } + else + { + /* don't do anything */ + } + + data->num_connection_attempts++; + + g_main_loop_quit (loop); + + /* stops other signal handlers from being invoked */ + return TRUE; +} + +static gpointer +service_thread_func (gpointer data) +{ + GMainContext *service_context; + gchar *socket_path; + GSocketAddress *address; + GError *error; + + service_context = g_main_context_new (); + g_main_context_push_thread_default (service_context); + + socket_path = g_strdup_printf ("/tmp/gdbus-test-pid-%d", getpid ()); + address = g_unix_socket_address_new (socket_path); + + service = g_socket_service_new (); + error = NULL; + g_socket_listener_add_address (G_SOCKET_LISTENER (service), + address, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + NULL, /* source_object */ + NULL, /* effective_address */ + &error); + g_assert_no_error (error); + g_signal_connect (service, + "incoming", + G_CALLBACK (on_incoming_connection), + data); + g_socket_service_start (service); + + service_loop = g_main_loop_new (service_context, FALSE); + g_main_loop_run (service_loop); + + g_main_context_pop_thread_default (service_context); + + g_main_loop_unref (service_loop); + g_main_context_unref (service_context); + + g_object_unref (address); + g_free (socket_path); + return NULL; +} +#endif + +/* ---------------------------------------------------------------------------------------------------- */ + +#if 0 +static gboolean +check_connection (gpointer user_data) +{ + PeerData *data = user_data; + guint n; + + for (n = 0; n < data->current_connections->len; n++) + { + GDBusConnection *c; + GIOStream *stream; + + c = G_DBUS_CONNECTION (data->current_connections->pdata[n]); + stream = g_dbus_connection_get_stream (c); + + g_debug ("In check_connection for %d: connection %p, stream %p", n, c, stream); + g_debug ("closed = %d", g_io_stream_is_closed (stream)); + + GSocket *socket; + socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream)); + g_debug ("socket_closed = %d", g_socket_is_closed (socket)); + g_debug ("socket_condition_check = %d", g_socket_condition_check (socket, G_IO_IN|G_IO_OUT|G_IO_ERR|G_IO_HUP)); + + gchar buf[128]; + GError *error; + gssize num_read; + error = NULL; + num_read = g_input_stream_read (g_io_stream_get_input_stream (stream), + buf, + 128, + NULL, + &error); + if (num_read < 0) + { + g_debug ("error: %s", error->message); + g_error_free (error); + } + else + { + g_debug ("no error, read %d bytes", (gint) num_read); + } + } + + return FALSE; +} + +static gboolean +on_do_disconnect_in_idle (gpointer data) +{ + GDBusConnection *c = G_DBUS_CONNECTION (data); + g_debug ("GDC %p has ref_count %d", c, G_OBJECT (c)->ref_count); + g_dbus_connection_disconnect (c); + g_object_unref (c); + return FALSE; +} +#endif + +static void +test_peer (void) +{ + GDBusConnection *c; + GDBusConnection *c2; + GDBusProxy *proxy; + GError *error; + PeerData data; + GVariant *value; + GVariant *result; + const gchar *s; + GThread *service_thread; + + memset (&data, '\0', sizeof (PeerData)); + data.current_connections = g_ptr_array_new_with_free_func (g_object_unref); + + /* first try to connect when there is no server */ + error = NULL; + c = g_dbus_connection_new_for_address_sync (is_unix ? "unix:path=/tmp/gdbus-test-does-not-exist-pid" : + /* NOTE: Even if something is listening on port 12345 the connection + * will fail because the nonce file doesn't exist */ + "nonce-tcp:host=localhost,port=12345,noncefile=this-does-not-exist-gdbus", + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, /* GDBusAuthObserver */ + NULL, /* cancellable */ + &error); + _g_assert_error_domain (error, G_IO_ERROR); + g_assert (!g_dbus_error_is_remote_error (error)); + g_clear_error (&error); + g_assert (c == NULL); + + /* bring up a server - we run the server in a different thread to avoid deadlocks */ + error = NULL; + service_thread = g_thread_create (service_thread_func, + &data, + TRUE, + &error); + while (service_loop == NULL) + g_thread_yield (); + g_assert (server != NULL); + + /* bring up a connection and accept it */ + data.accept_connection = TRUE; + error = NULL; + c = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (server), + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, /* GDBusAuthObserver */ + NULL, /* cancellable */ + &error); + g_assert_no_error (error); + g_assert (c != NULL); + while (data.current_connections->len < 1) + g_main_loop_run (loop); + g_assert_cmpint (data.current_connections->len, ==, 1); + g_assert_cmpint (data.num_connection_attempts, ==, 1); + g_assert (g_dbus_connection_get_unique_name (c) == NULL); + g_assert_cmpstr (g_dbus_connection_get_guid (c), ==, test_guid); + + /* check that we create a proxy, read properties, receive signals and invoke + * the HelloPeer() method. Since the server runs in another thread it's fine + * to use synchronous blocking API here. + */ + error = NULL; + proxy = g_dbus_proxy_new_sync (c, + G_TYPE_DBUS_PROXY, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + NULL, /* bus_name */ + "/org/gtk/GDBus/PeerTestObject", + "org.gtk.GDBus.PeerTestInterface", + NULL, /* GCancellable */ + &error); + g_assert_no_error (error); + g_assert (proxy != NULL); + error = NULL; + value = g_dbus_proxy_get_cached_property (proxy, "PeerProperty"); + g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "ThePropertyValue"); + + /* try invoking a method */ + error = NULL; + result = g_dbus_proxy_call_sync (proxy, + "HelloPeer", + g_variant_new ("(s)", "Hey Peer!"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, /* GCancellable */ + &error); + g_assert_no_error (error); + g_variant_get (result, "(s)", &s); + g_assert_cmpstr (s, ==, "You greeted me with 'Hey Peer!'."); + g_variant_unref (result); + g_assert_cmpint (data.num_method_calls, ==, 1); + + /* make the other peer emit a signal - catch it */ + g_signal_connect (proxy, + "g-signal", + G_CALLBACK (on_proxy_signal_received), + &data); + g_assert (!data.signal_received); + g_dbus_proxy_call (proxy, + "EmitSignal", + NULL, /* no arguments */ + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, /* GCancellable */ + NULL, /* GAsyncReadyCallback - we don't care about the result */ + NULL); /* user_data */ + g_main_loop_run (loop); + g_assert (data.signal_received); + g_assert_cmpint (data.num_method_calls, ==, 2); + + /* check for UNIX fd passing */ +#ifdef G_OS_UNIX + { + GDBusMessage *method_call_message; + GDBusMessage *method_reply_message; + GUnixFDList *fd_list; + gint fd; + gchar buf[1024]; + gssize len; + gchar *buf2; + gsize len2; + + method_call_message = g_dbus_message_new_method_call (NULL, /* name */ + "/org/gtk/GDBus/PeerTestObject", + "org.gtk.GDBus.PeerTestInterface", + "OpenFile"); + g_dbus_message_set_body (method_call_message, g_variant_new ("(s)", "/etc/hosts")); + error = NULL; + method_reply_message = g_dbus_connection_send_message_with_reply_sync (c, + method_call_message, + -1, + NULL, /* out_serial */ + NULL, /* cancellable */ + &error); + g_assert_no_error (error); + g_assert (g_dbus_message_get_message_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_METHOD_RETURN); + fd_list = g_dbus_message_get_unix_fd_list (method_reply_message); + g_assert (fd_list != NULL); + g_assert_cmpint (g_unix_fd_list_get_length (fd_list), ==, 1); + error = NULL; + fd = g_unix_fd_list_get (fd_list, 0, &error); + g_assert_no_error (error); + g_object_unref (method_call_message); + g_object_unref (method_reply_message); + + memset (buf, '\0', sizeof (buf)); + len = read (fd, buf, sizeof (buf) - 1); + close (fd); + + error = NULL; + g_file_get_contents ("/etc/hosts", + &buf2, + &len2, + &error); + g_assert_no_error (error); + if (len2 > sizeof (buf)) + buf2[sizeof (buf)] = '\0'; + g_assert_cmpstr (buf, ==, buf2); + g_free (buf2); + } +#endif /* G_OS_UNIX */ + + + /* bring up a connection - don't accept it - this should fail + */ + data.accept_connection = FALSE; + error = NULL; + c2 = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (server), + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, /* GDBusAuthObserver */ + NULL, /* cancellable */ + &error); + _g_assert_error_domain (error, G_IO_ERROR); + g_assert (c2 == NULL); + +#if 0 + /* TODO: THIS TEST DOESN'T WORK YET */ + + /* bring up a connection - accept it.. then disconnect from the client side - check + * that the server side gets the disconnect signal. + */ + error = NULL; + data.accept_connection = TRUE; + c2 = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (server), + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, /* GDBusAuthObserver */ + NULL, /* cancellable */ + &error); + g_assert_no_error (error); + g_assert (c2 != NULL); + g_assert (!g_dbus_connection_get_is_disconnected (c2)); + while (data.num_connection_attempts < 3) + g_main_loop_run (loop); + g_assert_cmpint (data.current_connections->len, ==, 2); + g_assert_cmpint (data.num_connection_attempts, ==, 3); + g_assert (!g_dbus_connection_get_is_disconnected (G_DBUS_CONNECTION (data.current_connections->pdata[1]))); + g_idle_add (on_do_disconnect_in_idle, c2); + g_debug ("=================================================="); + g_debug ("=================================================="); + g_debug ("=================================================="); + g_debug ("waiting for disconnect on connection %p, stream %p", + data.current_connections->pdata[1], + g_dbus_connection_get_stream (data.current_connections->pdata[1])); + + g_timeout_add (2000, check_connection, &data); + //_g_assert_signal_received (G_DBUS_CONNECTION (data.current_connections->pdata[1]), "closed"); + g_main_loop_run (loop); + g_assert (g_dbus_connection_get_is_disconnected (G_DBUS_CONNECTION (data.current_connections->pdata[1]))); + g_ptr_array_set_size (data.current_connections, 1); /* remove disconnected connection object */ +#endif + + /* unref the server and stop listening for new connections + * + * This won't bring down the established connections - check that c is still connected + * by invoking a method + */ + //g_socket_service_stop (service); + //g_object_unref (service); + g_dbus_server_stop (server); + g_object_unref (server); + server = NULL; + + error = NULL; + result = g_dbus_proxy_call_sync (proxy, + "HelloPeer", + g_variant_new ("(s)", "Hey Again Peer!"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, /* GCancellable */ + &error); + g_assert_no_error (error); + g_variant_get (result, "(s)", &s); + g_assert_cmpstr (s, ==, "You greeted me with 'Hey Again Peer!'."); + g_variant_unref (result); + g_assert_cmpint (data.num_method_calls, ==, 4); + +#if 0 + /* TODO: THIS TEST DOESN'T WORK YET */ + + /* now disconnect from the server side - check that the client side gets the signal */ + g_assert_cmpint (data.current_connections->len, ==, 1); + g_assert (G_DBUS_CONNECTION (data.current_connections->pdata[0]) != c); + g_dbus_connection_disconnect (G_DBUS_CONNECTION (data.current_connections->pdata[0])); + if (!g_dbus_connection_get_is_disconnected (c)) + _g_assert_signal_received (c, "closed"); + g_assert (g_dbus_connection_get_is_disconnected (c)); +#endif + + g_object_unref (c); + g_ptr_array_unref (data.current_connections); + g_object_unref (proxy); + + g_main_loop_quit (service_loop); + g_thread_join (service_thread); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +int +main (int argc, + char *argv[]) +{ + gint ret; + GDBusNodeInfo *introspection_data = NULL; + + g_type_init (); + g_thread_init (NULL); + g_test_init (&argc, &argv, NULL); + + introspection_data = g_dbus_node_info_new_for_xml (test_interface_introspection_xml, NULL); + g_assert (introspection_data != NULL); + test_interface_introspection_data = introspection_data->interfaces[0]; + + test_guid = g_dbus_generate_guid (); + + /* all the tests rely on a shared main loop */ + loop = g_main_loop_new (NULL, FALSE); + + g_test_add_func ("/gdbus/peer-to-peer", test_peer); + + ret = g_test_run(); + + g_main_loop_unref (loop); + g_free (test_guid); + g_dbus_node_info_unref (introspection_data); + + return ret; +} diff --git a/gio/tests/gdbus-proxy.c b/gio/tests/gdbus-proxy.c new file mode 100644 index 000000000..a0fabd8a0 --- /dev/null +++ b/gio/tests/gdbus-proxy.c @@ -0,0 +1,487 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include +#include +#include + +#include "gdbus-tests.h" + +/* all tests rely on a shared mainloop */ +static GMainLoop *loop = NULL; + +/* ---------------------------------------------------------------------------------------------------- */ +/* Test that the method aspects of GDBusProxy works */ +/* ---------------------------------------------------------------------------------------------------- */ + +static void +test_methods (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + GDBusProxy *proxy) +{ + GVariant *result; + GError *error; + const gchar *str; + gchar *dbus_error_name; + + /* check that we can invoke a method */ + error = NULL; + result = g_dbus_proxy_call_sync (proxy, + "HelloWorld", + g_variant_new ("(s)", "Hey"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_assert_cmpstr (g_variant_get_type_string (result), ==, "(s)"); + g_variant_get (result, "(s)", &str); + g_assert_cmpstr (str, ==, "You greeted me with 'Hey'. Thanks!"); + g_variant_unref (result); + + /* Check that we can completely recover the returned error */ + result = g_dbus_proxy_call_sync (proxy, + "HelloWorld", + g_variant_new ("(s)", "Yo"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR); + g_assert (g_dbus_error_is_remote_error (error)); + g_assert (g_dbus_error_is_remote_error (error)); + g_assert (result == NULL); + dbus_error_name = g_dbus_error_get_remote_error (error); + g_assert_cmpstr (dbus_error_name, ==, "com.example.TestException"); + g_free (dbus_error_name); + g_assert (g_dbus_error_strip_remote_error (error)); + g_assert_cmpstr (error->message, ==, "Yo is not a proper greeting"); + g_clear_error (&error); + + /* Check that we get a timeout if the method handling is taking longer than timeout */ + error = NULL; + result = g_dbus_proxy_call_sync (proxy, + "Sleep", + g_variant_new ("(i)", 500 /* msec */), + G_DBUS_CALL_FLAGS_NONE, + 100 /* msec */, + NULL, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT); + g_assert (!g_dbus_error_is_remote_error (error)); + g_assert (result == NULL); + g_clear_error (&error); + + /* Check that proxy-default timeouts work. */ + g_assert_cmpint (g_dbus_proxy_get_default_timeout (proxy), ==, -1); + + /* the default timeout is 25000 msec so this should work */ + result = g_dbus_proxy_call_sync (proxy, + "Sleep", + g_variant_new ("(i)", 500 /* msec */), + G_DBUS_CALL_FLAGS_NONE, + -1, /* use proxy default (e.g. -1 -> e.g. 25000 msec) */ + NULL, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_assert_cmpstr (g_variant_get_type_string (result), ==, "()"); + g_variant_unref (result); + + /* now set the proxy-default timeout to 250 msec and try the 500 msec call - this should FAIL */ + g_dbus_proxy_set_default_timeout (proxy, 250); + g_assert_cmpint (g_dbus_proxy_get_default_timeout (proxy), ==, 250); + result = g_dbus_proxy_call_sync (proxy, + "Sleep", + g_variant_new ("(i)", 500 /* msec */), + G_DBUS_CALL_FLAGS_NONE, + -1, /* use proxy default (e.g. 250 msec) */ + NULL, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT); + g_assert (!g_dbus_error_is_remote_error (error)); + g_assert (result == NULL); + g_clear_error (&error); + + /* clean up after ourselves */ + g_dbus_proxy_set_default_timeout (proxy, -1); +} + +/* ---------------------------------------------------------------------------------------------------- */ +/* Test that the property aspects of GDBusProxy works */ +/* ---------------------------------------------------------------------------------------------------- */ + +static void +test_properties (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + GDBusProxy *proxy) +{ + GError *error; + GVariant *variant; + GVariant *variant2; + GVariant *result; + + error = NULL; + + /* + * Check that we can read cached properties. + * + * No need to test all properties - GVariant has already been tested + */ + variant = g_dbus_proxy_get_cached_property (proxy, "y"); + g_assert (variant != NULL); + g_assert_cmpint (g_variant_get_byte (variant), ==, 1); + g_variant_unref (variant); + variant = g_dbus_proxy_get_cached_property (proxy, "o"); + g_assert (variant != NULL); + g_assert_cmpstr (g_variant_get_string (variant, NULL), ==, "/some/path"); + g_variant_unref (variant); + + /* + * Now ask the service to change a property and check that #GDBusProxy::g-property-changed + * is received. Also check that the cache is updated. + */ + variant2 = g_variant_new_byte (42); + result = g_dbus_proxy_call_sync (proxy, + "FrobSetProperty", + g_variant_new ("(sv)", + "y", + variant2), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_assert_cmpstr (g_variant_get_type_string (result), ==, "()"); + g_variant_unref (result); + _g_assert_signal_received (proxy, "g-properties-changed"); + variant = g_dbus_proxy_get_cached_property (proxy, "y"); + g_assert (variant != NULL); + g_assert_cmpint (g_variant_get_byte (variant), ==, 42); + g_variant_unref (variant); + + g_dbus_proxy_set_cached_property (proxy, "y", g_variant_new_byte (142)); + variant = g_dbus_proxy_get_cached_property (proxy, "y"); + g_assert (variant != NULL); + g_assert_cmpint (g_variant_get_byte (variant), ==, 142); + g_variant_unref (variant); + + g_dbus_proxy_set_cached_property (proxy, "y", NULL); + variant = g_dbus_proxy_get_cached_property (proxy, "y"); + g_assert (variant == NULL); +} + +/* ---------------------------------------------------------------------------------------------------- */ +/* Test that the signal aspects of GDBusProxy works */ +/* ---------------------------------------------------------------------------------------------------- */ + +static void +test_proxy_signals_on_signal (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GString *s = user_data; + + g_assert_cmpstr (signal_name, ==, "TestSignal"); + g_assert_cmpstr (g_variant_get_type_string (parameters), ==, "(sov)"); + + g_variant_print_string (parameters, s, TRUE); +} + +typedef struct +{ + GMainLoop *internal_loop; + GString *s; +} TestSignalData; + +static void +test_proxy_signals_on_emit_signal_cb (GDBusProxy *proxy, + GAsyncResult *res, + gpointer user_data) +{ + TestSignalData *data = user_data; + GError *error; + GVariant *result; + + error = NULL; + result = g_dbus_proxy_call_finish (proxy, + res, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_assert_cmpstr (g_variant_get_type_string (result), ==, "()"); + g_variant_unref (result); + + /* check that the signal was recieved before we got the method result */ + g_assert (strlen (data->s->str) > 0); + + /* break out of the loop */ + g_main_loop_quit (data->internal_loop); +} + +static void +test_signals (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + GDBusProxy *proxy) +{ + GError *error; + GString *s; + gulong signal_handler_id; + TestSignalData data; + GVariant *result; + + error = NULL; + + /* + * Ask the service to emit a signal and check that we receive it. + * + * Note that blocking calls don't block in the mainloop so wait for the signal (which + * is dispatched before the method reply) + */ + s = g_string_new (NULL); + signal_handler_id = g_signal_connect (proxy, + "g-signal", + G_CALLBACK (test_proxy_signals_on_signal), + s); + + result = g_dbus_proxy_call_sync (proxy, + "EmitSignal", + g_variant_new ("(so)", + "Accept the next proposition you hear", + "/some/path"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_assert_cmpstr (g_variant_get_type_string (result), ==, "()"); + g_variant_unref (result); + /* check that we haven't received the signal just yet */ + g_assert (strlen (s->str) == 0); + /* and now wait for the signal */ + _g_assert_signal_received (proxy, "g-signal"); + g_assert_cmpstr (s->str, + ==, + "('Accept the next proposition you hear .. in bed!', objectpath '/some/path/in/bed', <'a variant'>)"); + g_signal_handler_disconnect (proxy, signal_handler_id); + g_string_free (s, TRUE); + + /* + * Now do this async to check the signal is received before the method returns. + */ + s = g_string_new (NULL); + data.internal_loop = g_main_loop_new (NULL, FALSE); + data.s = s; + signal_handler_id = g_signal_connect (proxy, + "g-signal", + G_CALLBACK (test_proxy_signals_on_signal), + s); + g_dbus_proxy_call (proxy, + "EmitSignal", + g_variant_new ("(so)", + "You will make a great programmer", + "/some/other/path"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) test_proxy_signals_on_emit_signal_cb, + &data); + g_main_loop_run (data.internal_loop); + g_main_loop_unref (data.internal_loop); + g_assert_cmpstr (s->str, + ==, + "('You will make a great programmer .. in bed!', objectpath '/some/other/path/in/bed', <'a variant'>)"); + g_signal_handler_disconnect (proxy, signal_handler_id); + g_string_free (s, TRUE); +} + +static void +test_bogus_method_return (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + GDBusProxy *proxy) +{ + GError *error = NULL; + GVariant *result; + + result = g_dbus_proxy_call_sync (proxy, + "PairReturn", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert (result == NULL); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static const gchar *frob_dbus_interface_xml = + "" + " " + /* Deliberately different from gdbus-testserver.py's definition */ + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; +static GDBusInterfaceInfo *frob_dbus_interface_info; + +static void +on_proxy_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + GDBusProxy *proxy, + gpointer user_data) +{ + test_methods (connection, name, name_owner, proxy); + test_properties (connection, name, name_owner, proxy); + test_signals (connection, name, name_owner, proxy); + + /* This is obviously wrong but expected interface is not set so we don't fail... */ + g_dbus_proxy_set_cached_property (proxy, "y", g_variant_new_string ("error_me_out!")); + g_dbus_proxy_set_cached_property (proxy, "y", g_variant_new_byte (42)); + g_dbus_proxy_set_cached_property (proxy, "does-not-exist", g_variant_new_string ("something")); + g_dbus_proxy_set_cached_property (proxy, "does-not-exist", NULL); + + /* Now repeat the method tests, with an expected interface set */ + g_dbus_proxy_set_interface_info (proxy, frob_dbus_interface_info); + test_methods (connection, name, name_owner, proxy); + + /* And now one more test where we deliberately set the expected + * interface definition incorrectly + */ + test_bogus_method_return (connection, name, name_owner, proxy); + + /* Also check that we complain if setting a cached property of the wrong type */ + if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDOUT | G_TEST_TRAP_SILENCE_STDERR)) + { + g_dbus_proxy_set_cached_property (proxy, "y", g_variant_new_string ("error_me_out!")); + } + g_test_trap_assert_stderr ("*Trying to set property y of type s but according to the expected interface the type is y*"); + g_test_trap_assert_failed(); + + if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDOUT | G_TEST_TRAP_SILENCE_STDERR)) + { + g_dbus_proxy_set_cached_property (proxy, "does-not-exist", g_variant_new_string ("something")); + } + g_test_trap_assert_stderr ("*Trying to lookup property does-not-exist which isn't in expected interface com.example.Frob*"); + g_test_trap_assert_failed(); + + /* this should work, however (since the type is correct) */ + g_dbus_proxy_set_cached_property (proxy, "y", g_variant_new_byte (42)); + + g_main_loop_quit (loop); +} + +static void +on_proxy_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ +} + +static void +test_proxy (void) +{ + guint watcher_id; + + session_bus_up (); + + /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return + * until one can connect to the bus but that's not how things work right now + */ + usleep (500 * 1000); + + watcher_id = g_bus_watch_proxy (G_BUS_TYPE_SESSION, + "com.example.TestService", + G_BUS_NAME_WATCHER_FLAGS_NONE, + "/com/example/TestObject", + "com.example.Frob", + G_TYPE_DBUS_PROXY, + G_DBUS_PROXY_FLAGS_NONE, + on_proxy_appeared, + on_proxy_vanished, + NULL, + NULL); + + /* this is safe; testserver will exit once the bus goes away */ + g_assert (g_spawn_command_line_async ("./gdbus-testserver.py", NULL)); + + g_main_loop_run (loop); + + g_bus_unwatch_proxy (watcher_id); + + /* tear down bus */ + session_bus_down (); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +int +main (int argc, + char *argv[]) +{ + gint ret; + GDBusNodeInfo *introspection_data = NULL; + + g_type_init (); + g_test_init (&argc, &argv, NULL); + + introspection_data = g_dbus_node_info_new_for_xml (frob_dbus_interface_xml, NULL); + g_assert (introspection_data != NULL); + frob_dbus_interface_info = introspection_data->interfaces[0]; + + /* all the tests rely on a shared main loop */ + loop = g_main_loop_new (NULL, FALSE); + + /* all the tests use a session bus with a well-known address that we can bring up and down + * using session_bus_up() and session_bus_down(). + */ + g_unsetenv ("DISPLAY"); + g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE); + + g_test_add_func ("/gdbus/proxy", test_proxy); + + ret = g_test_run(); + + g_dbus_node_info_unref (introspection_data); + return ret; +} diff --git a/gio/tests/gdbus-serialization.c b/gio/tests/gdbus-serialization.c new file mode 100644 index 000000000..90e380fec --- /dev/null +++ b/gio/tests/gdbus-serialization.c @@ -0,0 +1,654 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include + +#include +#include +#include + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +hexdump (const guchar *str, gsize len) +{ + const guchar *data = (const guchar *) str; + guint n, m; + + for (n = 0; n < len; n += 16) + { + g_printerr ("%04x: ", n); + + for (m = n; m < n + 16; m++) + { + if (m > n && (m%4) == 0) + g_printerr (" "); + if (m < len) + g_printerr ("%02x ", data[m]); + else + g_printerr (" "); + } + + g_printerr (" "); + + for (m = n; m < len && m < n + 16; m++) + g_printerr ("%c", g_ascii_isprint (data[m]) ? data[m] : '.'); + + g_printerr ("\n"); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +append_gv_to_dbus_iter (DBusMessageIter *iter, + GVariant *value, + GError **error) +{ + const GVariantType *type; + + type = g_variant_get_type (value); + if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN)) + { + dbus_bool_t v = g_variant_get_boolean (value); + dbus_message_iter_append_basic (iter, DBUS_TYPE_BOOLEAN, &v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE)) + { + guint8 v = g_variant_get_byte (value); + dbus_message_iter_append_basic (iter, DBUS_TYPE_BYTE, &v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16)) + { + gint16 v = g_variant_get_int16 (value); + dbus_message_iter_append_basic (iter, DBUS_TYPE_INT16, &v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16)) + { + guint16 v = g_variant_get_uint16 (value); + dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT16, &v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32)) + { + gint32 v = g_variant_get_int32 (value); + dbus_message_iter_append_basic (iter, DBUS_TYPE_INT32, &v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32)) + { + guint32 v = g_variant_get_uint32 (value); + dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT32, &v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64)) + { + gint64 v = g_variant_get_int64 (value); + dbus_message_iter_append_basic (iter, DBUS_TYPE_INT64, &v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64)) + { + guint64 v = g_variant_get_uint64 (value); + dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT64, &v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE)) + { + gdouble v = g_variant_get_double (value); + dbus_message_iter_append_basic (iter, DBUS_TYPE_DOUBLE, &v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) + { + const gchar *v = g_variant_get_string (value, NULL); + dbus_message_iter_append_basic (iter, DBUS_TYPE_STRING, &v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH)) + { + const gchar *v = g_variant_get_string (value, NULL); + dbus_message_iter_append_basic (iter, DBUS_TYPE_OBJECT_PATH, &v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE)) + { + const gchar *v = g_variant_get_string (value, NULL); + dbus_message_iter_append_basic (iter, DBUS_TYPE_SIGNATURE, &v); + } + else if (g_variant_type_is_variant (type)) + { + DBusMessageIter sub; + GVariant *child; + + child = g_variant_get_child_value (value, 0); + dbus_message_iter_open_container (iter, DBUS_TYPE_VARIANT, + g_variant_get_type_string (child), + &sub); + if (!append_gv_to_dbus_iter (&sub, child, error)) + { + g_variant_unref (child); + goto fail; + } + dbus_message_iter_close_container (iter, &sub); + g_variant_unref (child); + } + else if (g_variant_type_is_array (type)) + { + DBusMessageIter dbus_iter; + const gchar *type_string; + GVariantIter gv_iter; + GVariant *item; + + type_string = g_variant_get_type_string (value); + type_string++; /* skip the 'a' */ + + dbus_message_iter_open_container (iter, DBUS_TYPE_ARRAY, + type_string, &dbus_iter); + g_variant_iter_init (&gv_iter, value); + + while ((item = g_variant_iter_next_value (&gv_iter))) + { + if (!append_gv_to_dbus_iter (&dbus_iter, item, error)) + { + goto fail; + } + } + + dbus_message_iter_close_container (iter, &dbus_iter); + } + else if (g_variant_type_is_tuple (type)) + { + DBusMessageIter dbus_iter; + GVariantIter gv_iter; + GVariant *item; + + dbus_message_iter_open_container (iter, DBUS_TYPE_STRUCT, + NULL, &dbus_iter); + g_variant_iter_init (&gv_iter, value); + + while ((item = g_variant_iter_next_value (&gv_iter))) + { + if (!append_gv_to_dbus_iter (&dbus_iter, item, error)) + goto fail; + } + + dbus_message_iter_close_container (iter, &dbus_iter); + } + else if (g_variant_type_is_dict_entry (type)) + { + DBusMessageIter dbus_iter; + GVariant *key, *val; + + dbus_message_iter_open_container (iter, DBUS_TYPE_DICT_ENTRY, + NULL, &dbus_iter); + key = g_variant_get_child_value (value, 0); + if (!append_gv_to_dbus_iter (&dbus_iter, key, error)) + { + g_variant_unref (key); + goto fail; + } + g_variant_unref (key); + + val = g_variant_get_child_value (value, 1); + if (!append_gv_to_dbus_iter (&dbus_iter, val, error)) + { + g_variant_unref (val); + goto fail; + } + g_variant_unref (val); + + dbus_message_iter_close_container (iter, &dbus_iter); + } + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Error serializing GVariant with type-string `%s' to a D-Bus message", + g_variant_get_type_string (value)); + goto fail; + } + + return TRUE; + + fail: + return FALSE; +} + +static gboolean +append_gv_to_dbus_message (DBusMessage *message, + GVariant *value, + GError **error) +{ + gboolean ret; + guint n; + + ret = FALSE; + + if (value != NULL) + { + DBusMessageIter iter; + GVariantIter gv_iter; + GVariant *item; + + dbus_message_iter_init_append (message, &iter); + + g_variant_iter_init (&gv_iter, value); + n = 0; + while ((item = g_variant_iter_next_value (&gv_iter))) + { + if (!append_gv_to_dbus_iter (&iter, item, error)) + { + g_prefix_error (error, + "Error encoding in-arg %d: ", + n); + goto out; + } + n++; + } + } + + ret = TRUE; + + out: + return ret; +} + +static void +print_gv_dbus_message (GVariant *value) +{ + DBusMessage *message; + char *blob; + int blob_len; + GError *error; + + message = dbus_message_new (DBUS_MESSAGE_TYPE_METHOD_CALL); + dbus_message_set_serial (message, 0x41); + dbus_message_set_path (message, "/foo/bar"); + dbus_message_set_member (message, "Member"); + + error = NULL; + if (!append_gv_to_dbus_message (message, value, &error)) + { + g_printerr ("Error printing GVariant as DBusMessage: %s", error->message); + g_error_free (error); + goto out; + } + + dbus_message_marshal (message, &blob, &blob_len); + g_printerr ("\n"); + hexdump ((guchar *) blob, blob_len); + out: + dbus_message_unref (message); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +dbus_1_message_append (GString *s, + guint indent, + DBusMessageIter *iter) +{ + gint arg_type; + DBusMessageIter sub; + + g_string_append_printf (s, "%*s", indent, ""); + + arg_type = dbus_message_iter_get_arg_type (iter); + switch (arg_type) + { + case DBUS_TYPE_BOOLEAN: + { + dbus_bool_t value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "bool: %s\n", value ? "true" : "false"); + break; + } + + case DBUS_TYPE_BYTE: + { + guchar value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "byte: 0x%02x\n", (guint) value); + break; + } + + case DBUS_TYPE_INT16: + { + gint16 value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "int16: %" G_GINT16_FORMAT "\n", value); + break; + } + + case DBUS_TYPE_UINT16: + { + guint16 value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "uint16: %" G_GUINT16_FORMAT "\n", value); + break; + } + + case DBUS_TYPE_INT32: + { + gint32 value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "int32: %" G_GINT32_FORMAT "\n", value); + break; + } + + case DBUS_TYPE_UINT32: + { + guint32 value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "uint32: %" G_GUINT32_FORMAT "\n", value); + break; + } + + case DBUS_TYPE_INT64: + { + gint64 value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "int64: %" G_GINT64_FORMAT "\n", value); + break; + } + + case DBUS_TYPE_UINT64: + { + guint64 value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "uint64: %" G_GUINT64_FORMAT "\n", value); + break; + } + + case DBUS_TYPE_DOUBLE: + { + gdouble value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "double: %f\n", value); + break; + } + + case DBUS_TYPE_STRING: + { + const gchar *value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "string: `%s'\n", value); + break; + } + + case DBUS_TYPE_OBJECT_PATH: + { + const gchar *value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "object_path: `%s'\n", value); + break; + } + + case DBUS_TYPE_SIGNATURE: + { + const gchar *value; + dbus_message_iter_get_basic (iter, &value); + g_string_append_printf (s, "signature: `%s'\n", value); + break; + } + + case DBUS_TYPE_VARIANT: + g_string_append_printf (s, "variant:\n"); + dbus_message_iter_recurse (iter, &sub); + while (dbus_message_iter_get_arg_type (&sub)) + { + dbus_1_message_append (s, indent + 2, &sub); + dbus_message_iter_next (&sub); + } + break; + + case DBUS_TYPE_ARRAY: + g_string_append_printf (s, "array:\n"); + dbus_message_iter_recurse (iter, &sub); + while (dbus_message_iter_get_arg_type (&sub)) + { + dbus_1_message_append (s, indent + 2, &sub); + dbus_message_iter_next (&sub); + } + break; + + case DBUS_TYPE_STRUCT: + g_string_append_printf (s, "struct:\n"); + dbus_message_iter_recurse (iter, &sub); + while (dbus_message_iter_get_arg_type (&sub)) + { + dbus_1_message_append (s, indent + 2, &sub); + dbus_message_iter_next (&sub); + } + break; + + case DBUS_TYPE_DICT_ENTRY: + g_string_append_printf (s, "dict_entry:\n"); + dbus_message_iter_recurse (iter, &sub); + while (dbus_message_iter_get_arg_type (&sub)) + { + dbus_1_message_append (s, indent + 2, &sub); + dbus_message_iter_next (&sub); + } + break; + + default: + g_printerr ("Error serializing D-Bus message to GVariant. Unsupported arg type `%c' (%d)", + arg_type, + arg_type); + g_assert_not_reached (); + break; + } +} + +static gchar * +dbus_1_message_print (DBusMessage *message) +{ + GString *s; + guint n; + DBusMessageIter iter; + + s = g_string_new (NULL); + n = 0; + dbus_message_iter_init (message, &iter); + while (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_INVALID) + { + g_string_append_printf (s, "value %d: ", n); + dbus_1_message_append (s, 2, &iter); + dbus_message_iter_next (&iter); + n++; + } + + return g_string_free (s, FALSE); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +get_body_signature (GVariant *value) +{ + const gchar *s; + gsize len; + gchar *ret; + + s = g_variant_get_type_string (value); + len = strlen (s); + g_assert (len>=2); + + ret = g_strndup (s + 1, len - 2); + + return ret; +} + +static void +check_serialization (GVariant *value, + const gchar *expected_dbus_1_output) +{ + guchar *blob; + gsize blob_size; + DBusMessage *dbus_1_message; + GDBusMessage *message; + GDBusMessage *recovered_message; + GError *error; + DBusError dbus_error; + gchar *s; + gchar *s1; + + message = g_dbus_message_new (); + g_dbus_message_set_body (message, value); + g_dbus_message_set_message_type (message, G_DBUS_MESSAGE_TYPE_METHOD_CALL); + g_dbus_message_set_serial (message, 0x41); + s = get_body_signature (value); + g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, g_variant_new_object_path ("/foo/bar")); + g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, g_variant_new_string ("Member")); + g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE, g_variant_new_signature (s)); + g_free (s); + + /* First check that the serialization to the D-Bus wire format is correct */ + + error = NULL; + blob = g_dbus_message_to_blob (message, + &blob_size, + G_DBUS_CAPABILITY_FLAGS_NONE, + &error); + g_assert_no_error (error); + g_assert (blob != NULL); + + dbus_error_init (&dbus_error); + dbus_1_message = dbus_message_demarshal ((char *) blob, blob_size, &dbus_error); + if (dbus_error_is_set (&dbus_error)) + { + g_printerr ("Error calling dbus_message_demarshal() on this blob: %s: %s\n", + dbus_error.name, + dbus_error.message); + hexdump (blob, blob_size); + dbus_error_free (&dbus_error); + + s = g_variant_print (value, TRUE); + g_printerr ("\nThe blob was generated from the following GVariant value:\n%s\n\n", s); + g_free (s); + + g_printerr ("If the blob was encoded using DBusMessageIter, the payload would have been:\n"); + print_gv_dbus_message (value); + + g_assert_not_reached (); + } + + s = dbus_1_message_print (dbus_1_message); + dbus_message_unref (dbus_1_message); + + g_assert_cmpstr (s, ==, expected_dbus_1_output); + g_free (s); + + /* Then serialize back and check that the body is identical */ + + error = NULL; + recovered_message = g_dbus_message_new_from_blob (blob, + blob_size, + G_DBUS_CAPABILITY_FLAGS_NONE, + &error); + g_assert_no_error (error); + g_assert (recovered_message != NULL); + g_assert (g_dbus_message_get_body (recovered_message) != NULL); + + if (!g_variant_equal (g_dbus_message_get_body (recovered_message), value)) + { + s = g_variant_print (g_dbus_message_get_body (recovered_message), TRUE); + s1 = g_variant_print (value, TRUE); + g_printerr ("Recovered value:\n%s\ndoes not match given value\n%s\n", + s, + s1); + g_free (s); + g_free (s1); + g_assert_not_reached (); + } + g_object_unref (message); + g_object_unref (recovered_message); +} + +static void +message_serialize_basic (void) +{ + check_serialization (g_variant_new ("(sogybnqiuxtd)", + "this is a string", + "/this/is/a/path", + "sad", + 42, + TRUE, + -42, + 60000, + -44, + 100000, + -G_GINT64_CONSTANT(2)<<34, + G_GUINT64_CONSTANT(0xffffffffffffffff), + 42.5), + "value 0: string: `this is a string'\n" + "value 1: object_path: `/this/is/a/path'\n" + "value 2: signature: `sad'\n" + "value 3: byte: 0x2a\n" + "value 4: bool: true\n" + "value 5: int16: -42\n" + "value 6: uint16: 60000\n" + "value 7: int32: -44\n" + "value 8: uint32: 100000\n" + "value 9: int64: -34359738368\n" + "value 10: uint64: 18446744073709551615\n" + "value 11: double: 42.500000\n"); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +message_serialize_complex (void) +{ + GError *error; + GVariant *value; + + error = NULL; + value = g_variant_parse (G_VARIANT_TYPE ("(aia{ss})"), + "([1, 2, 3], {'one': 'white', 'two': 'black'})", + NULL, NULL, &error); + g_assert_no_error (error); + g_assert (value != NULL); + check_serialization (value, + "value 0: array:\n" + " int32: 1\n" + " int32: 2\n" + " int32: 3\n" + "value 1: array:\n" + " dict_entry:\n" + " string: `one'\n" + " string: `white'\n" + " dict_entry:\n" + " string: `two'\n" + " string: `black'\n"); +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +int +main (int argc, + char *argv[]) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/gdbus/message-serialize-basic", message_serialize_basic); + g_test_add_func ("/gdbus/message-serialize-complex", message_serialize_complex); + return g_test_run(); +} + diff --git a/gio/tests/gdbus-sessionbus.c b/gio/tests/gdbus-sessionbus.c new file mode 100644 index 000000000..6e70e02f3 --- /dev/null +++ b/gio/tests/gdbus-sessionbus.c @@ -0,0 +1,342 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "gdbus-sessionbus.h" + +/* ---------------------------------------------------------------------------------------------------- */ +/* Utilities for bringing up and tearing down session message bus instances */ + +static void +watch_parent (gint fd) +{ + GPollFD fds[1]; + gint num_events; + gchar buf[512]; + guint bytes_read; + GArray *buses_to_kill_array; + + fds[0].fd = fd; + fds[0].events = G_IO_HUP | G_IO_IN; + fds[0].revents = 0; + + buses_to_kill_array = g_array_new (FALSE, TRUE, sizeof (guint)); + + do + { + guint pid; + guint n; + + num_events = g_poll (fds, 1, -1); + if (num_events == 0) + continue; + + if (fds[0].revents == G_IO_HUP) + { + for (n = 0; n < buses_to_kill_array->len; n++) + { + pid = g_array_index (buses_to_kill_array, guint, n); + g_print ("cleaning up bus with pid %d\n", pid); + kill (pid, SIGTERM); + } + g_array_free (buses_to_kill_array, TRUE); + exit (0); + } + + //g_debug ("data from parent"); + + memset (buf, '\0', sizeof buf); + again: + bytes_read = read (fds[0].fd, buf, sizeof buf); + if (bytes_read < 0 && (errno == EAGAIN || errno == EINTR)) + goto again; + + if (sscanf (buf, "add %d\n", &pid) == 1) + { + g_array_append_val (buses_to_kill_array, pid); + } + else if (sscanf (buf, "remove %d\n", &pid) == 1) + { + for (n = 0; n < buses_to_kill_array->len; n++) + { + if (g_array_index (buses_to_kill_array, guint, n) == pid) + { + g_array_remove_index (buses_to_kill_array, n); + pid = 0; + break; + } + } + if (pid != 0) + { + g_warning ("unknown pid %d to remove", pid); + } + } + else + { + g_warning ("unknown command from parent '%s'", buf); + } + } + while (TRUE); + +} + +static GHashTable *session_bus_address_to_pid = NULL; +static gint pipe_fds[2]; + +const gchar * +session_bus_up_with_address (const gchar *given_address) +{ + gchar *address; + int stdout_fd; + GError *error; + gchar *argv[] = {"dbus-daemon", "--print-address", "--config-file=foo", NULL}; + GPid pid; + gchar buf[512]; + ssize_t bytes_read; + gchar *config_file_name; + gint config_file_fd; + GString *config_file_contents; + + address = NULL; + error = NULL; + config_file_name = NULL; + config_file_fd = -1; + argv[2] = NULL; + + config_file_fd = g_file_open_tmp ("g-dbus-tests-XXXXXX", + &config_file_name, + &error); + if (config_file_fd < 0) + { + g_warning ("Error creating temporary config file: %s", error->message); + g_error_free (error); + goto out; + } + + config_file_contents = g_string_new (NULL); + g_string_append (config_file_contents, "\n"); + g_string_append (config_file_contents, " session\n"); + g_string_append_printf (config_file_contents, " %s\n", given_address); + g_string_append (config_file_contents, + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"); + g_string_append (config_file_contents, "\n"); + + if (write (config_file_fd, config_file_contents->str, config_file_contents->len) != (gssize) config_file_contents->len) + { + g_warning ("Error writing %d bytes to config file: %m", (gint) config_file_contents->len); + g_string_free (config_file_contents, TRUE); + goto out; + } + g_string_free (config_file_contents, TRUE); + + argv[2] = g_strdup_printf ("--config-file=%s", config_file_name); + + if (session_bus_address_to_pid == NULL) + { + /* keep a mapping from session bus address to the pid */ + session_bus_address_to_pid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + /* fork a child to clean up session buses when we are killed */ + if (pipe (pipe_fds) != 0) + { + g_warning ("pipe() failed: %m"); + g_assert_not_reached (); + } + switch (fork ()) + { + case -1: + g_warning ("fork() failed: %m"); + g_assert_not_reached (); + break; + + case 0: + /* child */ + close (pipe_fds[1]); + watch_parent (pipe_fds[0]); + break; + + default: + /* parent */ + close (pipe_fds[0]); + break; + } + + //atexit (cleanup_session_buses); + /* TODO: need to handle the cases where we crash */ + } + else + { + /* check if we already have a bus running for this address */ + if (g_hash_table_lookup (session_bus_address_to_pid, given_address) != NULL) + { + g_warning ("Already have a bus instance for the given address %s", given_address); + goto out; + } + } + + if (!g_spawn_async_with_pipes (NULL, + argv, + NULL, + G_SPAWN_SEARCH_PATH, + NULL, + NULL, + &pid, + NULL, + &stdout_fd, + NULL, + &error)) + { + g_warning ("Error spawning dbus-daemon: %s", error->message); + g_error_free (error); + goto out; + } + + memset (buf, '\0', sizeof buf); + again: + bytes_read = read (stdout_fd, buf, sizeof buf); + if (bytes_read < 0 && (errno == EAGAIN || errno == EINTR)) + goto again; + close (stdout_fd); + + if (bytes_read == 0 || bytes_read == sizeof buf) + { + g_warning ("Error reading address from dbus daemon, %d bytes read", (gint) bytes_read); + kill (SIGTERM, pid); + goto out; + } + + address = g_strdup (buf); + g_strstrip (address); + + /* write the pid to the child so it can kill it when we die */ + g_snprintf (buf, sizeof buf, "add %d\n", (guint) pid); + write (pipe_fds[1], buf, strlen (buf)); + + /* start dbus-monitor */ + if (g_getenv ("G_DBUS_MONITOR") != NULL) + { + g_spawn_command_line_async ("dbus-monitor --session", NULL); + usleep (500 * 1000); + } + + g_hash_table_insert (session_bus_address_to_pid, address, GUINT_TO_POINTER (pid)); + + out: + if (config_file_fd > 0) + { + if (close (config_file_fd) != 0) + { + g_warning ("Error closing fd for config file %s: %m", config_file_name); + } + g_assert (config_file_name != NULL); + if (unlink (config_file_name) != 0) + { + g_warning ("Error unlinking config file %s: %m", config_file_name); + } + } + g_free (argv[2]); + g_free (config_file_name); + return address; +} + +void +session_bus_down_with_address (const gchar *address) +{ + gpointer value; + GPid pid; + gchar buf[512]; + + g_assert (address != NULL); + g_assert (session_bus_address_to_pid != NULL); + + value = g_hash_table_lookup (session_bus_address_to_pid, address); + g_assert (value != NULL); + + pid = GPOINTER_TO_UINT (g_hash_table_lookup (session_bus_address_to_pid, address)); + + kill (pid, SIGTERM); + + /* write the pid to the child so it won't kill it when we die */ + g_snprintf (buf, sizeof buf, "remove %d\n", (guint) pid); + write (pipe_fds[1], buf, strlen (buf)); + + g_hash_table_remove (session_bus_address_to_pid, address); +} + +static gchar *temporary_address = NULL; +static gchar *temporary_address_used_by_bus = NULL; + +const gchar * +session_bus_get_temporary_address (void) +{ + if (temporary_address == NULL) + { + /* TODO: maybe use a more random name etc etc */ + temporary_address = g_strdup_printf ("unix:path=/tmp/g-dbus-tests-pid-%d", getpid ()); + } + + return temporary_address; +} + +const gchar * +session_bus_up (void) +{ + if (temporary_address_used_by_bus != NULL) + { + g_warning ("There is already a session bus up"); + goto out; + } + + temporary_address_used_by_bus = g_strdup (session_bus_up_with_address (session_bus_get_temporary_address ())); + + out: + return temporary_address_used_by_bus; +} + +void +session_bus_down (void) +{ + if (temporary_address_used_by_bus == NULL) + { + g_warning ("There is not a session bus up"); + } + else + { + session_bus_down_with_address (temporary_address_used_by_bus); + g_free (temporary_address_used_by_bus); + temporary_address_used_by_bus = NULL; + } +} diff --git a/gio/tests/gdbus-sessionbus.h b/gio/tests/gdbus-sessionbus.h new file mode 100644 index 000000000..e107e0664 --- /dev/null +++ b/gio/tests/gdbus-sessionbus.h @@ -0,0 +1,38 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2009 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __SESSION_BUS_H__ +#define __SESSION_BUS_H__ + +#include + +G_BEGIN_DECLS + +const gchar *session_bus_up_with_address (const gchar *given_address); +void session_bus_down_with_address (const gchar *address); +const gchar *session_bus_get_temporary_address (void); +const gchar *session_bus_up (void); +void session_bus_down (void); + +G_END_DECLS + +#endif /* __SESSION_BUS_H__ */ diff --git a/gio/tests/gdbus-tests.c b/gio/tests/gdbus-tests.c new file mode 100644 index 000000000..347ff3664 --- /dev/null +++ b/gio/tests/gdbus-tests.c @@ -0,0 +1,219 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include +#include + +#include "gdbus-tests.h" + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GMainLoop *loop; + gboolean timed_out; +} PropertyNotifyData; + +static void +on_property_notify (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + PropertyNotifyData *data = user_data; + g_main_loop_quit (data->loop); +} + +static gboolean +on_property_notify_timeout (gpointer user_data) +{ + PropertyNotifyData *data = user_data; + data->timed_out = TRUE; + g_main_loop_quit (data->loop); + return TRUE; +} + +gboolean +_g_assert_property_notify_run (gpointer object, + const gchar *property_name) +{ + gchar *s; + gulong handler_id; + guint timeout_id; + PropertyNotifyData data; + + data.loop = g_main_loop_new (NULL, FALSE); + data.timed_out = FALSE; + s = g_strdup_printf ("notify::%s", property_name); + handler_id = g_signal_connect (object, + s, + G_CALLBACK (on_property_notify), + &data); + g_free (s); + timeout_id = g_timeout_add (5 * 1000, + on_property_notify_timeout, + &data); + g_main_loop_run (data.loop); + g_signal_handler_disconnect (object, handler_id); + g_source_remove (timeout_id); + g_main_loop_unref (data.loop); + + return data.timed_out; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GMainLoop *loop; + gboolean timed_out; +} SignalReceivedData; + +static void +on_signal_received (gpointer user_data) +{ + SignalReceivedData *data = user_data; + g_main_loop_quit (data->loop); +} + +static gboolean +on_signal_received_timeout (gpointer user_data) +{ + SignalReceivedData *data = user_data; + data->timed_out = TRUE; + g_main_loop_quit (data->loop); + return TRUE; +} + +gboolean +_g_assert_signal_received_run (gpointer object, + const gchar *signal_name) +{ + gulong handler_id; + guint timeout_id; + SignalReceivedData data; + + data.loop = g_main_loop_new (NULL, FALSE); + data.timed_out = FALSE; + handler_id = g_signal_connect_swapped (object, + signal_name, + G_CALLBACK (on_signal_received), + &data); + timeout_id = g_timeout_add (5 * 1000, + on_signal_received_timeout, + &data); + g_main_loop_run (data.loop); + g_signal_handler_disconnect (object, handler_id); + g_source_remove (timeout_id); + g_main_loop_unref (data.loop); + + return data.timed_out; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +GDBusConnection * +_g_bus_get_priv (GBusType bus_type, + GCancellable *cancellable, + GError **error) +{ + gchar *address; + GDBusConnection *ret; + + ret = NULL; + + address = g_dbus_address_get_for_bus_sync (bus_type, cancellable, error); + if (address == NULL) + goto out; + + ret = g_dbus_connection_new_for_address_sync (address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + NULL, /* GDBusAuthObserver */ + cancellable, + error); + g_free (address); + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GMainLoop *loop; + gboolean timed_out; +} WaitSingleRefData; + +static gboolean +on_wait_single_ref_timeout (gpointer user_data) +{ + WaitSingleRefData *data = user_data; + data->timed_out = TRUE; + g_main_loop_quit (data->loop); + return TRUE; +} + +static void +on_wait_for_single_ref_toggled (gpointer user_data, + GObject *object, + gboolean is_last_ref) +{ + WaitSingleRefData *data = user_data; + g_main_loop_quit (data->loop); +} + +gboolean +_g_object_wait_for_single_ref_do (gpointer object) +{ + WaitSingleRefData data; + guint timeout_id; + + data.timed_out = FALSE; + + if (G_OBJECT (object)->ref_count == 1) + goto out; + + data.loop = g_main_loop_new (NULL, FALSE); + timeout_id = g_timeout_add (5 * 1000, + on_wait_single_ref_timeout, + &data); + + g_object_add_toggle_ref (G_OBJECT (object), + on_wait_for_single_ref_toggled, + &data); + g_object_unref (object); + + g_main_loop_run (data.loop); + + g_object_ref (object); + g_object_remove_toggle_ref (object, + on_wait_for_single_ref_toggled, + &data); + + g_source_remove (timeout_id); + g_main_loop_unref (data.loop); + out: + return data.timed_out; +} + +/* ---------------------------------------------------------------------------------------------------- */ diff --git a/gio/tests/gdbus-tests.h b/gio/tests/gdbus-tests.h new file mode 100644 index 000000000..ffcc57cfb --- /dev/null +++ b/gio/tests/gdbus-tests.h @@ -0,0 +1,146 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2009 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifndef __TESTS_H__ +#define __TESTS_H__ + +#include +#include "gdbus-sessionbus.h" + +G_BEGIN_DECLS + +/* TODO: clean up and move to gtestutils.c + * + * This is needed because libdbus-1 does not give predictable error messages - e.g. you + * get a different error message on connecting to a bus if the socket file is there vs + * if the socket file is missing. + */ + +#define _g_assert_error_domain(err, dom) do { if (!err || (err)->domain != dom) \ + g_assertion_message_error (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #err, err, dom, -1); } while (0) + +#define _g_assert_property_notify(object, property_name) \ + do \ + { \ + if (!G_IS_OBJECT (object)) \ + { \ + g_assertion_message (G_LOG_DOMAIN, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + "Not a GObject instance"); \ + } \ + if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), \ + property_name) == NULL) \ + { \ + g_assertion_message (G_LOG_DOMAIN, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + "Property " property_name " does not " \ + "exist on object"); \ + } \ + if (_g_assert_property_notify_run (object, property_name)) \ + { \ + g_assertion_message (G_LOG_DOMAIN, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + "Timed out waiting for notification " \ + "on property " property_name); \ + } \ + } \ + while (FALSE) + +#define _g_assert_signal_received(object, signal_name) \ + do \ + { \ + if (!G_IS_OBJECT (object)) \ + { \ + g_assertion_message (G_LOG_DOMAIN, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + "Not a GObject instance"); \ + } \ + if (g_signal_lookup (signal_name, \ + G_TYPE_FROM_INSTANCE (object)) == 0) \ + { \ + g_assertion_message (G_LOG_DOMAIN, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + "Signal `" signal_name "' does not " \ + "exist on object"); \ + } \ + if (_g_assert_signal_received_run (object, signal_name)) \ + { \ + g_assertion_message (G_LOG_DOMAIN, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + "Timed out waiting for signal `" \ + signal_name "'"); \ + } \ + } \ + while (FALSE) + +gboolean _g_assert_property_notify_run (gpointer object, + const gchar *property_name); + + +gboolean _g_assert_signal_received_run (gpointer object, + const gchar *signal_name); + +GDBusConnection *_g_bus_get_priv (GBusType bus_type, + GCancellable *cancellable, + GError **error); + + +#define _g_object_wait_for_single_ref(object) \ + do \ + { \ + if (!G_IS_OBJECT (object)) \ + { \ + g_assertion_message (G_LOG_DOMAIN, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + "Not a GObject instance"); \ + } \ + if (_g_object_wait_for_single_ref_do (object)) \ + { \ + g_assertion_message (G_LOG_DOMAIN, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + "Timed out waiting for single ref"); \ + } \ + } \ + while (FALSE) + +gboolean _g_object_wait_for_single_ref_do (gpointer object); + +G_END_DECLS + +#endif /* __TESTS_H__ */ diff --git a/gio/tests/gdbus-testserver.py b/gio/tests/gdbus-testserver.py new file mode 100755 index 000000000..d31a49343 --- /dev/null +++ b/gio/tests/gdbus-testserver.py @@ -0,0 +1,271 @@ +#!/usr/bin/env python + +import gobject +import time + +import dbus +import dbus.service +import dbus.mainloop.glib + +class TestException(dbus.DBusException): + _dbus_error_name = 'com.example.TestException' + + +class TestService(dbus.service.Object): + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", + in_signature='s', out_signature='s') + def HelloWorld(self, hello_message): + if str(hello_message) == 'Yo': + raise TestException('Yo is not a proper greeting') + else: + return "You greeted me with '%s'. Thanks!"%(str(hello_message)) + + @dbus.service.method("com.example.Frob", + in_signature='ss', out_signature='ss') + def DoubleHelloWorld(self, hello1, hello2): + return ("You greeted me with '%s'. Thanks!"%(str(hello1)), "Yo dawg, you uttered '%s'. Thanks!"%(str(hello2))) + + @dbus.service.method("com.example.Frob", + in_signature='', out_signature='su') + def PairReturn(self): + return ("foo", 42) + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", + in_signature='ybnqiuxtdsog', out_signature='ybnqiuxtdsog') + def TestPrimitiveTypes(self, val_byte, val_boolean, val_int16, val_uint16, val_int32, val_uint32, val_int64, val_uint64, val_double, val_string, val_objpath, val_signature): + return val_byte + 1, not val_boolean, val_int16 + 1, val_uint16 + 1, val_int32 + 1, val_uint32 + 1, val_int64 + 1, val_uint64 + 1, -val_double + 0.123, val_string * 2, val_objpath + "/modified", val_signature * 2 + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", + in_signature='ayabanaqaiauaxatad', out_signature='ayabanaqaiauaxatad') + def TestArrayOfPrimitiveTypes(self, val_byte, val_boolean, val_int16, val_uint16, val_int32, val_uint32, val_int64, val_uint64, val_double): + return val_byte*2, val_boolean*2, val_int16*2, val_uint16*2, val_int32*2, val_uint32*2, val_int64*2, val_uint64*2, val_double*2 + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", + in_signature='asaoag', out_signature='asaoag') + def TestArrayOfStringTypes(self, val_string, val_objpath, val_signature): + return val_string * 2, val_objpath * 2, val_signature * 2 + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", + in_signature = 'a{yy}a{bb}a{nn}a{qq}a{ii}a{uu}a{xx}a{tt}a{dd}a{ss}a{oo}a{gg}', + out_signature = 'a{yy}a{bb}a{nn}a{qq}a{ii}a{uu}a{xx}a{tt}a{dd}a{ss}a{oo}a{gg}') + def TestHashTables(self, hyy, hbb, hnn, hqq, hii, huu, hxx, htt, hdd, hss, hoo, hgg): + + ret_hyy = {} + for i in hyy: + ret_hyy[i*2] = (hyy[i]*3) & 255 + + ret_hbb = {} + for i in hbb: + ret_hbb[i] = True + + ret_hnn = {} + for i in hnn: + ret_hnn[i*2] = hnn[i]*3 + + ret_hqq = {} + for i in hqq: + ret_hqq[i*2] = hqq[i]*3 + + ret_hii = {} + for i in hii: + ret_hii[i*2] = hii[i]*3 + + ret_huu = {} + for i in huu: + ret_huu[i*2] = huu[i]*3 + + ret_hxx = {} + for i in hxx: + ret_hxx[i + 2] = hxx[i] + 1 + + ret_htt = {} + for i in htt: + ret_htt[i + 2] = htt[i] + 1 + + ret_hdd = {} + for i in hdd: + ret_hdd[i + 2.5] = hdd[i] + 5.0 + + ret_hss = {} + for i in hss: + ret_hss[i + "mod"] = hss[i]*2 + + ret_hoo = {} + for i in hoo: + ret_hoo[i + "/mod"] = hoo[i] + "/mod2" + + ret_hgg = {} + for i in hgg: + ret_hgg[i + "assgit"] = hgg[i]*2 + + return ret_hyy, ret_hbb, ret_hnn, ret_hqq, ret_hii, ret_huu, ret_hxx, ret_htt, ret_hdd, ret_hss, ret_hoo, ret_hgg + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", + in_signature='(ii)(s(ii)aya{ss})', out_signature='(ii)(s(ii)aya{ss})') + def TestStructureTypes(self, s1, s2): + (x, y) = s1; + (desc, (x1, y1), ay, hss) = s2; + ret_hss = {} + for i in hss: + ret_hss[i] = hss[i] + " ... in bed!" + return (x + 1, y + 1), (desc + " ... in bed!", (x1 + 2, y1 + 2), ay * 2, ret_hss) + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", + in_signature='vb', out_signature='v') + def TestVariant(self, v, modify): + + if modify: + if type(v)==dbus.Boolean: + ret = False + elif type(v)==dbus.Dictionary: + ret = {} + for i in v: + ret[i] = v[i] * 2 + elif type(v)==dbus.Struct: + ret = ["other struct", dbus.Int16(100)] + else: + ret = v * 2 + else: + ret = v + return (type(v))(ret) + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", + in_signature='a(ii)aa(ii)aasaa{ss}aayavaav', out_signature='a(ii)aa(ii)aasaa{ss}aayavaav') + def TestComplexArrays(self, aii, aaii, aas, ahashes, aay, av, aav): + return aii * 2, aaii * 2, aas * 2, ahashes * 2, aay * 2, av *2, aav * 2 + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", + in_signature='a{s(ii)}a{sv}a{sav}a{saav}a{sa(ii)}a{sa{ss}}', + out_signature='a{s(ii)}a{sv}a{sav}a{saav}a{sa(ii)}a{sa{ss}}') + def TestComplexHashTables(self, h_str_to_pair, h_str_to_variant, h_str_to_av, h_str_to_aav, + h_str_to_array_of_pairs, hash_of_hashes): + + ret_h_str_to_pair = {} + for i in h_str_to_pair: + ret_h_str_to_pair[i + "_baz"] = h_str_to_pair[i] + + ret_h_str_to_variant = {} + for i in h_str_to_variant: + ret_h_str_to_variant[i + "_baz"] = h_str_to_variant[i] + + return ret_h_str_to_pair, ret_h_str_to_variant, h_str_to_av, h_str_to_aav, h_str_to_array_of_pairs, hash_of_hashes + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", + in_signature='', out_signature='') + def Quit(self): + mainloop.quit() + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", + in_signature='sv', out_signature='') + def FrobSetProperty(self, prop_name, prop_value): + self.frob_props[prop_name] = prop_value + message = dbus.lowlevel.SignalMessage("/com/example/TestObject", + "org.freedesktop.DBus.Properties", + "PropertiesChanged") + message.append("com.example.Frob") + message.append({prop_name : prop_value}) + message.append([], signature="as") + session_bus.send_message(message) + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.signal("com.example.Frob", + signature="sov") + def TestSignal(self, str1, objpath1, variant1): + pass + + @dbus.service.method("com.example.Frob", + in_signature='so', out_signature='') + def EmitSignal(self, str1, objpath1): + self.TestSignal (str1 + " .. in bed!", objpath1 + "/in/bed", "a variant") + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("com.example.Frob", in_signature='i', out_signature='', + async_callbacks=('return_cb', 'raise_cb')) + def Sleep(self, msec, return_cb, raise_cb): + def return_from_async_wait(): + return_cb() + return False + gobject.timeout_add(msec, return_from_async_wait) + + # ---------------------------------------------------------------------------------------------------- + + @dbus.service.method("org.freedesktop.DBus.Properties", + in_signature = 'ss', + out_signature = 'v') + def Get(self, interface_name, property_name): + + if interface_name == "com.example.Frob": + return self.frob_props[property_name] + else: + raise TestException("No such interface " + interface_name) + + @dbus.service.method("org.freedesktop.DBus.Properties", + in_signature = 's', + out_signature = 'a{sv}') + def GetAll(self, interface_name): + if interface_name == "com.example.Frob": + return self.frob_props + else: + raise TestException("No such interface " + interface_name) + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + session_bus = dbus.SessionBus() + name = dbus.service.BusName("com.example.TestService", session_bus) + + obj = TestService(session_bus, '/com/example/TestObject') + + #print "Our unique name is %s"%(session_bus.get_unique_name()) + + obj.frob_props = {} + obj.frob_props["y"] = dbus.Byte(1) + obj.frob_props["b"] = dbus.Boolean(True) + obj.frob_props["n"] = dbus.Int16(2) + obj.frob_props["q"] = dbus.UInt16(3) + obj.frob_props["i"] = dbus.Int32(4) + obj.frob_props["u"] = dbus.UInt32(5) + obj.frob_props["x"] = dbus.Int64(6) + obj.frob_props["t"] = dbus.UInt64(7) + obj.frob_props["d"] = dbus.Double(7.5) + obj.frob_props["s"] = dbus.String("a string") + obj.frob_props["o"] = dbus.ObjectPath("/some/path") + obj.frob_props["ay"] = [dbus.Byte(1), dbus.Byte(11)] + obj.frob_props["ab"] = [dbus.Boolean(True), dbus.Boolean(False)] + obj.frob_props["an"] = [dbus.Int16(2), dbus.Int16(12)] + obj.frob_props["aq"] = [dbus.UInt16(3), dbus.UInt16(13)] + obj.frob_props["ai"] = [dbus.Int32(4), dbus.Int32(14)] + obj.frob_props["au"] = [dbus.UInt32(5), dbus.UInt32(15)] + obj.frob_props["ax"] = [dbus.Int64(6), dbus.Int64(16)] + obj.frob_props["at"] = [dbus.UInt64(7), dbus.UInt64(17)] + obj.frob_props["ad"] = [dbus.Double(7.5), dbus.Double(17.5)] + obj.frob_props["as"] = [dbus.String("a string"), dbus.String("another string")] + obj.frob_props["ao"] = [dbus.ObjectPath("/some/path"), dbus.ObjectPath("/another/path")] + obj.frob_props["foo"] = "a frobbed string" + + mainloop = gobject.MainLoop() + mainloop.run() diff --git a/gio/tests/gdbus-threading.c b/gio/tests/gdbus-threading.c new file mode 100644 index 000000000..1de395c3b --- /dev/null +++ b/gio/tests/gdbus-threading.c @@ -0,0 +1,532 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#include +#include +#include + +#include "gdbus-tests.h" + +/* all tests rely on a global connection */ +static GDBusConnection *c = NULL; + +/* all tests rely on a shared mainloop */ +static GMainLoop *loop = NULL; + +/* ---------------------------------------------------------------------------------------------------- */ +/* Ensure that signal and method replies are delivered in the right thread */ +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct { + GThread *thread; + GMainLoop *thread_loop; + guint signal_count; +} DeliveryData; + +static void +msg_cb_expect_success (GDBusConnection *connection, + GAsyncResult *res, + gpointer user_data) +{ + DeliveryData *data = user_data; + GError *error; + GVariant *result; + + error = NULL; + result = g_dbus_connection_call_finish (connection, + res, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_variant_unref (result); + + g_assert (g_thread_self () == data->thread); + + g_main_loop_quit (data->thread_loop); +} + +static void +msg_cb_expect_error_cancelled (GDBusConnection *connection, + GAsyncResult *res, + gpointer user_data) +{ + DeliveryData *data = user_data; + GError *error; + GVariant *result; + + error = NULL; + result = g_dbus_connection_call_finish (connection, + res, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_assert (!g_dbus_error_is_remote_error (error)); + g_error_free (error); + g_assert (result == NULL); + + g_assert (g_thread_self () == data->thread); + + g_main_loop_quit (data->thread_loop); +} + +static void +signal_handler (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + DeliveryData *data = user_data; + + g_assert (g_thread_self () == data->thread); + + data->signal_count++; + + g_main_loop_quit (data->thread_loop); +} + +static gpointer +test_delivery_in_thread_func (gpointer _data) +{ + GMainLoop *thread_loop; + GMainContext *thread_context; + DeliveryData data; + GCancellable *ca; + guint subscription_id; + GDBusConnection *priv_c; + GError *error; + + error = NULL; + + thread_context = g_main_context_new (); + thread_loop = g_main_loop_new (thread_context, FALSE); + g_main_context_push_thread_default (thread_context); + + data.thread = g_thread_self (); + data.thread_loop = thread_loop; + data.signal_count = 0; + + /* ---------------------------------------------------------------------------------------------------- */ + + /* + * Check that we get a reply to the GetId() method call. + */ + g_dbus_connection_call (c, + "org.freedesktop.DBus", /* bus_name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "GetId", /* method name */ + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) msg_cb_expect_success, + &data); + g_main_loop_run (thread_loop); + + /* + * Check that we never actually send a message if the GCancellable + * is already cancelled - i.e. we should get #G_IO_ERROR_CANCELLED + * when the actual connection is not up. + */ + ca = g_cancellable_new (); + g_cancellable_cancel (ca); + g_dbus_connection_call (c, + "org.freedesktop.DBus", /* bus_name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "GetId", /* method name */ + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + ca, + (GAsyncReadyCallback) msg_cb_expect_error_cancelled, + &data); + g_main_loop_run (thread_loop); + g_object_unref (ca); + + /* + * Check that cancellation works when the message is already in flight. + */ + ca = g_cancellable_new (); + g_dbus_connection_call (c, + "org.freedesktop.DBus", /* bus_name */ + "/org/freedesktop/DBus", /* object path */ + "org.freedesktop.DBus", /* interface name */ + "GetId", /* method name */ + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + ca, + (GAsyncReadyCallback) msg_cb_expect_error_cancelled, + &data); + g_cancellable_cancel (ca); + g_main_loop_run (thread_loop); + g_object_unref (ca); + + /* + * Check that signals are delivered to the correct thread. + * + * First we subscribe to the signal, then we create a a private + * connection. This should cause a NameOwnerChanged message from + * the message bus. + */ + subscription_id = g_dbus_connection_signal_subscribe (c, + "org.freedesktop.DBus", /* sender */ + "org.freedesktop.DBus", /* interface */ + "NameOwnerChanged", /* member */ + "/org/freedesktop/DBus", /* path */ + NULL, + signal_handler, + &data, + NULL); + g_assert (subscription_id != 0); + g_assert (data.signal_count == 0); + + priv_c = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert (priv_c != NULL); + + g_main_loop_run (thread_loop); + g_assert (data.signal_count == 1); + + g_object_unref (priv_c); + + g_dbus_connection_signal_unsubscribe (c, subscription_id); + + /* ---------------------------------------------------------------------------------------------------- */ + + g_main_context_pop_thread_default (thread_context); + g_main_loop_unref (thread_loop); + g_main_context_unref (thread_context); + + g_main_loop_quit (loop); + + return NULL; +} + +static void +test_delivery_in_thread (void) +{ + GError *error; + GThread *thread; + + error = NULL; + thread = g_thread_create (test_delivery_in_thread_func, + NULL, + TRUE, + &error); + g_assert_no_error (error); + g_assert (thread != NULL); + + /* run the event loop - it is needed to dispatch D-Bus messages */ + g_main_loop_run (loop); + + g_thread_join (thread); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct { + GDBusProxy *proxy; + gint msec; + guint num; + gboolean async; + + GMainLoop *thread_loop; + GThread *thread; + + gboolean done; +} SyncThreadData; + +static void +sleep_cb (GDBusProxy *proxy, + GAsyncResult *res, + gpointer user_data) +{ + SyncThreadData *data = user_data; + GError *error; + GVariant *result; + + error = NULL; + result = g_dbus_proxy_call_finish (proxy, + res, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_assert_cmpstr (g_variant_get_type_string (result), ==, "()"); + g_variant_unref (result); + + g_assert (data->thread == g_thread_self ()); + + g_main_loop_quit (data->thread_loop); + + //g_debug ("async cb (%p)", g_thread_self ()); +} + +static gpointer +test_sleep_in_thread_func (gpointer _data) +{ + SyncThreadData *data = _data; + GMainContext *thread_context; + guint n; + + thread_context = g_main_context_new (); + data->thread_loop = g_main_loop_new (thread_context, FALSE); + g_main_context_push_thread_default (thread_context); + + data->thread = g_thread_self (); + + for (n = 0; n < data->num; n++) + { + if (data->async) + { + //g_debug ("invoking async (%p)", g_thread_self ()); + g_dbus_proxy_call (data->proxy, + "Sleep", + g_variant_new ("(i)", data->msec), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) sleep_cb, + data); + g_main_loop_run (data->thread_loop); + g_print ("A"); + //g_debug ("done invoking async (%p)", g_thread_self ()); + } + else + { + GError *error; + GVariant *result; + + error = NULL; + //g_debug ("invoking sync (%p)", g_thread_self ()); + result = g_dbus_proxy_call_sync (data->proxy, + "Sleep", + g_variant_new ("(i)", data->msec), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + g_print ("S"); + //g_debug ("done invoking sync (%p)", g_thread_self ()); + g_assert_no_error (error); + g_assert (result != NULL); + g_assert_cmpstr (g_variant_get_type_string (result), ==, "()"); + g_variant_unref (result); + } + } + + g_main_context_pop_thread_default (thread_context); + g_main_loop_unref (data->thread_loop); + g_main_context_unref (thread_context); + + data->done = TRUE; + g_main_loop_quit (loop); + + return NULL; +} + +static void +on_proxy_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + GDBusProxy *proxy, + gpointer user_data) +{ + guint n; + + /* + * Check that multiple threads can do calls without interferring with + * each other. We do this by creating three threads that call the + * Sleep() method on the server (which handles it asynchronously, e.g. + * it won't block other requests) with different sleep durations and + * a number of times. We do this so each set of calls add up to 4000 + * milliseconds. + * + * We run this test twice - first with async calls in each thread, then + * again with sync calls + */ + + for (n = 0; n < 2; n++) + { + gboolean do_async; + GThread *thread1; + GThread *thread2; + GThread *thread3; + SyncThreadData data1; + SyncThreadData data2; + SyncThreadData data3; + GError *error; + GTimeVal start_time; + GTimeVal end_time; + guint elapsed_msec; + + error = NULL; + do_async = (n == 0); + + g_get_current_time (&start_time); + + data1.proxy = proxy; + data1.msec = 40; + data1.num = 100; + data1.async = do_async; + data1.done = FALSE; + thread1 = g_thread_create (test_sleep_in_thread_func, + &data1, + TRUE, + &error); + g_assert_no_error (error); + g_assert (thread1 != NULL); + + data2.proxy = proxy; + data2.msec = 20; + data2.num = 200; + data2.async = do_async; + data2.done = FALSE; + thread2 = g_thread_create (test_sleep_in_thread_func, + &data2, + TRUE, + &error); + g_assert_no_error (error); + g_assert (thread2 != NULL); + + data3.proxy = proxy; + data3.msec = 100; + data3.num = 40; + data3.async = do_async; + data3.done = FALSE; + thread3 = g_thread_create (test_sleep_in_thread_func, + &data3, + TRUE, + &error); + g_assert_no_error (error); + g_assert (thread3 != NULL); + + /* we handle messages in the main loop - threads will quit it when they are done */ + while (!(data1.done && data2.done && data3.done)) + g_main_loop_run (loop); + + g_thread_join (thread1); + g_thread_join (thread2); + g_thread_join (thread3); + + g_get_current_time (&end_time); + + elapsed_msec = ((end_time.tv_sec * G_USEC_PER_SEC + end_time.tv_usec) - + (start_time.tv_sec * G_USEC_PER_SEC + start_time.tv_usec)) / 1000; + + //g_debug ("Elapsed time for %s = %d msec", n == 0 ? "async" : "sync", elapsed_msec); + + /* elapsed_msec should be 4000 msec + change for overhead */ + g_assert_cmpint (elapsed_msec, >=, 4000); + g_assert_cmpint (elapsed_msec, <, 5000); + + g_print (" "); + } + + g_main_loop_quit (loop); +} + +static void +on_proxy_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ +} + +static void +test_method_calls_in_thread (void) +{ + guint watcher_id; + + watcher_id = g_bus_watch_proxy (G_BUS_TYPE_SESSION, + "com.example.TestService", + G_BUS_NAME_WATCHER_FLAGS_NONE, + "/com/example/TestObject", + "com.example.Frob", + G_TYPE_DBUS_PROXY, + G_DBUS_PROXY_FLAGS_NONE, + on_proxy_appeared, + on_proxy_vanished, + NULL, + NULL); + + g_main_loop_run (loop); + + g_bus_unwatch_proxy (watcher_id); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +int +main (int argc, + char *argv[]) +{ + GError *error; + gint ret; + + g_type_init (); + g_thread_init (NULL); + g_test_init (&argc, &argv, NULL); + + /* all the tests rely on a shared main loop */ + loop = g_main_loop_new (NULL, FALSE); + + /* all the tests use a session bus with a well-known address that we can bring up and down + * using session_bus_up() and session_bus_down(). + */ + g_unsetenv ("DISPLAY"); + g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE); + + session_bus_up (); + + /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return + * until one can connect to the bus but that's not how things work right now + */ + usleep (500 * 1000); + + /* this is safe; testserver will exit once the bus goes away */ + g_assert (g_spawn_command_line_async ("./gdbus-testserver.py", NULL)); + + /* wait for the service to come up */ + usleep (500 * 1000); + + /* Create the connection in the main thread */ + error = NULL; + c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert (c != NULL); + + g_test_add_func ("/gdbus/delivery-in-thread", test_delivery_in_thread); + g_test_add_func ("/gdbus/method-calls-in-thread", test_method_calls_in_thread); + + ret = g_test_run(); + + g_object_unref (c); + + /* tear down bus */ + session_bus_down (); + + return ret; +}