Release 1.5.29.
[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 "mountmgr.h"
31 #include "winreg.h"
32 #include "winuser.h"
33 #include "dbt.h"
34
35 #include "wine/library.h"
36 #include "wine/list.h"
37 #include "wine/unicode.h"
38 #include "wine/debug.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(mountmgr);
41
42 #define MAX_DOS_DRIVES 26
43
44 static const WCHAR drive_types[][8] =
45 {
46     { 0 },                           /* DEVICE_UNKNOWN */
47     { 0 },                           /* DEVICE_HARDDISK */
48     {'h','d',0},                     /* DEVICE_HARDDISK_VOL */
49     {'f','l','o','p','p','y',0},     /* DEVICE_FLOPPY */
50     {'c','d','r','o','m',0},         /* DEVICE_CDROM */
51     {'c','d','r','o','m',0},         /* DEVICE_DVD */
52     {'n','e','t','w','o','r','k',0}, /* DEVICE_NETWORK */
53     {'r','a','m','d','i','s','k',0}  /* DEVICE_RAMDISK */
54 };
55
56 static const WCHAR drives_keyW[] = {'S','o','f','t','w','a','r','e','\\',
57                                     'W','i','n','e','\\','D','r','i','v','e','s',0};
58
59 struct disk_device
60 {
61     enum device_type      type;        /* drive type */
62     DEVICE_OBJECT        *dev_obj;     /* disk device allocated for this volume */
63     UNICODE_STRING        name;        /* device name */
64     UNICODE_STRING        symlink;     /* device symlink if any */
65     STORAGE_DEVICE_NUMBER devnum;      /* device number info */
66     char                 *unix_device; /* unix device path */
67     char                 *unix_mount;  /* unix mount point path */
68 };
69
70 struct volume
71 {
72     struct list           entry;       /* entry in volumes list */
73     struct disk_device   *device;      /* disk device */
74     char                 *udi;         /* unique identifier for dynamic volumes */
75     unsigned int          ref;         /* ref count */
76     GUID                  guid;        /* volume uuid */
77     struct mount_point   *mount;       /* Volume{xxx} mount point */
78 };
79
80 struct dos_drive
81 {
82     struct list           entry;       /* entry in drives list */
83     struct volume        *volume;      /* volume for this drive */
84     int                   drive;       /* drive letter (0 = A: etc.) */
85     struct mount_point   *mount;       /* DosDevices mount point */
86 };
87
88 static struct list drives_list = LIST_INIT(drives_list);
89 static struct list volumes_list = LIST_INIT(volumes_list);
90
91 static DRIVER_OBJECT *harddisk_driver;
92
93 static CRITICAL_SECTION device_section;
94 static CRITICAL_SECTION_DEBUG critsect_debug =
95 {
96     0, 0, &device_section,
97     { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
98       0, 0, { (DWORD_PTR)(__FILE__ ": device_section") }
99 };
100 static CRITICAL_SECTION device_section = { &critsect_debug, -1, 0, 0, 0, 0 };
101
102 static char *get_dosdevices_path( char **drive )
103 {
104     const char *config_dir = wine_get_config_dir();
105     size_t len = strlen(config_dir) + sizeof("/dosdevices/a::");
106     char *path = HeapAlloc( GetProcessHeap(), 0, len );
107     if (path)
108     {
109         strcpy( path, config_dir );
110         strcat( path, "/dosdevices/a::" );
111         *drive = path + len - 4;
112     }
113     return path;
114 }
115
116 static char *strdupA( const char *str )
117 {
118     char *ret;
119
120     if (!str) return NULL;
121     if ((ret = RtlAllocateHeap( GetProcessHeap(), 0, strlen(str) + 1 ))) strcpy( ret, str );
122     return ret;
123 }
124
125 static const GUID *get_default_uuid( int letter )
126 {
127     static GUID guid;
128
129     guid.Data4[7] = 'A' + letter;
130     return &guid;
131 }
132
133 /* read a Unix symlink; returned buffer must be freed by caller */
134 static char *read_symlink( const char *path )
135 {
136     char *buffer;
137     int ret, size = 128;
138
139     for (;;)
140     {
141         if (!(buffer = RtlAllocateHeap( GetProcessHeap(), 0, size )))
142         {
143             SetLastError( ERROR_NOT_ENOUGH_MEMORY );
144             return 0;
145         }
146         ret = readlink( path, buffer, size );
147         if (ret == -1)
148         {
149             RtlFreeHeap( GetProcessHeap(), 0, buffer );
150             return 0;
151         }
152         if (ret != size)
153         {
154             buffer[ret] = 0;
155             return buffer;
156         }
157         RtlFreeHeap( GetProcessHeap(), 0, buffer );
158         size *= 2;
159     }
160 }
161
162 /* update a symlink if it changed; return TRUE if updated */
163 static void update_symlink( const char *path, const char *dest, const char *orig_dest )
164 {
165     if (dest && dest[0])
166     {
167         if (!orig_dest || strcmp( orig_dest, dest ))
168         {
169             unlink( path );
170             symlink( dest, path );
171         }
172     }
173     else unlink( path );
174 }
175
176 /* send notification about a change to a given drive */
177 static void send_notify( int drive, int code )
178 {
179     DEV_BROADCAST_VOLUME info;
180
181     info.dbcv_size       = sizeof(info);
182     info.dbcv_devicetype = DBT_DEVTYP_VOLUME;
183     info.dbcv_reserved   = 0;
184     info.dbcv_unitmask   = 1 << drive;
185     info.dbcv_flags      = DBTF_MEDIA;
186     BroadcastSystemMessageW( BSF_FORCEIFHUNG|BSF_QUERY, NULL,
187                              WM_DEVICECHANGE, code, (LPARAM)&info );
188 }
189
190 /* create the disk device for a given volume */
191 static NTSTATUS create_disk_device( enum device_type type, struct disk_device **device_ret )
192 {
193     static const WCHAR harddiskvolW[] = {'\\','D','e','v','i','c','e',
194                                          '\\','H','a','r','d','d','i','s','k','V','o','l','u','m','e','%','u',0};
195     static const WCHAR harddiskW[] = {'\\','D','e','v','i','c','e','\\','H','a','r','d','d','i','s','k','%','u',0};
196     static const WCHAR cdromW[] = {'\\','D','e','v','i','c','e','\\','C','d','R','o','m','%','u',0};
197     static const WCHAR floppyW[] = {'\\','D','e','v','i','c','e','\\','F','l','o','p','p','y','%','u',0};
198     static const WCHAR ramdiskW[] = {'\\','D','e','v','i','c','e','\\','R','a','m','d','i','s','k','%','u',0};
199     static const WCHAR cdromlinkW[] = {'\\','?','?','\\','C','d','R','o','m','%','u',0};
200     static const WCHAR physdriveW[] = {'\\','?','?','\\','P','h','y','s','i','c','a','l','D','r','i','v','e','%','u',0};
201
202     UINT i, first = 0;
203     NTSTATUS status = 0;
204     const WCHAR *format = NULL;
205     const WCHAR *link_format = NULL;
206     UNICODE_STRING name;
207     DEVICE_OBJECT *dev_obj;
208     struct disk_device *device;
209
210     switch(type)
211     {
212     case DEVICE_UNKNOWN:
213     case DEVICE_HARDDISK:
214     case DEVICE_NETWORK:  /* FIXME */
215         format = harddiskW;
216         link_format = physdriveW;
217         break;
218     case DEVICE_HARDDISK_VOL:
219         format = harddiskvolW;
220         first = 1;  /* harddisk volumes start counting from 1 */
221         break;
222     case DEVICE_FLOPPY:
223         format = floppyW;
224         break;
225     case DEVICE_CDROM:
226     case DEVICE_DVD:
227         format = cdromW;
228         link_format = cdromlinkW;
229         break;
230     case DEVICE_RAMDISK:
231         format = ramdiskW;
232         break;
233     }
234
235     name.MaximumLength = (strlenW(format) + 10) * sizeof(WCHAR);
236     name.Buffer = RtlAllocateHeap( GetProcessHeap(), 0, name.MaximumLength );
237     for (i = first; i < 32; i++)
238     {
239         sprintfW( name.Buffer, format, i );
240         name.Length = strlenW(name.Buffer) * sizeof(WCHAR);
241         status = IoCreateDevice( harddisk_driver, sizeof(*device), &name, 0, 0, FALSE, &dev_obj );
242         if (status != STATUS_OBJECT_NAME_COLLISION) break;
243     }
244     if (!status)
245     {
246         device = dev_obj->DeviceExtension;
247         device->dev_obj        = dev_obj;
248         device->name           = name;
249         device->type           = type;
250         device->unix_device    = NULL;
251         device->unix_mount     = NULL;
252         device->symlink.Buffer = NULL;
253
254         if (link_format)
255         {
256             UNICODE_STRING symlink;
257
258             symlink.MaximumLength = (strlenW(link_format) + 10) * sizeof(WCHAR);
259             if ((symlink.Buffer = RtlAllocateHeap( GetProcessHeap(), 0, symlink.MaximumLength)))
260             {
261                 sprintfW( symlink.Buffer, link_format, i );
262                 symlink.Length = strlenW(symlink.Buffer) * sizeof(WCHAR);
263                 if (!IoCreateSymbolicLink( &symlink, &name )) device->symlink = symlink;
264             }
265         }
266
267         switch (type)
268         {
269         case DEVICE_FLOPPY:
270         case DEVICE_RAMDISK:
271             device->devnum.DeviceType = FILE_DEVICE_DISK;
272             device->devnum.DeviceNumber = i;
273             device->devnum.PartitionNumber = ~0u;
274             break;
275         case DEVICE_CDROM:
276             device->devnum.DeviceType = FILE_DEVICE_CD_ROM;
277             device->devnum.DeviceNumber = i;
278             device->devnum.PartitionNumber = ~0u;
279             break;
280         case DEVICE_DVD:
281             device->devnum.DeviceType = FILE_DEVICE_DVD;
282             device->devnum.DeviceNumber = i;
283             device->devnum.PartitionNumber = ~0u;
284             break;
285         case DEVICE_UNKNOWN:
286         case DEVICE_HARDDISK:
287         case DEVICE_NETWORK:  /* FIXME */
288             device->devnum.DeviceType = FILE_DEVICE_DISK;
289             device->devnum.DeviceNumber = i;
290             device->devnum.PartitionNumber = 0;
291             break;
292         case DEVICE_HARDDISK_VOL:
293             device->devnum.DeviceType = FILE_DEVICE_DISK;
294             device->devnum.DeviceNumber = 0;
295             device->devnum.PartitionNumber = i;
296             break;
297         }
298         *device_ret = device;
299         TRACE( "created device %s\n", debugstr_w(name.Buffer) );
300     }
301     else
302     {
303         FIXME( "IoCreateDevice %s got %x\n", debugstr_w(name.Buffer), status );
304         RtlFreeUnicodeString( &name );
305     }
306     return status;
307 }
308
309 /* delete the disk device for a given drive */
310 static void delete_disk_device( struct disk_device *device )
311 {
312     TRACE( "deleting device %s\n", debugstr_w(device->name.Buffer) );
313     if (device->symlink.Buffer)
314     {
315         IoDeleteSymbolicLink( &device->symlink );
316         RtlFreeUnicodeString( &device->symlink );
317     }
318     RtlFreeHeap( GetProcessHeap(), 0, device->unix_device );
319     RtlFreeHeap( GetProcessHeap(), 0, device->unix_mount );
320     RtlFreeUnicodeString( &device->name );
321     IoDeleteDevice( device->dev_obj );
322 }
323
324 /* grab another reference to a volume */
325 static struct volume *grab_volume( struct volume *volume )
326 {
327     volume->ref++;
328     return volume;
329 }
330
331 /* release a volume and delete the corresponding disk device when refcount is 0 */
332 static unsigned int release_volume( struct volume *volume )
333 {
334     unsigned int ret = --volume->ref;
335
336     if (!ret)
337     {
338         TRACE( "%s udi %s\n", debugstr_guid(&volume->guid), debugstr_a(volume->udi) );
339         assert( !volume->udi );
340         list_remove( &volume->entry );
341         if (volume->mount) delete_mount_point( volume->mount );
342         delete_disk_device( volume->device );
343         RtlFreeHeap( GetProcessHeap(), 0, volume );
344     }
345     return ret;
346 }
347
348 /* set the volume udi */
349 static void set_volume_udi( struct volume *volume, const char *udi )
350 {
351     if (udi)
352     {
353         assert( !volume->udi );
354         /* having a udi means the HAL side holds an extra reference */
355         if ((volume->udi = strdupA( udi ))) grab_volume( volume );
356     }
357     else if (volume->udi)
358     {
359         RtlFreeHeap( GetProcessHeap(), 0, volume->udi );
360         volume->udi = NULL;
361         release_volume( volume );
362     }
363 }
364
365 /* create a disk volume */
366 static NTSTATUS create_volume( const char *udi, enum device_type type, struct volume **volume_ret )
367 {
368     struct volume *volume;
369     NTSTATUS status;
370
371     if (!(volume = RtlAllocateHeap( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*volume) )))
372         return STATUS_NO_MEMORY;
373
374     if (!(status = create_disk_device( type, &volume->device )))
375     {
376         if (udi) set_volume_udi( volume, udi );
377         list_add_tail( &volumes_list, &volume->entry );
378         *volume_ret = grab_volume( volume );
379     }
380     else RtlFreeHeap( GetProcessHeap(), 0, volume );
381
382     return status;
383 }
384
385 /* create the disk device for a given volume */
386 static NTSTATUS create_dos_device( struct volume *volume, const char *udi, int letter,
387                                    enum device_type type, struct dos_drive **drive_ret )
388 {
389     struct dos_drive *drive;
390     NTSTATUS status;
391
392     if (!(drive = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*drive) ))) return STATUS_NO_MEMORY;
393     drive->drive = letter;
394     drive->mount = NULL;
395
396     if (volume)
397     {
398         if (udi) set_volume_udi( volume, udi );
399         drive->volume = grab_volume( volume );
400         status = STATUS_SUCCESS;
401     }
402     else status = create_volume( udi, type, &drive->volume );
403
404     if (status == STATUS_SUCCESS)
405     {
406         list_add_tail( &drives_list, &drive->entry );
407         *drive_ret = drive;
408     }
409     else RtlFreeHeap( GetProcessHeap(), 0, drive );
410
411     return status;
412 }
413
414 /* delete the disk device for a given drive */
415 static void delete_dos_device( struct dos_drive *drive )
416 {
417     list_remove( &drive->entry );
418     if (drive->mount) delete_mount_point( drive->mount );
419     release_volume( drive->volume );
420     RtlFreeHeap( GetProcessHeap(), 0, drive );
421 }
422
423 /* find a volume that matches the parameters */
424 static struct volume *find_matching_volume( const char *udi, const char *device,
425                                             const char *mount_point, enum device_type type )
426 {
427     struct volume *volume;
428     struct disk_device *disk_device;
429
430     LIST_FOR_EACH_ENTRY( volume, &volumes_list, struct volume, entry )
431     {
432         int match = 0;
433
434         /* when we have a udi we only match drives added manually */
435         if (udi && volume->udi) continue;
436         /* and when we don't have a udi we only match dynamic drives */
437         if (!udi && !volume->udi) continue;
438
439         disk_device = volume->device;
440         if (disk_device->type != type) continue;
441         if (device && disk_device->unix_device)
442         {
443             if (strcmp( device, disk_device->unix_device )) continue;
444             match++;
445         }
446         if (mount_point && disk_device->unix_mount)
447         {
448             if (strcmp( mount_point, disk_device->unix_mount )) continue;
449             match++;
450         }
451         if (!match) continue;
452         TRACE( "found matching volume %s for device %s mount %s type %u\n",
453                debugstr_guid(&volume->guid), debugstr_a(device), debugstr_a(mount_point), type );
454         return grab_volume( volume );
455     }
456     return NULL;
457 }
458
459 /* change the information for an existing volume */
460 static NTSTATUS set_volume_info( struct volume *volume, struct dos_drive *drive, const char *device,
461                                  const char *mount_point, enum device_type type, const GUID *guid )
462 {
463     void *id = NULL;
464     unsigned int id_len = 0;
465     struct disk_device *disk_device = volume->device;
466     NTSTATUS status;
467
468     if (type != disk_device->type)
469     {
470         if ((status = create_disk_device( type, &disk_device ))) return status;
471         if (volume->mount)
472         {
473             delete_mount_point( volume->mount );
474             volume->mount = NULL;
475         }
476         if (drive && drive->mount)
477         {
478             delete_mount_point( drive->mount );
479             drive->mount = NULL;
480         }
481         delete_disk_device( volume->device );
482         volume->device = disk_device;
483     }
484     else
485     {
486         RtlFreeHeap( GetProcessHeap(), 0, disk_device->unix_device );
487         RtlFreeHeap( GetProcessHeap(), 0, disk_device->unix_mount );
488     }
489     disk_device->unix_device = strdupA( device );
490     disk_device->unix_mount = strdupA( mount_point );
491
492     if (guid && memcmp( &volume->guid, guid, sizeof(volume->guid) ))
493     {
494         volume->guid = *guid;
495         if (volume->mount)
496         {
497             delete_mount_point( volume->mount );
498             volume->mount = NULL;
499         }
500     }
501
502     if (!volume->mount)
503         volume->mount = add_volume_mount_point( disk_device->dev_obj, &disk_device->name, &volume->guid );
504     if (drive && !drive->mount)
505         drive->mount = add_dosdev_mount_point( disk_device->dev_obj, &disk_device->name, drive->drive );
506
507     if (disk_device->unix_mount)
508     {
509         id = disk_device->unix_mount;
510         id_len = strlen( disk_device->unix_mount ) + 1;
511     }
512     if (volume->mount) set_mount_point_id( volume->mount, id, id_len );
513     if (drive && drive->mount) set_mount_point_id( drive->mount, id, id_len );
514
515     return STATUS_SUCCESS;
516 }
517
518 /* change the drive letter or volume for an existing drive */
519 static void set_drive_info( struct dos_drive *drive, int letter, struct volume *volume )
520 {
521     if (drive->drive != letter)
522     {
523         if (drive->mount) delete_mount_point( drive->mount );
524         drive->mount = NULL;
525         drive->drive = letter;
526     }
527     if (drive->volume != volume)
528     {
529         if (drive->mount) delete_mount_point( drive->mount );
530         drive->mount = NULL;
531         grab_volume( volume );
532         release_volume( drive->volume );
533         drive->volume = volume;
534     }
535 }
536
537 static inline int is_valid_device( struct stat *st )
538 {
539 #if defined(linux) || defined(__sun__)
540     return S_ISBLK( st->st_mode );
541 #else
542     /* disks are char devices on *BSD */
543     return S_ISCHR( st->st_mode );
544 #endif
545 }
546
547 /* find or create a DOS drive for the corresponding device */
548 static int add_drive( const char *device, enum device_type type )
549 {
550     char *path, *p;
551     char in_use[26];
552     struct stat dev_st, drive_st;
553     int drive, first, last, avail = 0;
554
555     if (stat( device, &dev_st ) == -1 || !is_valid_device( &dev_st )) return -1;
556
557     if (!(path = get_dosdevices_path( &p ))) return -1;
558
559     memset( in_use, 0, sizeof(in_use) );
560
561     switch (type)
562     {
563     case DEVICE_FLOPPY:
564         first = 0;
565         last = 2;
566         break;
567     case DEVICE_CDROM:
568     case DEVICE_DVD:
569         first = 3;
570         last = 26;
571         break;
572     default:
573         first = 2;
574         last = 26;
575         break;
576     }
577
578     while (avail != -1)
579     {
580         avail = -1;
581         for (drive = first; drive < last; drive++)
582         {
583             if (in_use[drive]) continue;  /* already checked */
584             *p = 'a' + drive;
585             if (stat( path, &drive_st ) == -1)
586             {
587                 if (lstat( path, &drive_st ) == -1 && errno == ENOENT)  /* this is a candidate */
588                 {
589                     if (avail == -1)
590                     {
591                         p[2] = 0;
592                         /* if mount point symlink doesn't exist either, it's available */
593                         if (lstat( path, &drive_st ) == -1 && errno == ENOENT) avail = drive;
594                         p[2] = ':';
595                     }
596                 }
597                 else in_use[drive] = 1;
598             }
599             else
600             {
601                 in_use[drive] = 1;
602                 if (!is_valid_device( &drive_st )) continue;
603                 if (dev_st.st_rdev == drive_st.st_rdev) goto done;
604             }
605         }
606         if (avail != -1)
607         {
608             /* try to use the one we found */
609             drive = avail;
610             *p = 'a' + drive;
611             if (symlink( device, path ) != -1) goto done;
612             /* failed, retry the search */
613         }
614     }
615     drive = -1;
616
617 done:
618     HeapFree( GetProcessHeap(), 0, path );
619     return drive;
620 }
621
622 /* create devices for mapped drives */
623 static void create_drive_devices(void)
624 {
625     char *path, *p, *link, *device;
626     struct dos_drive *drive;
627     struct volume *volume;
628     unsigned int i;
629     HKEY drives_key;
630     enum device_type drive_type;
631     WCHAR driveW[] = {'a',':',0};
632
633     if (!(path = get_dosdevices_path( &p ))) return;
634     if (RegOpenKeyW( HKEY_LOCAL_MACHINE, drives_keyW, &drives_key )) drives_key = 0;
635
636     for (i = 0; i < MAX_DOS_DRIVES; i++)
637     {
638         p[0] = 'a' + i;
639         p[2] = 0;
640         if (!(link = read_symlink( path ))) continue;
641         p[2] = ':';
642         device = read_symlink( path );
643
644         drive_type = i < 2 ? DEVICE_FLOPPY : DEVICE_HARDDISK_VOL;
645         if (drives_key)
646         {
647             WCHAR buffer[32];
648             DWORD j, type, size = sizeof(buffer);
649
650             driveW[0] = 'a' + i;
651             if (!RegQueryValueExW( drives_key, driveW, NULL, &type, (BYTE *)buffer, &size ) &&
652                 type == REG_SZ)
653             {
654                 for (j = 0; j < sizeof(drive_types)/sizeof(drive_types[0]); j++)
655                     if (drive_types[j][0] && !strcmpiW( buffer, drive_types[j] ))
656                     {
657                         drive_type = j;
658                         break;
659                     }
660                 if (drive_type == DEVICE_FLOPPY && i >= 2) drive_type = DEVICE_HARDDISK;
661             }
662         }
663
664         volume = find_matching_volume( NULL, device, link, drive_type );
665         if (!create_dos_device( volume, NULL, i, drive_type, &drive ))
666         {
667             /* don't reset uuid if we used an existing volume */
668             const GUID *guid = volume ? NULL : get_default_uuid(i);
669             set_volume_info( drive->volume, drive, device, link, drive_type, guid );
670         }
671         else
672         {
673             RtlFreeHeap( GetProcessHeap(), 0, link );
674             RtlFreeHeap( GetProcessHeap(), 0, device );
675         }
676         if (volume) release_volume( volume );
677     }
678     RegCloseKey( drives_key );
679     RtlFreeHeap( GetProcessHeap(), 0, path );
680 }
681
682 /* create a new disk volume */
683 NTSTATUS add_volume( const char *udi, const char *device, const char *mount_point,
684                      enum device_type type, const GUID *guid )
685 {
686     struct volume *volume;
687     NTSTATUS status = STATUS_SUCCESS;
688
689     TRACE( "adding %s device %s mount %s type %u uuid %s\n", debugstr_a(udi),
690            debugstr_a(device), debugstr_a(mount_point), type, debugstr_guid(guid) );
691
692     EnterCriticalSection( &device_section );
693     LIST_FOR_EACH_ENTRY( volume, &volumes_list, struct volume, entry )
694         if (volume->udi && !strcmp( udi, volume->udi ))
695         {
696             grab_volume( volume );
697             goto found;
698         }
699
700     /* udi not found, search for a non-dynamic volume */
701     if ((volume = find_matching_volume( udi, device, mount_point, type ))) set_volume_udi( volume, udi );
702     else status = create_volume( udi, type, &volume );
703
704 found:
705     if (!status) status = set_volume_info( volume, NULL, device, mount_point, type, guid );
706     if (volume) release_volume( volume );
707     LeaveCriticalSection( &device_section );
708     return status;
709 }
710
711 /* create a new disk volume */
712 NTSTATUS remove_volume( const char *udi )
713 {
714     NTSTATUS status = STATUS_NO_SUCH_DEVICE;
715     struct volume *volume;
716
717     EnterCriticalSection( &device_section );
718     LIST_FOR_EACH_ENTRY( volume, &volumes_list, struct volume, entry )
719     {
720         if (!volume->udi || strcmp( udi, volume->udi )) continue;
721         set_volume_udi( volume, NULL );
722         status = STATUS_SUCCESS;
723         break;
724     }
725     LeaveCriticalSection( &device_section );
726     return status;
727 }
728
729
730 /* create a new dos drive */
731 NTSTATUS add_dos_device( int letter, const char *udi, const char *device,
732                          const char *mount_point, enum device_type type, const GUID *guid )
733 {
734     char *path, *p;
735     HKEY hkey;
736     NTSTATUS status = STATUS_SUCCESS;
737     struct dos_drive *drive, *next;
738     struct volume *volume;
739     int notify = -1;
740
741     if (!(path = get_dosdevices_path( &p ))) return STATUS_NO_MEMORY;
742
743     EnterCriticalSection( &device_section );
744     volume = find_matching_volume( udi, device, mount_point, type );
745
746     if (letter == -1)  /* auto-assign a letter */
747     {
748         letter = add_drive( device, type );
749         if (letter == -1)
750         {
751             status = STATUS_OBJECT_NAME_COLLISION;
752             goto done;
753         }
754
755         LIST_FOR_EACH_ENTRY_SAFE( drive, next, &drives_list, struct dos_drive, entry )
756         {
757             if (drive->volume->udi && !strcmp( udi, drive->volume->udi )) goto found;
758             if (drive->drive == letter) delete_dos_device( drive );
759         }
760     }
761     else  /* simply reset the device symlink */
762     {
763         LIST_FOR_EACH_ENTRY( drive, &drives_list, struct dos_drive, entry )
764             if (drive->drive == letter) break;
765
766         *p = 'a' + letter;
767         if (&drive->entry == &drives_list) update_symlink( path, device, NULL );
768         else
769         {
770             update_symlink( path, device, drive->volume->device->unix_device );
771             delete_dos_device( drive );
772         }
773     }
774
775     if ((status = create_dos_device( volume, udi, letter, type, &drive ))) goto done;
776
777 found:
778     if (!guid && !volume) guid = get_default_uuid( letter );
779     if (!volume) volume = grab_volume( drive->volume );
780     set_drive_info( drive, letter, volume );
781     p[0] = 'a' + drive->drive;
782     p[2] = 0;
783     update_symlink( path, mount_point, volume->device->unix_mount );
784     set_volume_info( volume, drive, device, mount_point, type, guid );
785
786     TRACE( "added device %c: udi %s for %s on %s type %u\n",
787            'a' + drive->drive, wine_dbgstr_a(udi), wine_dbgstr_a(device),
788            wine_dbgstr_a(mount_point), type );
789
790     /* hack: force the drive type in the registry */
791     if (!RegCreateKeyW( HKEY_LOCAL_MACHINE, drives_keyW, &hkey ))
792     {
793         const WCHAR *type_name = drive_types[type];
794         WCHAR name[] = {'a',':',0};
795
796         name[0] += drive->drive;
797         if (!type_name[0] && type == DEVICE_HARDDISK) type_name = drive_types[DEVICE_FLOPPY];
798         if (type_name[0])
799             RegSetValueExW( hkey, name, 0, REG_SZ, (const BYTE *)type_name,
800                             (strlenW(type_name) + 1) * sizeof(WCHAR) );
801         else
802             RegDeleteValueW( hkey, name );
803         RegCloseKey( hkey );
804     }
805
806     if (udi) notify = drive->drive;
807
808 done:
809     if (volume) release_volume( volume );
810     LeaveCriticalSection( &device_section );
811     RtlFreeHeap( GetProcessHeap(), 0, path );
812     if (notify != -1) send_notify( notify, DBT_DEVICEARRIVAL );
813     return status;
814 }
815
816 /* remove an existing dos drive, by letter or udi */
817 NTSTATUS remove_dos_device( int letter, const char *udi )
818 {
819     NTSTATUS status = STATUS_NO_SUCH_DEVICE;
820     HKEY hkey;
821     struct dos_drive *drive;
822     char *path, *p;
823     int notify = -1;
824
825     EnterCriticalSection( &device_section );
826     LIST_FOR_EACH_ENTRY( drive, &drives_list, struct dos_drive, entry )
827     {
828         if (udi)
829         {
830             if (!drive->volume->udi) continue;
831             if (strcmp( udi, drive->volume->udi )) continue;
832             set_volume_udi( drive->volume, NULL );
833         }
834         else if (drive->drive != letter) continue;
835
836         if ((path = get_dosdevices_path( &p )))
837         {
838             p[0] = 'a' + drive->drive;
839             p[2] = 0;
840             unlink( path );
841             RtlFreeHeap( GetProcessHeap(), 0, path );
842         }
843
844         /* clear the registry key too */
845         if (!RegOpenKeyW( HKEY_LOCAL_MACHINE, drives_keyW, &hkey ))
846         {
847             WCHAR name[] = {'a',':',0};
848             name[0] += drive->drive;
849             RegDeleteValueW( hkey, name );
850             RegCloseKey( hkey );
851         }
852
853         if (udi && drive->volume->device->unix_mount) notify = drive->drive;
854
855         delete_dos_device( drive );
856         status = STATUS_SUCCESS;
857         break;
858     }
859     LeaveCriticalSection( &device_section );
860     if (notify != -1) send_notify( notify, DBT_DEVICEREMOVECOMPLETE );
861     return status;
862 }
863
864 /* query information about an existing dos drive, by letter or udi */
865 NTSTATUS query_dos_device( int letter, enum device_type *type, char **device, char **mount_point )
866 {
867     NTSTATUS status = STATUS_NO_SUCH_DEVICE;
868     struct dos_drive *drive;
869     struct disk_device *disk_device;
870
871     EnterCriticalSection( &device_section );
872     LIST_FOR_EACH_ENTRY( drive, &drives_list, struct dos_drive, entry )
873     {
874         if (drive->drive != letter) continue;
875         disk_device = drive->volume->device;
876         if (type) *type = disk_device->type;
877         if (device) *device = strdupA( disk_device->unix_device );
878         if (mount_point) *mount_point = strdupA( disk_device->unix_mount );
879         status = STATUS_SUCCESS;
880         break;
881     }
882     LeaveCriticalSection( &device_section );
883     return status;
884 }
885
886 /* handler for ioctls on the harddisk device */
887 static NTSTATUS WINAPI harddisk_ioctl( DEVICE_OBJECT *device, IRP *irp )
888 {
889     IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp );
890     struct disk_device *dev = device->DeviceExtension;
891
892     TRACE( "ioctl %x insize %u outsize %u\n",
893            irpsp->Parameters.DeviceIoControl.IoControlCode,
894            irpsp->Parameters.DeviceIoControl.InputBufferLength,
895            irpsp->Parameters.DeviceIoControl.OutputBufferLength );
896
897     EnterCriticalSection( &device_section );
898
899     switch(irpsp->Parameters.DeviceIoControl.IoControlCode)
900     {
901     case IOCTL_DISK_GET_DRIVE_GEOMETRY:
902     {
903         DISK_GEOMETRY info;
904         DWORD len = min( sizeof(info), irpsp->Parameters.DeviceIoControl.OutputBufferLength );
905
906         info.Cylinders.QuadPart = 10000;
907         info.MediaType = (dev->devnum.DeviceType == FILE_DEVICE_DISK) ? FixedMedia : RemovableMedia;
908         info.TracksPerCylinder = 255;
909         info.SectorsPerTrack = 63;
910         info.BytesPerSector = 512;
911         memcpy( irp->AssociatedIrp.SystemBuffer, &info, len );
912         irp->IoStatus.Information = len;
913         irp->IoStatus.u.Status = STATUS_SUCCESS;
914         break;
915     }
916     case IOCTL_DISK_GET_DRIVE_GEOMETRY_EX:
917     {
918         DISK_GEOMETRY_EX info;
919         DWORD len = min( sizeof(info), irpsp->Parameters.DeviceIoControl.OutputBufferLength );
920
921         FIXME("The DISK_PARTITION_INFO and DISK_DETECTION_INFO structures will not be filled\n");
922
923         info.Geometry.Cylinders.QuadPart = 10000;
924         info.Geometry.MediaType = (dev->devnum.DeviceType == FILE_DEVICE_DISK) ? FixedMedia : RemovableMedia;
925         info.Geometry.TracksPerCylinder = 255;
926         info.Geometry.SectorsPerTrack = 63;
927         info.Geometry.BytesPerSector = 512;
928         info.DiskSize.QuadPart = info.Geometry.Cylinders.QuadPart * info.Geometry.TracksPerCylinder *
929                                  info.Geometry.SectorsPerTrack * info.Geometry.BytesPerSector;
930         info.Data[0]  = 0;
931         memcpy( irp->AssociatedIrp.SystemBuffer, &info, len );
932         irp->IoStatus.Information = len;
933         irp->IoStatus.u.Status = STATUS_SUCCESS;
934         break;
935     }
936     case IOCTL_STORAGE_GET_DEVICE_NUMBER:
937     {
938         DWORD len = min( sizeof(dev->devnum), irpsp->Parameters.DeviceIoControl.OutputBufferLength );
939
940         memcpy( irp->AssociatedIrp.SystemBuffer, &dev->devnum, len );
941         irp->IoStatus.Information = len;
942         irp->IoStatus.u.Status = STATUS_SUCCESS;
943         break;
944     }
945     case IOCTL_CDROM_READ_TOC:
946         irp->IoStatus.u.Status = STATUS_INVALID_DEVICE_REQUEST;
947         break;
948     case IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS:
949     {
950         DWORD len = min( 32, irpsp->Parameters.DeviceIoControl.OutputBufferLength );
951
952         FIXME( "returning zero-filled buffer for IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS\n" );
953         memset( irp->AssociatedIrp.SystemBuffer, 0, len );
954         irp->IoStatus.Information = len;
955         irp->IoStatus.u.Status = STATUS_SUCCESS;
956         break;
957     }
958     default:
959     {
960         ULONG code = irpsp->Parameters.DeviceIoControl.IoControlCode;
961         FIXME("Unsupported ioctl %x (device=%x access=%x func=%x method=%x)\n",
962               code, code >> 16, (code >> 14) & 3, (code >> 2) & 0xfff, code & 3);
963         irp->IoStatus.u.Status = STATUS_NOT_SUPPORTED;
964         break;
965     }
966     }
967
968     LeaveCriticalSection( &device_section );
969     IoCompleteRequest( irp, IO_NO_INCREMENT );
970     return irp->IoStatus.u.Status;
971 }
972
973 /* driver entry point for the harddisk driver */
974 NTSTATUS WINAPI harddisk_driver_entry( DRIVER_OBJECT *driver, UNICODE_STRING *path )
975 {
976     struct disk_device *device;
977
978     harddisk_driver = driver;
979     driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = harddisk_ioctl;
980
981     /* create a harddisk0 device that isn't assigned to any drive */
982     create_disk_device( DEVICE_HARDDISK, &device );
983
984     create_drive_devices();
985
986     return STATUS_SUCCESS;
987 }