msvcrt: Add a helper function to flush all buffers.
[wine] / dlls / mountmgr.sys / dbus.c
1 /*
2  * DBus devices support
3  *
4  * Copyright 2006, 2011 Alexandre Julliard
5  *
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.
10  *
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.
15  *
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
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include <assert.h>
25 #include <errno.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <sys/time.h>
29 #ifdef SONAME_LIBDBUS_1
30 # include <dbus/dbus.h>
31 #endif
32 #ifdef SONAME_LIBHAL
33 # include <hal/libhal.h>
34 #endif
35
36 #include "mountmgr.h"
37 #include "winnls.h"
38 #include "excpt.h"
39
40 #include "wine/library.h"
41 #include "wine/exception.h"
42 #include "wine/debug.h"
43
44 WINE_DEFAULT_DEBUG_CHANNEL(mountmgr);
45
46 #ifdef SONAME_LIBDBUS_1
47
48 #define DBUS_FUNCS \
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);
76
77 #define DO_FUNC(f) static typeof(f) * p_##f
78 DBUS_FUNCS;
79 #undef DO_FUNC
80
81 static int udisks_timeout = -1;
82 static DBusConnection *connection;
83
84 #ifdef SONAME_LIBHAL
85
86 #define HAL_FUNCS \
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)
102
103 #define DO_FUNC(f) static typeof(f) * p_##f
104 HAL_FUNCS;
105 #undef DO_FUNC
106
107 static BOOL load_hal_functions(void)
108 {
109     void *hal_handle;
110     char error[128];
111
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. */
115
116     if (!(hal_handle = wine_dlopen(SONAME_LIBHAL, RTLD_NOW|RTLD_GLOBAL, error, sizeof(error))))
117         goto failed;
118
119 #define DO_FUNC(f) if (!(p_##f = wine_dlsym( RTLD_DEFAULT, #f, error, sizeof(error) ))) goto failed
120     DBUS_FUNCS;
121 #undef DO_FUNC
122
123 #define DO_FUNC(f) if (!(p_##f = wine_dlsym( hal_handle, #f, error, sizeof(error) ))) goto failed
124     HAL_FUNCS;
125 #undef DO_FUNC
126
127     udisks_timeout = 3000;  /* don't try for too long if we can fall back to HAL */
128     return TRUE;
129
130 failed:
131     WARN( "failed to load HAL support: %s\n", error );
132     return FALSE;
133 }
134
135 #endif /* SONAME_LIBHAL */
136
137 static LONG WINAPI assert_fault(EXCEPTION_POINTERS *eptr)
138 {
139     if (eptr->ExceptionRecord->ExceptionCode == EXCEPTION_WINE_ASSERTION)
140         return EXCEPTION_EXECUTE_HANDLER;
141     return EXCEPTION_CONTINUE_SEARCH;
142 }
143
144 static GUID *parse_uuid( GUID *guid, const char *str )
145 {
146     /* standard uuid format */
147     if (strlen(str) == 36)
148     {
149         UNICODE_STRING strW;
150         WCHAR buffer[39];
151
152         if (MultiByteToWideChar( CP_UNIXCP, 0, str, 36, buffer + 1, 36 ))
153         {
154             buffer[0] = '{';
155             buffer[37] = '}';
156             buffer[38] = 0;
157             RtlInitUnicodeString( &strW, buffer );
158             if (!RtlGUIDFromString( &strW, guid )) return guid;
159         }
160     }
161
162     /* check for xxxx-xxxx format (FAT serial number) */
163     if (strlen(str) == 9 && str[4] == '-')
164     {
165         memset( guid, 0, sizeof(*guid) );
166         if (sscanf( str, "%hx-%hx", &guid->Data2, &guid->Data3 ) == 2) return guid;
167     }
168     return NULL;
169 }
170
171 static BOOL load_dbus_functions(void)
172 {
173     void *handle;
174     char error[128];
175
176     if (!(handle = wine_dlopen(SONAME_LIBDBUS_1, RTLD_NOW, error, sizeof(error))))
177         goto failed;
178
179 #define DO_FUNC(f) if (!(p_##f = wine_dlsym( handle, #f, error, sizeof(error) ))) goto failed
180     DBUS_FUNCS;
181 #undef DO_FUNC
182     return TRUE;
183
184 failed:
185     WARN( "failed to load DBUS support: %s\n", error );
186     return FALSE;
187 }
188
189 static const char *udisks_next_dict_entry( DBusMessageIter *iter, DBusMessageIter *variant )
190 {
191     DBusMessageIter sub;
192     const char *name;
193
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 );
200     return name;
201 }
202
203 /* UDisks callback for new device */
204 static void udisks_new_device( const char *udi )
205 {
206     static const char *dev_name = "org.freedesktop.UDisks.Device";
207     DBusMessage *request, *reply;
208     DBusMessageIter iter, variant;
209     DBusError error;
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;
216
217     request = p_dbus_message_new_method_call( "org.freedesktop.UDisks", udi,
218                                               "org.freedesktop.DBus.Properties", "GetAll" );
219     if (!request) return;
220
221     p_dbus_message_iter_init_append( request, &iter );
222     p_dbus_message_iter_append_basic( &iter, DBUS_TYPE_STRING, &dev_name );
223
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 );
227     if (!reply)
228     {
229         WARN( "failed: %s\n", error.message );
230         p_dbus_error_free( &error );
231         return;
232     }
233     p_dbus_error_free( &error );
234
235     p_dbus_message_iter_init( reply, &iter );
236     if (p_dbus_message_iter_get_arg_type( &iter ) == DBUS_TYPE_ARRAY)
237     {
238         const char *name;
239
240         p_dbus_message_iter_recurse( &iter, &iter );
241         while ((name = udisks_next_dict_entry( &iter, &variant )))
242         {
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" ))
250             {
251                 DBusMessageIter media;
252                 p_dbus_message_iter_recurse( &variant, &media );
253                 while (p_dbus_message_iter_get_arg_type( &media ) == DBUS_TYPE_STRING)
254                 {
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 );
264                 }
265             }
266             else if (!strcmp( name, "DeviceMountPaths" ))
267             {
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 );
272             }
273             else if (!strcmp( name, "IdUuid" ))
274             {
275                 char *uuid_str;
276                 p_dbus_message_iter_get_basic( &variant, &uuid_str );
277                 guid_ptr = parse_uuid( &guid, uuid_str );
278             }
279         }
280     }
281
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 );
285
286     if (type)
287     {
288         if (!strcmp( type, "iso9660" ))
289         {
290             removable = TRUE;
291             drive_type = DEVICE_CDROM;
292         }
293         else if (!strcmp( type, "udf" ))
294         {
295             removable = TRUE;
296             drive_type = DEVICE_DVD;
297         }
298     }
299
300     if (device)
301     {
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 );
304     }
305
306     p_dbus_message_unref( reply );
307 }
308
309 /* UDisks callback for removed device */
310 static void udisks_removed_device( const char *udi )
311 {
312     TRACE( "removed %s\n", wine_dbgstr_a(udi) );
313
314     if (!remove_dos_device( -1, udi )) remove_volume( udi );
315 }
316
317 /* UDisks callback for changed device */
318 static void udisks_changed_device( const char *udi )
319 {
320     udisks_new_device( udi );
321 }
322
323 static BOOL udisks_enumerate_devices(void)
324 {
325     DBusMessage *request, *reply;
326     DBusError error;
327     char **paths;
328     int i, count;
329
330     request = p_dbus_message_new_method_call( "org.freedesktop.UDisks", "/org/freedesktop/UDisks",
331                                               "org.freedesktop.UDisks", "EnumerateDevices" );
332     if (!request) return FALSE;
333
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 );
337     if (!reply)
338     {
339         WARN( "failed: %s\n", error.message );
340         p_dbus_error_free( &error );
341         return FALSE;
342     }
343     p_dbus_error_free( &error );
344
345     if (p_dbus_message_get_args( reply, &error, DBUS_TYPE_ARRAY,
346                                  DBUS_TYPE_OBJECT_PATH, &paths, &count, DBUS_TYPE_INVALID ))
347     {
348         for (i = 0; i < count; i++) udisks_new_device( paths[i] );
349         p_dbus_free_string_array( paths );
350     }
351     else WARN( "unexpected args in EnumerateDevices reply\n" );
352
353     p_dbus_message_unref( reply );
354     return TRUE;
355 }
356
357 static DBusHandlerResult udisks_filter( DBusConnection *ctx, DBusMessage *msg, void *user_data )
358 {
359     char *path;
360     DBusError error;
361
362     p_dbus_error_init( &error );
363
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 ))
366     {
367         udisks_new_device( path );
368     }
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 ))
371     {
372         udisks_removed_device( path );
373     }
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 ))
376     {
377         udisks_changed_device( path );
378     }
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 ) );
382
383     p_dbus_error_free( &error );
384     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
385 }
386
387 #ifdef SONAME_LIBHAL
388
389 /* HAL callback for new device */
390 static void hal_new_device( LibHalContext *ctx, const char *udi )
391 {
392     DBusError error;
393     char *parent = NULL;
394     char *mount_point = NULL;
395     char *device = NULL;
396     char *type = NULL;
397     char *uuid_str = NULL;
398     GUID guid, *guid_ptr = NULL;
399     enum device_type drive_type;
400
401     p_dbus_error_init( &error );
402
403     if (!(device = p_libhal_device_get_property_string( ctx, udi, "block.device", &error )))
404         goto done;
405
406     if (!(mount_point = p_libhal_device_get_property_string( ctx, udi, "volume.mount_point", &error )))
407         goto done;
408
409     if (!(parent = p_libhal_device_get_property_string( ctx, udi, "info.parent", &error )))
410         goto done;
411
412     if (!(uuid_str = p_libhal_device_get_property_string( ctx, udi, "volume.uuid", &error )))
413         p_dbus_error_free( &error );  /* ignore error */
414     else
415         guid_ptr = parse_uuid( &guid, uuid_str );
416
417     if (!(type = p_libhal_device_get_property_string( ctx, parent, "storage.drive_type", &error )))
418         p_dbus_error_free( &error );  /* ignore error */
419
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;
423
424     if (p_libhal_device_get_property_bool( ctx, parent, "storage.removable", &error ))
425     {
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 );
429     }
430     else if (guid_ptr) add_volume( udi, device, mount_point, DEVICE_HARDDISK_VOL, guid_ptr );
431
432 done:
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 );
439 }
440
441 /* HAL callback for removed device */
442 static void hal_removed_device( LibHalContext *ctx, const char *udi )
443 {
444     DBusError error;
445
446     TRACE( "removed %s\n", wine_dbgstr_a(udi) );
447
448     if (!remove_dos_device( -1, udi ))
449     {
450         p_dbus_error_init( &error );
451         p_libhal_device_remove_property_watch( ctx, udi, &error );
452         p_dbus_error_free( &error );
453     }
454     else remove_volume( udi );
455 }
456
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)
460 {
461     TRACE( "udi %s key %s %s\n", wine_dbgstr_a(udi), wine_dbgstr_a(key),
462            is_added ? "added" : is_removed ? "removed" : "modified" );
463
464     if (!strcmp( key, "volume.mount_point" )) hal_new_device( ctx, udi );
465 }
466
467 static BOOL hal_enumerate_devices(void)
468 {
469     LibHalContext *ctx;
470     DBusError error;
471     int i, num;
472     char **list;
473
474     if (!p_libhal_ctx_new) return FALSE;
475     if (!(ctx = p_libhal_ctx_new())) return FALSE;
476
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 );
481
482     p_dbus_error_init( &error );
483     if (!p_libhal_ctx_init( ctx, &error ))
484     {
485         WARN( "HAL context init failed: %s\n", error.message );
486         p_dbus_error_free( &error );
487         return FALSE;
488     }
489
490     /* retrieve all existing devices */
491     if (!(list = p_libhal_get_all_devices( ctx, &num, &error ))) p_dbus_error_free( &error );
492     else
493     {
494         for (i = 0; i < num; i++) hal_new_device( ctx, list[i] );
495         p_libhal_free_string_array( list );
496     }
497     return TRUE;
498 }
499
500 #endif /* SONAME_LIBHAL */
501
502 static DWORD WINAPI dbus_thread( void *arg )
503 {
504     static const char udisks_match[] = "type='signal',"
505                                        "interface='org.freedesktop.UDisks',"
506                                        "sender='org.freedesktop.UDisks'";
507
508     DBusError error;
509
510     p_dbus_error_init( &error );
511     if (!(connection = p_dbus_bus_get( DBUS_BUS_SYSTEM, &error )))
512     {
513         WARN( "failed to get system dbus connection: %s\n", error.message );
514         p_dbus_error_free( &error );
515         return 1;
516     }
517
518     if (p_dbus_connection_add_filter( connection, udisks_filter, NULL, NULL ))
519         p_dbus_bus_add_match( connection, udisks_match, &error );
520
521     if (!udisks_enumerate_devices())
522     {
523         p_dbus_bus_remove_match( connection, udisks_match, &error );
524         p_dbus_connection_remove_filter( connection, udisks_filter, NULL );
525
526 #ifdef SONAME_LIBHAL
527         if (!hal_enumerate_devices())
528         {
529             p_dbus_connection_close( connection );
530             p_dbus_error_free( &error );
531             return 1;
532         }
533 #endif
534     }
535
536     __TRY
537     {
538         while (p_dbus_connection_read_write_dispatch( connection, -1 )) /* nothing */ ;
539     }
540     __EXCEPT( assert_fault )
541     {
542         WARN( "dbus assertion failure, disabling support\n" );
543         return 1;
544     }
545     __ENDTRY;
546
547     p_dbus_connection_close( connection );
548     return 0;
549 }
550
551 void initialize_dbus(void)
552 {
553     HANDLE handle;
554
555 #ifdef SONAME_LIBHAL
556     if (!load_hal_functions())
557 #endif
558         if (!load_dbus_functions()) return;
559     if (!(handle = CreateThread( NULL, 0, dbus_thread, NULL, 0, NULL ))) return;
560     CloseHandle( handle );
561 }
562
563 #else  /* SONAME_LIBDBUS_1 */
564
565 void initialize_dbus(void)
566 {
567     TRACE( "Skipping, DBUS support not compiled in\n" );
568 }
569
570 #endif  /* SONAME_LIBDBUS_1 */