shlwapi: Fix ASSOC_GetExecutable not to use uninitialised variable.
[wine] / dlls / mountmgr.sys / device.c
1 /*
2  * Dynamic 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., 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
30 #include "windef.h"
31 #include "winbase.h"
32 #include "winreg.h"
33 #include "winuser.h"
34 #include "dbt.h"
35
36 #include "wine/library.h"
37 #include "wine/list.h"
38 #include "wine/unicode.h"
39 #include "wine/debug.h"
40
41 WINE_DEFAULT_DEBUG_CHANNEL(mountmgr);
42
43 static const WCHAR drive_types[][8] =
44 {
45     { 0 }, /* DRIVE_UNKNOWN */
46     { 0 }, /* DRIVE_NO_ROOT_DIR */
47     {'f','l','o','p','p','y',0}, /* DRIVE_REMOVABLE */
48     {'h','d',0}, /* DRIVE_FIXED */
49     {'n','e','t','w','o','r','k',0}, /* DRIVE_REMOTE */
50     {'c','d','r','o','m',0}, /* DRIVE_CDROM */
51     {'r','a','m','d','i','s','k',0} /* DRIVE_RAMDISK */
52 };
53
54 struct dos_drive
55 {
56     struct list entry;
57     char *udi;
58     int   drive;
59 };
60
61 static struct list drives_list = LIST_INIT(drives_list);
62
63 static char *get_dosdevices_path(void)
64 {
65     const char *config_dir = wine_get_config_dir();
66     size_t len = strlen(config_dir) + sizeof("/dosdevices/a::");
67     char *path = HeapAlloc( GetProcessHeap(), 0, len );
68     if (path)
69     {
70         strcpy( path, config_dir );
71         strcat( path, "/dosdevices/a::" );
72     }
73     return path;
74 }
75
76 /* send notification about a change to a given drive */
77 static void send_notify( int drive, int code )
78 {
79     DWORD_PTR result;
80     DEV_BROADCAST_VOLUME info;
81
82     info.dbcv_size       = sizeof(info);
83     info.dbcv_devicetype = DBT_DEVTYP_VOLUME;
84     info.dbcv_reserved   = 0;
85     info.dbcv_unitmask   = 1 << drive;
86     info.dbcv_flags      = DBTF_MEDIA;
87     result = BroadcastSystemMessageW( BSF_FORCEIFHUNG|BSF_QUERY, NULL,
88                                       WM_DEVICECHANGE, code, (LPARAM)&info );
89 }
90
91 static inline int is_valid_device( struct stat *st )
92 {
93 #if defined(linux) || defined(__sun__)
94     return S_ISBLK( st->st_mode );
95 #else
96     /* disks are char devices on *BSD */
97     return S_ISCHR( st->st_mode );
98 #endif
99 }
100
101 /* find or create a DOS drive for the corresponding device */
102 static int add_drive( const char *device, DWORD type )
103 {
104     char *path, *p;
105     char in_use[26];
106     struct stat dev_st, drive_st;
107     int drive, first, last, avail = 0;
108
109     if (stat( device, &dev_st ) == -1 || !is_valid_device( &dev_st )) return -1;
110
111     if (!(path = get_dosdevices_path())) return -1;
112     p = path + strlen(path) - 3;
113
114     memset( in_use, 0, sizeof(in_use) );
115
116     first = 2;
117     last = 26;
118     if (type == DRIVE_REMOVABLE)
119     {
120         first = 0;
121         last = 2;
122     }
123
124     while (avail != -1)
125     {
126         avail = -1;
127         for (drive = first; drive < last; drive++)
128         {
129             if (in_use[drive]) continue;  /* already checked */
130             *p = 'a' + drive;
131             if (stat( path, &drive_st ) == -1)
132             {
133                 if (lstat( path, &drive_st ) == -1 && errno == ENOENT)  /* this is a candidate */
134                 {
135                     if (avail == -1)
136                     {
137                         p[2] = 0;
138                         /* if mount point symlink doesn't exist either, it's available */
139                         if (lstat( path, &drive_st ) == -1 && errno == ENOENT) avail = drive;
140                         p[2] = ':';
141                     }
142                 }
143                 else in_use[drive] = 1;
144             }
145             else
146             {
147                 in_use[drive] = 1;
148                 if (!is_valid_device( &drive_st )) continue;
149                 if (dev_st.st_rdev == drive_st.st_rdev) goto done;
150             }
151         }
152         if (avail != -1)
153         {
154             /* try to use the one we found */
155             drive = avail;
156             *p = 'a' + drive;
157             if (symlink( device, path ) != -1) goto done;
158             /* failed, retry the search */
159         }
160     }
161     drive = -1;
162
163 done:
164     HeapFree( GetProcessHeap(), 0, path );
165     return drive;
166 }
167
168 static BOOL set_mount_point( struct dos_drive *drive, const char *mount_point )
169 {
170     char *path, *p;
171     struct stat path_st, mnt_st;
172     BOOL modified = FALSE;
173
174     if (drive->drive == -1) return FALSE;
175     if (!(path = get_dosdevices_path())) return FALSE;
176     p = path + strlen(path) - 3;
177     *p = 'a' + drive->drive;
178     p[2] = 0;
179
180     if (mount_point[0])
181     {
182         /* try to avoid unlinking if already set correctly */
183         if (stat( path, &path_st ) == -1 || stat( mount_point, &mnt_st ) == -1 ||
184             path_st.st_dev != mnt_st.st_dev || path_st.st_ino != mnt_st.st_ino)
185         {
186             unlink( path );
187             symlink( mount_point, path );
188             modified = TRUE;
189         }
190     }
191     else
192     {
193         if (unlink( path ) != -1) modified = TRUE;
194     }
195
196     HeapFree( GetProcessHeap(), 0, path );
197     return modified;
198 }
199
200 BOOL add_dos_device( const char *udi, const char *device,
201                      const char *mount_point, DWORD type )
202 {
203     struct dos_drive *drive;
204
205     /* first check if it already exists */
206     LIST_FOR_EACH_ENTRY( drive, &drives_list, struct dos_drive, entry )
207     {
208         if (!strcmp( udi, drive->udi )) goto found;
209     }
210
211     if (!(drive = HeapAlloc( GetProcessHeap(), 0, sizeof(*drive) ))) return FALSE;
212     if (!(drive->udi = HeapAlloc( GetProcessHeap(), 0, strlen(udi)+1 )))
213     {
214         HeapFree( GetProcessHeap(), 0, drive );
215         return FALSE;
216     }
217     strcpy( drive->udi, udi );
218     list_add_tail( &drives_list, &drive->entry );
219
220 found:
221     drive->drive = add_drive( device, type );
222     if (drive->drive != -1)
223     {
224         HKEY hkey;
225
226         set_mount_point( drive, mount_point );
227
228         TRACE( "added device %c: udi %s for %s on %s type %u\n",
229                     'a' + drive->drive, wine_dbgstr_a(udi), wine_dbgstr_a(device),
230                     wine_dbgstr_a(mount_point), type );
231
232         /* hack: force the drive type in the registry */
233         if (!RegCreateKeyA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Drives", &hkey ))
234         {
235             const WCHAR *type_name = drive_types[type];
236             WCHAR name[3] = {'a',':',0};
237
238             name[0] += drive->drive;
239             if (type_name[0])
240                 RegSetValueExW( hkey, name, 0, REG_SZ, (const BYTE *)type_name,
241                                 (strlenW(type_name) + 1) * sizeof(WCHAR) );
242             else
243                 RegDeleteValueW( hkey, name );
244             RegCloseKey( hkey );
245         }
246
247         send_notify( drive->drive, DBT_DEVICEARRIVAL );
248     }
249     return TRUE;
250 }
251
252 BOOL remove_dos_device( const char *udi )
253 {
254     HKEY hkey;
255     struct dos_drive *drive;
256
257     LIST_FOR_EACH_ENTRY( drive, &drives_list, struct dos_drive, entry )
258     {
259         if (strcmp( udi, drive->udi )) continue;
260
261         if (drive->drive != -1)
262         {
263             BOOL modified = set_mount_point( drive, "" );
264
265             /* clear the registry key too */
266             if (!RegOpenKeyA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Drives", &hkey ))
267             {
268                 WCHAR name[3] = {'a',':',0};
269                 name[0] += drive->drive;
270                 RegDeleteValueW( hkey, name );
271                 RegCloseKey( hkey );
272             }
273
274             if (modified) send_notify( drive->drive, DBT_DEVICEREMOVECOMPLETE );
275         }
276
277         list_remove( &drive->entry );
278         HeapFree( GetProcessHeap(), 0, drive->udi );
279         HeapFree( GetProcessHeap(), 0, drive );
280         return TRUE;
281     }
282     return FALSE;
283 }