4 * Copyright 2006, 2011 Alexandre Julliard
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #include "wine/port.h"
29 #ifdef SONAME_LIBDBUS_1
30 # include <dbus/dbus.h>
33 # include <hal/libhal.h>
40 #include "wine/library.h"
41 #include "wine/exception.h"
42 #include "wine/debug.h"
44 WINE_DEFAULT_DEBUG_CHANNEL(mountmgr);
46 #ifdef SONAME_LIBDBUS_1
49 DO_FUNC(dbus_bus_add_match); \
50 DO_FUNC(dbus_bus_get); \
51 DO_FUNC(dbus_bus_remove_match); \
52 DO_FUNC(dbus_connection_add_filter); \
53 DO_FUNC(dbus_connection_close); \
54 DO_FUNC(dbus_connection_read_write_dispatch); \
55 DO_FUNC(dbus_connection_remove_filter); \
56 DO_FUNC(dbus_connection_send_with_reply_and_block); \
57 DO_FUNC(dbus_error_free); \
58 DO_FUNC(dbus_error_init); \
59 DO_FUNC(dbus_error_is_set); \
60 DO_FUNC(dbus_free_string_array); \
61 DO_FUNC(dbus_message_get_args); \
62 DO_FUNC(dbus_message_get_interface); \
63 DO_FUNC(dbus_message_get_member); \
64 DO_FUNC(dbus_message_get_path); \
65 DO_FUNC(dbus_message_get_type); \
66 DO_FUNC(dbus_message_is_signal); \
67 DO_FUNC(dbus_message_iter_append_basic); \
68 DO_FUNC(dbus_message_iter_get_arg_type); \
69 DO_FUNC(dbus_message_iter_get_basic); \
70 DO_FUNC(dbus_message_iter_init); \
71 DO_FUNC(dbus_message_iter_init_append); \
72 DO_FUNC(dbus_message_iter_next); \
73 DO_FUNC(dbus_message_iter_recurse); \
74 DO_FUNC(dbus_message_new_method_call); \
75 DO_FUNC(dbus_message_unref);
77 #define DO_FUNC(f) static typeof(f) * p_##f
81 static int udisks_timeout = -1;
82 static DBusConnection *connection;
87 DO_FUNC(libhal_ctx_free); \
88 DO_FUNC(libhal_ctx_init); \
89 DO_FUNC(libhal_ctx_new); \
90 DO_FUNC(libhal_ctx_set_dbus_connection); \
91 DO_FUNC(libhal_ctx_set_device_added); \
92 DO_FUNC(libhal_ctx_set_device_property_modified); \
93 DO_FUNC(libhal_ctx_set_device_removed); \
94 DO_FUNC(libhal_ctx_shutdown); \
95 DO_FUNC(libhal_device_get_property_bool); \
96 DO_FUNC(libhal_device_get_property_string); \
97 DO_FUNC(libhal_device_add_property_watch); \
98 DO_FUNC(libhal_device_remove_property_watch); \
99 DO_FUNC(libhal_free_string); \
100 DO_FUNC(libhal_free_string_array); \
101 DO_FUNC(libhal_get_all_devices)
103 #define DO_FUNC(f) static typeof(f) * p_##f
107 static BOOL load_hal_functions(void)
112 /* Load libhal with RTLD_GLOBAL so that the dbus symbols are available.
113 * We can't load libdbus directly since libhal may have been built against a
114 * different version but with the same soname. Binary compatibility is for wimps. */
116 if (!(hal_handle = wine_dlopen(SONAME_LIBHAL, RTLD_NOW|RTLD_GLOBAL, error, sizeof(error))))
119 #define DO_FUNC(f) if (!(p_##f = wine_dlsym( RTLD_DEFAULT, #f, error, sizeof(error) ))) goto failed
123 #define DO_FUNC(f) if (!(p_##f = wine_dlsym( hal_handle, #f, error, sizeof(error) ))) goto failed
127 udisks_timeout = 3000; /* don't try for too long if we can fall back to HAL */
131 WARN( "failed to load HAL support: %s\n", error );
135 #endif /* SONAME_LIBHAL */
137 static LONG WINAPI assert_fault(EXCEPTION_POINTERS *eptr)
139 if (eptr->ExceptionRecord->ExceptionCode == EXCEPTION_WINE_ASSERTION)
140 return EXCEPTION_EXECUTE_HANDLER;
141 return EXCEPTION_CONTINUE_SEARCH;
144 static GUID *parse_uuid( GUID *guid, const char *str )
146 /* standard uuid format */
147 if (strlen(str) == 36)
152 if (MultiByteToWideChar( CP_UNIXCP, 0, str, 36, buffer + 1, 36 ))
157 RtlInitUnicodeString( &strW, buffer );
158 if (!RtlGUIDFromString( &strW, guid )) return guid;
162 /* check for xxxx-xxxx format (FAT serial number) */
163 if (strlen(str) == 9 && str[4] == '-')
165 memset( guid, 0, sizeof(*guid) );
166 if (sscanf( str, "%hx-%hx", &guid->Data2, &guid->Data3 ) == 2) return guid;
171 static BOOL load_dbus_functions(void)
176 if (!(handle = wine_dlopen(SONAME_LIBDBUS_1, RTLD_NOW, error, sizeof(error))))
179 #define DO_FUNC(f) if (!(p_##f = wine_dlsym( handle, #f, error, sizeof(error) ))) goto failed
185 WARN( "failed to load DBUS support: %s\n", error );
189 static const char *udisks_next_dict_entry( DBusMessageIter *iter, DBusMessageIter *variant )
194 if (p_dbus_message_iter_get_arg_type( iter ) != DBUS_TYPE_DICT_ENTRY) return NULL;
195 p_dbus_message_iter_recurse( iter, &sub );
196 p_dbus_message_iter_next( iter );
197 p_dbus_message_iter_get_basic( &sub, &name );
198 p_dbus_message_iter_next( &sub );
199 p_dbus_message_iter_recurse( &sub, variant );
203 /* UDisks callback for new device */
204 static void udisks_new_device( const char *udi )
206 static const char *dev_name = "org.freedesktop.UDisks.Device";
207 DBusMessage *request, *reply;
208 DBusMessageIter iter, variant;
210 const char *device = NULL;
211 const char *mount_point = NULL;
212 const char *type = NULL;
213 GUID guid, *guid_ptr = NULL;
214 int removable = FALSE;
215 enum device_type drive_type = DEVICE_UNKNOWN;
217 request = p_dbus_message_new_method_call( "org.freedesktop.UDisks", udi,
218 "org.freedesktop.DBus.Properties", "GetAll" );
219 if (!request) return;
221 p_dbus_message_iter_init_append( request, &iter );
222 p_dbus_message_iter_append_basic( &iter, DBUS_TYPE_STRING, &dev_name );
224 p_dbus_error_init( &error );
225 reply = p_dbus_connection_send_with_reply_and_block( connection, request, -1, &error );
226 p_dbus_message_unref( request );
229 WARN( "failed: %s\n", error.message );
230 p_dbus_error_free( &error );
233 p_dbus_error_free( &error );
235 p_dbus_message_iter_init( reply, &iter );
236 if (p_dbus_message_iter_get_arg_type( &iter ) == DBUS_TYPE_ARRAY)
240 p_dbus_message_iter_recurse( &iter, &iter );
241 while ((name = udisks_next_dict_entry( &iter, &variant )))
243 if (!strcmp( name, "DeviceFile" ))
244 p_dbus_message_iter_get_basic( &variant, &device );
245 else if (!strcmp( name, "DeviceIsRemovable" ))
246 p_dbus_message_iter_get_basic( &variant, &removable );
247 else if (!strcmp( name, "IdType" ))
248 p_dbus_message_iter_get_basic( &variant, &type );
249 else if (!strcmp( name, "DriveMediaCompatibility" ))
251 DBusMessageIter media;
252 p_dbus_message_iter_recurse( &variant, &media );
253 while (p_dbus_message_iter_get_arg_type( &media ) == DBUS_TYPE_STRING)
255 const char *media_type;
256 p_dbus_message_iter_get_basic( &media, &media_type );
257 if (!strncmp( media_type, "optical_dvd", 11 ))
258 drive_type = DEVICE_DVD;
259 if (!strncmp( media_type, "floppy", 6 ))
260 drive_type = DEVICE_FLOPPY;
261 else if (!strncmp( media_type, "optical_", 8 ) && drive_type == DEVICE_UNKNOWN)
262 drive_type = DEVICE_CDROM;
263 p_dbus_message_iter_next( &media );
266 else if (!strcmp( name, "DeviceMountPaths" ))
268 DBusMessageIter paths;
269 p_dbus_message_iter_recurse( &variant, &paths );
270 if (p_dbus_message_iter_get_arg_type( &paths ) == DBUS_TYPE_STRING)
271 p_dbus_message_iter_get_basic( &paths, &mount_point );
273 else if (!strcmp( name, "IdUuid" ))
276 p_dbus_message_iter_get_basic( &variant, &uuid_str );
277 guid_ptr = parse_uuid( &guid, uuid_str );
282 TRACE( "udi %s device %s mount point %s uuid %s type %s removable %u\n",
283 debugstr_a(udi), debugstr_a(device), debugstr_a(mount_point),
284 debugstr_guid(guid_ptr), debugstr_a(type), removable );
288 if (!strcmp( type, "iso9660" ))
291 drive_type = DEVICE_CDROM;
293 else if (!strcmp( type, "udf" ))
296 drive_type = DEVICE_DVD;
302 if (removable) add_dos_device( -1, udi, device, mount_point, drive_type, guid_ptr );
303 else if (guid_ptr) add_volume( udi, device, mount_point, DEVICE_HARDDISK_VOL, guid_ptr );
306 p_dbus_message_unref( reply );
309 /* UDisks callback for removed device */
310 static void udisks_removed_device( const char *udi )
312 TRACE( "removed %s\n", wine_dbgstr_a(udi) );
314 if (!remove_dos_device( -1, udi )) remove_volume( udi );
317 /* UDisks callback for changed device */
318 static void udisks_changed_device( const char *udi )
320 udisks_new_device( udi );
323 static BOOL udisks_enumerate_devices(void)
325 DBusMessage *request, *reply;
330 request = p_dbus_message_new_method_call( "org.freedesktop.UDisks", "/org/freedesktop/UDisks",
331 "org.freedesktop.UDisks", "EnumerateDevices" );
332 if (!request) return FALSE;
334 p_dbus_error_init( &error );
335 reply = p_dbus_connection_send_with_reply_and_block( connection, request, udisks_timeout, &error );
336 p_dbus_message_unref( request );
339 WARN( "failed: %s\n", error.message );
340 p_dbus_error_free( &error );
343 p_dbus_error_free( &error );
345 if (p_dbus_message_get_args( reply, &error, DBUS_TYPE_ARRAY,
346 DBUS_TYPE_OBJECT_PATH, &paths, &count, DBUS_TYPE_INVALID ))
348 for (i = 0; i < count; i++) udisks_new_device( paths[i] );
349 p_dbus_free_string_array( paths );
351 else WARN( "unexpected args in EnumerateDevices reply\n" );
353 p_dbus_message_unref( reply );
357 static DBusHandlerResult udisks_filter( DBusConnection *ctx, DBusMessage *msg, void *user_data )
362 p_dbus_error_init( &error );
364 if (p_dbus_message_is_signal( msg, "org.freedesktop.UDisks", "DeviceAdded" ) &&
365 p_dbus_message_get_args( msg, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID ))
367 udisks_new_device( path );
369 else if (p_dbus_message_is_signal( msg, "org.freedesktop.UDisks", "DeviceRemoved" ) &&
370 p_dbus_message_get_args( msg, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID ))
372 udisks_removed_device( path );
374 else if (p_dbus_message_is_signal( msg, "org.freedesktop.UDisks", "DeviceChanged" ) &&
375 p_dbus_message_get_args( msg, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID ))
377 udisks_changed_device( path );
379 else TRACE( "ignoring message type=%d path=%s interface=%s method=%s\n",
380 p_dbus_message_get_type( msg ), p_dbus_message_get_path( msg ),
381 p_dbus_message_get_interface( msg ), p_dbus_message_get_member( msg ) );
383 p_dbus_error_free( &error );
384 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
389 /* HAL callback for new device */
390 static void hal_new_device( LibHalContext *ctx, const char *udi )
394 char *mount_point = NULL;
397 char *uuid_str = NULL;
398 GUID guid, *guid_ptr = NULL;
399 enum device_type drive_type;
401 p_dbus_error_init( &error );
403 if (!(device = p_libhal_device_get_property_string( ctx, udi, "block.device", &error )))
406 if (!(mount_point = p_libhal_device_get_property_string( ctx, udi, "volume.mount_point", &error )))
409 if (!(parent = p_libhal_device_get_property_string( ctx, udi, "info.parent", &error )))
412 if (!(uuid_str = p_libhal_device_get_property_string( ctx, udi, "volume.uuid", &error )))
413 p_dbus_error_free( &error ); /* ignore error */
415 guid_ptr = parse_uuid( &guid, uuid_str );
417 if (!(type = p_libhal_device_get_property_string( ctx, parent, "storage.drive_type", &error )))
418 p_dbus_error_free( &error ); /* ignore error */
420 if (type && !strcmp( type, "cdrom" )) drive_type = DEVICE_CDROM;
421 else if (type && !strcmp( type, "floppy" )) drive_type = DEVICE_FLOPPY;
422 else drive_type = DEVICE_UNKNOWN;
424 if (p_libhal_device_get_property_bool( ctx, parent, "storage.removable", &error ))
426 add_dos_device( -1, udi, device, mount_point, drive_type, guid_ptr );
427 /* add property watch for mount point */
428 p_libhal_device_add_property_watch( ctx, udi, &error );
430 else if (guid_ptr) add_volume( udi, device, mount_point, DEVICE_HARDDISK_VOL, guid_ptr );
433 if (type) p_libhal_free_string( type );
434 if (parent) p_libhal_free_string( parent );
435 if (device) p_libhal_free_string( device );
436 if (uuid_str) p_libhal_free_string( uuid_str );
437 if (mount_point) p_libhal_free_string( mount_point );
438 p_dbus_error_free( &error );
441 /* HAL callback for removed device */
442 static void hal_removed_device( LibHalContext *ctx, const char *udi )
446 TRACE( "removed %s\n", wine_dbgstr_a(udi) );
448 if (!remove_dos_device( -1, udi ))
450 p_dbus_error_init( &error );
451 p_libhal_device_remove_property_watch( ctx, udi, &error );
452 p_dbus_error_free( &error );
454 else remove_volume( udi );
457 /* HAL callback for property changes */
458 static void hal_property_modified (LibHalContext *ctx, const char *udi,
459 const char *key, dbus_bool_t is_removed, dbus_bool_t is_added)
461 TRACE( "udi %s key %s %s\n", wine_dbgstr_a(udi), wine_dbgstr_a(key),
462 is_added ? "added" : is_removed ? "removed" : "modified" );
464 if (!strcmp( key, "volume.mount_point" )) hal_new_device( ctx, udi );
467 static BOOL hal_enumerate_devices(void)
474 if (!p_libhal_ctx_new) return FALSE;
475 if (!(ctx = p_libhal_ctx_new())) return FALSE;
477 p_libhal_ctx_set_dbus_connection( ctx, connection );
478 p_libhal_ctx_set_device_added( ctx, hal_new_device );
479 p_libhal_ctx_set_device_removed( ctx, hal_removed_device );
480 p_libhal_ctx_set_device_property_modified( ctx, hal_property_modified );
482 p_dbus_error_init( &error );
483 if (!p_libhal_ctx_init( ctx, &error ))
485 WARN( "HAL context init failed: %s\n", error.message );
486 p_dbus_error_free( &error );
490 /* retrieve all existing devices */
491 if (!(list = p_libhal_get_all_devices( ctx, &num, &error ))) p_dbus_error_free( &error );
494 for (i = 0; i < num; i++) hal_new_device( ctx, list[i] );
495 p_libhal_free_string_array( list );
500 #endif /* SONAME_LIBHAL */
502 static DWORD WINAPI dbus_thread( void *arg )
504 static const char udisks_match[] = "type='signal',"
505 "interface='org.freedesktop.UDisks',"
506 "sender='org.freedesktop.UDisks'";
510 p_dbus_error_init( &error );
511 if (!(connection = p_dbus_bus_get( DBUS_BUS_SYSTEM, &error )))
513 WARN( "failed to get system dbus connection: %s\n", error.message );
514 p_dbus_error_free( &error );
518 if (p_dbus_connection_add_filter( connection, udisks_filter, NULL, NULL ))
519 p_dbus_bus_add_match( connection, udisks_match, &error );
521 if (!udisks_enumerate_devices())
523 p_dbus_bus_remove_match( connection, udisks_match, &error );
524 p_dbus_connection_remove_filter( connection, udisks_filter, NULL );
527 if (!hal_enumerate_devices())
529 p_dbus_connection_close( connection );
530 p_dbus_error_free( &error );
538 while (p_dbus_connection_read_write_dispatch( connection, -1 )) /* nothing */ ;
540 __EXCEPT( assert_fault )
542 WARN( "dbus assertion failure, disabling support\n" );
547 p_dbus_connection_close( connection );
551 void initialize_dbus(void)
556 if (!load_hal_functions())
558 if (!load_dbus_functions()) return;
559 if (!(handle = CreateThread( NULL, 0, dbus_thread, NULL, 0, NULL ))) return;
560 CloseHandle( handle );
563 #else /* SONAME_LIBDBUS_1 */
565 void initialize_dbus(void)
567 TRACE( "Skipping, DBUS support not compiled in\n" );
570 #endif /* SONAME_LIBDBUS_1 */