explorer: Reuse drives that point to the same device to avoid memory leaks.
[wine] / programs / explorer / hal.c
1 /*
2  * HAL devices support
3  *
4  * Copyright 2006 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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
30 #include "windef.h"
31 #include "winbase.h"
32 #include "winreg.h"
33 #include "winuser.h"
34 #include "dbt.h"
35 #include "excpt.h"
36
37 #include "wine/library.h"
38 #include "wine/list.h"
39 #include "wine/exception.h"
40 #include "wine/debug.h"
41
42 WINE_DEFAULT_DEBUG_CHANNEL(explorer);
43
44 #ifdef HAVE_LIBHAL
45
46 #include <dbus/dbus.h>
47 #include <hal/libhal.h>
48
49 struct dos_drive
50 {
51     struct list entry;
52     char *udi;
53     int   drive;
54 };
55
56 static struct list drives_list = LIST_INIT(drives_list);
57
58
59 #define DBUS_FUNCS \
60     DO_FUNC(dbus_bus_get); \
61     DO_FUNC(dbus_connection_close); \
62     DO_FUNC(dbus_connection_read_write_dispatch); \
63     DO_FUNC(dbus_error_init); \
64     DO_FUNC(dbus_error_free); \
65     DO_FUNC(dbus_error_is_set)
66
67 #define HAL_FUNCS \
68     DO_FUNC(libhal_ctx_free); \
69     DO_FUNC(libhal_ctx_init); \
70     DO_FUNC(libhal_ctx_new); \
71     DO_FUNC(libhal_ctx_set_dbus_connection); \
72     DO_FUNC(libhal_ctx_set_device_added); \
73     DO_FUNC(libhal_ctx_set_device_property_modified); \
74     DO_FUNC(libhal_ctx_set_device_removed); \
75     DO_FUNC(libhal_ctx_shutdown); \
76     DO_FUNC(libhal_device_get_property_bool); \
77     DO_FUNC(libhal_device_get_property_string); \
78     DO_FUNC(libhal_device_add_property_watch); \
79     DO_FUNC(libhal_device_remove_property_watch); \
80     DO_FUNC(libhal_free_string); \
81     DO_FUNC(libhal_free_string_array); \
82     DO_FUNC(libhal_get_all_devices)
83
84 #define DO_FUNC(f) static typeof(f) * p_##f
85 DBUS_FUNCS;
86 HAL_FUNCS;
87 #undef DO_FUNC
88
89 static BOOL load_functions(void)
90 {
91     void *dbus_handle, *hal_handle;
92     char error[128];
93
94     if (!(dbus_handle = wine_dlopen(SONAME_LIBDBUS_1, RTLD_NOW, error, sizeof(error)))) goto failed;
95     if (!(hal_handle = wine_dlopen(SONAME_LIBHAL, RTLD_NOW, error, sizeof(error)))) goto failed;
96
97 #define DO_FUNC(f) if (!(p_##f = wine_dlsym( dbus_handle, #f, error, sizeof(error) ))) goto failed
98     DBUS_FUNCS;
99 #undef DO_FUNC
100
101 #define DO_FUNC(f) if (!(p_##f = wine_dlsym( hal_handle, #f, error, sizeof(error) ))) goto failed
102     HAL_FUNCS;
103 #undef DO_FUNC
104
105     return TRUE;
106
107 failed:
108     WINE_WARN( "failed to load HAL support: %s\n", error );
109     return FALSE;
110 }
111
112 static WINE_EXCEPTION_FILTER(assert_fault)
113 {
114     if (GetExceptionCode() == EXCEPTION_WINE_ASSERTION) return EXCEPTION_EXECUTE_HANDLER;
115     return EXCEPTION_CONTINUE_SEARCH;
116 }
117
118 /* send notification about a change to a given drive */
119 static void send_notify( int drive, int code )
120 {
121     DWORD_PTR result;
122     DEV_BROADCAST_VOLUME info;
123
124     info.dbcv_size       = sizeof(info);
125     info.dbcv_devicetype = DBT_DEVTYP_VOLUME;
126     info.dbcv_reserved   = 0;
127     info.dbcv_unitmask   = 1 << drive;
128     info.dbcv_flags      = DBTF_MEDIA;
129     SendMessageTimeoutW( HWND_BROADCAST, WM_DEVICECHANGE, code, (LPARAM)&info,
130                          SMTO_ABORTIFHUNG, 0, &result );
131 }
132
133 static char *get_dosdevices_path(void)
134 {
135     const char *config_dir = wine_get_config_dir();
136     size_t len = strlen(config_dir) + sizeof("/dosdevices/a::");
137     char *path = HeapAlloc( GetProcessHeap(), 0, len );
138     if (path)
139     {
140         strcpy( path, config_dir );
141         strcat( path, "/dosdevices/a::" );
142     }
143     return path;
144 }
145
146 /* find or create a DOS drive for the corresponding device */
147 static int add_drive( const char *device, const char *type )
148 {
149     char *path, *p;
150     char in_use[26];
151     struct stat dev_st, drive_st;
152     int drive, first, last, avail = 0;
153
154     if (stat( device, &dev_st ) == -1 || !S_ISBLK( dev_st.st_mode )) return -1;
155
156     if (!(path = get_dosdevices_path())) return -1;
157     p = path + strlen(path) - 3;
158
159     memset( in_use, 0, sizeof(in_use) );
160
161     first = 2;
162     last = 26;
163     if (type && !strcmp( type, "floppy" ))
164     {
165         first = 0;
166         last = 2;
167     }
168
169     while (avail != -1)
170     {
171         avail = -1;
172         for (drive = first; drive < last; drive++)
173         {
174             if (in_use[drive]) continue;  /* already checked */
175             *p = 'a' + drive;
176             if (stat( path, &drive_st ) == -1)
177             {
178                 if (lstat( path, &drive_st ) == -1 && errno == ENOENT)  /* this is a candidate */
179                 {
180                     if (avail == -1)
181                     {
182                         p[2] = 0;
183                         /* if mount point symlink doesn't exist either, it's available */
184                         if (lstat( path, &drive_st ) == -1 && errno == ENOENT) avail = drive;
185                         p[2] = ':';
186                     }
187                 }
188                 else in_use[drive] = 1;
189             }
190             else
191             {
192                 in_use[drive] = 1;
193                 if (!S_ISBLK( drive_st.st_mode )) continue;
194                 if (dev_st.st_rdev == drive_st.st_rdev) goto done;
195             }
196         }
197         if (avail != -1)
198         {
199             /* try to use the one we found */
200             drive = avail;
201             *p = 'a' + drive;
202             if (symlink( device, path ) != -1) goto done;
203             /* failed, retry the search */
204         }
205     }
206     drive = -1;
207
208 done:
209     HeapFree( GetProcessHeap(), 0, path );
210     return drive;
211 }
212
213 static void set_mount_point( struct dos_drive *drive, const char *mount_point )
214 {
215     char *path, *p;
216     struct stat path_st, mnt_st;
217
218     if (drive->drive == -1) return;
219     if (!(path = get_dosdevices_path())) return;
220     p = path + strlen(path) - 3;
221     *p = 'a' + drive->drive;
222     p[2] = 0;
223
224     if (mount_point[0])
225     {
226         /* try to avoid unlinking if already set correctly */
227         if (stat( path, &path_st ) == -1 || stat( mount_point, &mnt_st ) == -1 ||
228             path_st.st_dev != mnt_st.st_dev || path_st.st_ino != mnt_st.st_ino)
229         {
230             unlink( path );
231             symlink( mount_point, path );
232         }
233     }
234     else unlink( path );
235
236     HeapFree( GetProcessHeap(), 0, path );
237 }
238
239 static void add_dos_device( const char *udi, const char *device,
240                             const char *mount_point, const char *type )
241 {
242     struct dos_drive *drive;
243
244     /* first check if it already exists */
245     LIST_FOR_EACH_ENTRY( drive, &drives_list, struct dos_drive, entry )
246     {
247         if (!strcmp( udi, drive->udi )) goto found;
248     }
249
250     if (!(drive = HeapAlloc( GetProcessHeap(), 0, sizeof(*drive) ))) return;
251     if (!(drive->udi = HeapAlloc( GetProcessHeap(), 0, strlen(udi)+1 )))
252     {
253         HeapFree( GetProcessHeap(), 0, drive );
254         return;
255     }
256     strcpy( drive->udi, udi );
257     list_add_tail( &drives_list, &drive->entry );
258
259 found:
260     drive->drive = add_drive( device, type );
261     if (drive->drive != -1)
262     {
263         HKEY hkey;
264
265         set_mount_point( drive, mount_point );
266
267         WINE_TRACE( "added device %c: udi %s for %s on %s type %s\n",
268                     'a' + drive->drive, wine_dbgstr_a(udi), wine_dbgstr_a(device),
269                     wine_dbgstr_a(mount_point), wine_dbgstr_a(type) );
270
271         /* hack: force the drive type in the registry */
272         if (!RegCreateKeyA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Drives", &hkey ))
273         {
274             char name[3] = "a:";
275             name[0] += drive->drive;
276             if (!type || strcmp( type, "cdrom" )) type = "floppy";  /* FIXME: default to floppy */
277             RegSetValueExA( hkey, name, 0, REG_SZ, (const BYTE *)type, strlen(type) + 1 );
278             RegCloseKey( hkey );
279         }
280
281         send_notify( drive->drive, DBT_DEVICEARRIVAL );
282     }
283 }
284
285 static void remove_dos_device( struct dos_drive *drive )
286 {
287     HKEY hkey;
288
289     if (drive->drive != -1)
290     {
291         set_mount_point( drive, "" );
292
293         /* clear the registry key too */
294         if (!RegOpenKeyA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Drives", &hkey ))
295         {
296             char name[3] = "a:";
297             name[0] += drive->drive;
298             RegDeleteValueA( hkey, name );
299             RegCloseKey( hkey );
300         }
301
302         send_notify( drive->drive, DBT_DEVICEREMOVECOMPLETE );
303     }
304
305     list_remove( &drive->entry );
306     HeapFree( GetProcessHeap(), 0, drive->udi );
307     HeapFree( GetProcessHeap(), 0, drive );
308 }
309
310 /* HAL callback for new device */
311 static void new_device( LibHalContext *ctx, const char *udi )
312 {
313     DBusError error;
314     char *parent, *mount_point, *device, *type;
315
316     p_dbus_error_init( &error );
317
318     if (!(device = p_libhal_device_get_property_string( ctx, udi, "block.device", &error )))
319         goto done;
320
321     if (!(mount_point = p_libhal_device_get_property_string( ctx, udi, "volume.mount_point", &error )))
322         goto done;
323
324     if (!(parent = p_libhal_device_get_property_string( ctx, udi, "info.parent", &error )))
325         goto done;
326
327     if (!p_libhal_device_get_property_bool( ctx, parent, "storage.removable", &error ))
328         goto done;
329
330     if (!(type = p_libhal_device_get_property_string( ctx, parent, "storage.drive_type", &error )))
331         p_dbus_error_free( &error );  /* ignore error */
332
333     add_dos_device( udi, device, mount_point, type );
334
335     if (type) p_libhal_free_string( type );
336     p_libhal_free_string( parent );
337     p_libhal_free_string( device );
338     p_libhal_free_string( mount_point );
339
340     /* add property watch for mount point */
341     p_libhal_device_add_property_watch( ctx, udi, &error );
342
343 done:
344     p_dbus_error_free( &error );
345 }
346
347 /* HAL callback for removed device */
348 static void removed_device( LibHalContext *ctx, const char *udi )
349 {
350     DBusError error;
351     struct dos_drive *drive;
352
353     WINE_TRACE( "removed %s\n", wine_dbgstr_a(udi) );
354
355     LIST_FOR_EACH_ENTRY( drive, &drives_list, struct dos_drive, entry )
356     {
357         if (strcmp( udi, drive->udi )) continue;
358         p_dbus_error_init( &error );
359         p_libhal_device_remove_property_watch( ctx, udi, &error );
360         remove_dos_device( drive );
361         p_dbus_error_free( &error );
362         return;
363     }
364 }
365
366 /* HAL callback for property changes */
367 static void property_modified (LibHalContext *ctx, const char *udi,
368                                const char *key, dbus_bool_t is_removed, dbus_bool_t is_added)
369 {
370     WINE_TRACE( "udi %s key %s %s\n", wine_dbgstr_a(udi), wine_dbgstr_a(key),
371                 is_added ? "added" : is_removed ? "removed" : "modified" );
372
373     if (!strcmp( key, "volume.mount_point" )) new_device( ctx, udi );
374 }
375
376
377 static DWORD WINAPI hal_thread( void *arg )
378 {
379     DBusError error;
380     DBusConnection *dbc;
381     LibHalContext *ctx;
382     int i, num;
383     char **list;
384
385     if (!(ctx = p_libhal_ctx_new())) return 1;
386
387     p_dbus_error_init( &error );
388     if (!(dbc = p_dbus_bus_get( DBUS_BUS_SYSTEM, &error )))
389     {
390         WINE_WARN( "failed to get system dbus connection: %s\n", error.message );
391         p_dbus_error_free( &error );
392         return 1;
393     }
394
395     p_libhal_ctx_set_dbus_connection( ctx, dbc );
396     p_libhal_ctx_set_device_added( ctx, new_device );
397     p_libhal_ctx_set_device_removed( ctx, removed_device );
398     p_libhal_ctx_set_device_property_modified( ctx, property_modified );
399
400     if (!p_libhal_ctx_init( ctx, &error ))
401     {
402         WINE_WARN( "HAL context init failed: %s\n", error.message );
403         p_dbus_error_free( &error );
404         return 1;
405     }
406
407     /* retrieve all existing devices */
408     if (!(list = p_libhal_get_all_devices( ctx, &num, &error ))) p_dbus_error_free( &error );
409     else
410     {
411         for (i = 0; i < num; i++) new_device( ctx, list[i] );
412         p_libhal_free_string_array( list );
413     }
414
415     __TRY
416     {
417         while (p_dbus_connection_read_write_dispatch( dbc, -1 )) /* nothing */ ;
418     }
419     __EXCEPT( assert_fault )
420     {
421         WINE_WARN( "dbus assertion failure, disabling HAL support\n" );
422         return 1;
423     }
424     __ENDTRY;
425
426     p_libhal_ctx_shutdown( ctx, &error );
427     p_dbus_error_free( &error );  /* just in case */
428     p_dbus_connection_close( dbc );
429     p_libhal_ctx_free( ctx );
430     return 0;
431 }
432
433 void initialize_hal(void)
434 {
435     HANDLE handle;
436
437     if (!load_functions()) return;
438     if (!(handle = CreateThread( NULL, 0, hal_thread, NULL, 0, NULL ))) return;
439     CloseHandle( handle );
440 }
441
442 #else  /* HAVE_LIBHAL */
443
444 void initialize_hal(void)
445 {
446     WINE_WARN( "HAL support not compiled in\n" );
447 }
448
449 #endif  /* HAVE_LIBHAL */