Added/fixed some documentation reported by winapi_check.
[wine] / files / drive.c
1 /*
2  * DOS drives handling functions
3  *
4  * Copyright 1993 Erik Bos
5  * Copyright 1996 Alexandre Julliard
6  *
7  * Label & serial number read support.
8  *  (c) 1999 Petr Tomasek <tomasek@etf.cuni.cz>
9  *  (c) 2000 Andreas Mohr (changes)
10  *
11  */
12
13 #include "config.h"
14
15 #include <assert.h>
16 #include <ctype.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <fcntl.h>
22 #include <errno.h>
23 #include <unistd.h>
24
25 #ifdef HAVE_SYS_PARAM_H
26 # include <sys/param.h>
27 #endif
28 #ifdef STATFS_DEFINED_BY_SYS_VFS
29 # include <sys/vfs.h>
30 #else
31 # ifdef STATFS_DEFINED_BY_SYS_MOUNT
32 #  include <sys/mount.h>
33 # else
34 #  ifdef STATFS_DEFINED_BY_SYS_STATFS
35 #   include <sys/statfs.h>
36 #  endif
37 # endif
38 #endif
39
40 #include "winbase.h"
41 #include "wine/winbase16.h"   /* for GetCurrentTask */
42 #include "wine/winestring.h"  /* for lstrcpyAtoW */
43 #include "winerror.h"
44 #include "drive.h"
45 #include "cdrom.h"
46 #include "file.h"
47 #include "heap.h"
48 #include "msdos.h"
49 #include "options.h"
50 #include "wine/port.h"
51 #include "task.h"
52 #include "debugtools.h"
53
54 DEFAULT_DEBUG_CHANNEL(dosfs)
55 DECLARE_DEBUG_CHANNEL(file)
56
57 typedef struct
58 {
59     char     *root;      /* root dir in Unix format without trailing / */
60     char     *dos_cwd;   /* cwd in DOS format without leading or trailing \ */
61     char     *unix_cwd;  /* cwd in Unix format without leading or trailing / */
62     char     *device;    /* raw device path */
63     BOOL      read_volinfo; /* read the volume info from the device ? */
64     char      label_conf[12]; /* drive label as cfg'd in wine.conf */
65     char      label_read[12]; /* drive label as read from device */
66     DWORD     serial_conf;    /* drive serial number as cfg'd in wine.conf */
67     DRIVETYPE type;      /* drive type */
68     UINT      flags;     /* drive flags */
69     dev_t     dev;       /* unix device number */
70     ino_t     ino;       /* unix inode number */
71 } DOSDRIVE;
72
73
74 static const char * const DRIVE_Types[] =
75 {
76     "floppy",   /* TYPE_FLOPPY */
77     "hd",       /* TYPE_HD */
78     "cdrom",    /* TYPE_CDROM */
79     "network"   /* TYPE_NETWORK */
80 };
81
82
83 /* Known filesystem types */
84
85 typedef struct
86 {
87     const char *name;
88     UINT      flags;
89 } FS_DESCR;
90
91 static const FS_DESCR DRIVE_Filesystems[] =
92 {
93     { "unix",   DRIVE_CASE_SENSITIVE | DRIVE_CASE_PRESERVING },
94     { "msdos",  DRIVE_SHORT_NAMES },
95     { "dos",    DRIVE_SHORT_NAMES },
96     { "fat",    DRIVE_SHORT_NAMES },
97     { "vfat",   DRIVE_CASE_PRESERVING },
98     { "win95",  DRIVE_CASE_PRESERVING },
99     { NULL, 0 }
100 };
101
102
103 static DOSDRIVE DOSDrives[MAX_DOS_DRIVES];
104 static int DRIVE_CurDrive = -1;
105
106 static HTASK16 DRIVE_LastTask = 0;
107
108
109 /***********************************************************************
110  *           DRIVE_GetDriveType
111  */
112 static DRIVETYPE DRIVE_GetDriveType( const char *name )
113 {
114     char buffer[20];
115     int i;
116
117     PROFILE_GetWineIniString( name, "Type", "hd", buffer, sizeof(buffer) );
118     for (i = 0; i < sizeof(DRIVE_Types)/sizeof(DRIVE_Types[0]); i++)
119     {
120         if (!strcasecmp( buffer, DRIVE_Types[i] )) return (DRIVETYPE)i;
121     }
122     MESSAGE("%s: unknown drive type '%s', defaulting to 'hd'.\n",
123         name, buffer );
124     return TYPE_HD;
125 }
126
127
128 /***********************************************************************
129  *           DRIVE_GetFSFlags
130  */
131 static UINT DRIVE_GetFSFlags( const char *name, const char *value )
132 {
133     const FS_DESCR *descr;
134
135     for (descr = DRIVE_Filesystems; descr->name; descr++)
136         if (!strcasecmp( value, descr->name )) return descr->flags;
137     MESSAGE("%s: unknown filesystem type '%s', defaulting to 'win95'.\n",
138         name, value );
139     return DRIVE_CASE_PRESERVING;
140 }
141
142
143 /***********************************************************************
144  *           DRIVE_Init
145  */
146 int DRIVE_Init(void)
147 {
148     int i, len, count = 0;
149     char name[] = "Drive A";
150     char path[MAX_PATHNAME_LEN];
151     char buffer[80];
152     struct stat drive_stat_buffer;
153     char *p;
154     DOSDRIVE *drive;
155
156     for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, name[6]++, drive++)
157     {
158         PROFILE_GetWineIniString( name, "Path", "", path, sizeof(path)-1 );
159         if (path[0])
160         {
161             p = path + strlen(path) - 1;
162             while ((p > path) && ((*p == '/') || (*p == '\\'))) *p-- = '\0';
163             if (!path[0]) strcpy( path, "/" );
164
165             if (stat( path, &drive_stat_buffer ))
166             {
167                 MESSAGE("Could not stat %s, ignoring drive %c:\n", path, 'A' + i );
168                 continue;
169             }
170             if (!S_ISDIR(drive_stat_buffer.st_mode))
171             {
172                 MESSAGE("%s is not a directory, ignoring drive %c:\n",
173                     path, 'A' + i );
174                 continue;
175             }
176
177             drive->root = HEAP_strdupA( GetProcessHeap(), 0, path );
178             drive->dos_cwd  = HEAP_strdupA( GetProcessHeap(), 0, "" );
179             drive->unix_cwd = HEAP_strdupA( GetProcessHeap(), 0, "" );
180             drive->type     = DRIVE_GetDriveType( name );
181             drive->device   = NULL;
182             drive->flags    = 0;
183             drive->dev      = drive_stat_buffer.st_dev;
184             drive->ino      = drive_stat_buffer.st_ino;
185
186             /* Get the drive label */
187             PROFILE_GetWineIniString( name, "Label", name, drive->label_conf, 12 );
188             if ((len = strlen(drive->label_conf)) < 11)
189             {
190                 /* Pad label with spaces */
191                 memset( drive->label_conf + len, ' ', 11 - len );
192                 drive->label_conf[11] = '\0';
193             }
194
195             /* Get the serial number */
196             PROFILE_GetWineIniString( name, "Serial", "12345678",
197                                       buffer, sizeof(buffer) );
198             drive->serial_conf = strtoul( buffer, NULL, 16 );
199
200             /* Get the filesystem type */
201             PROFILE_GetWineIniString( name, "Filesystem", "win95",
202                                       buffer, sizeof(buffer) );
203             drive->flags = DRIVE_GetFSFlags( name, buffer );
204
205             /* Get the device */
206             PROFILE_GetWineIniString( name, "Device", "",
207                                       buffer, sizeof(buffer) );
208             if (buffer[0])
209             {
210                 drive->device = HEAP_strdupA( GetProcessHeap(), 0, buffer );
211                 drive->read_volinfo =
212                 (BOOL)PROFILE_GetWineIniInt( name, "ReadVolInfo", 1);
213             }
214             else
215                 drive->read_volinfo = FALSE;
216
217             /* Make the first hard disk the current drive */
218             if ((DRIVE_CurDrive == -1) && (drive->type == TYPE_HD))
219                 DRIVE_CurDrive = i;
220
221             count++;
222             TRACE("%s: path=%s type=%s label='%s' serial=%08lx "
223                   "flags=%08x dev=%x ino=%x\n",
224                   name, path, DRIVE_Types[drive->type],
225                   drive->label_conf, drive->serial_conf, drive->flags,
226                   (int)drive->dev, (int)drive->ino );
227         }
228         else WARN("%s: not defined\n", name );
229     }
230
231     if (!count) 
232     {
233         MESSAGE("Warning: no valid DOS drive found, check your configuration file.\n" );
234         /* Create a C drive pointing to Unix root dir */
235         DOSDrives[2].root     = HEAP_strdupA( GetProcessHeap(), 0, "/" );
236         DOSDrives[2].dos_cwd  = HEAP_strdupA( GetProcessHeap(), 0, "" );
237         DOSDrives[2].unix_cwd = HEAP_strdupA( GetProcessHeap(), 0, "" );
238         strcpy( DOSDrives[2].label_conf, "Drive C    " );
239         DOSDrives[2].serial_conf   = 12345678;
240         DOSDrives[2].type     = TYPE_HD;
241         DOSDrives[2].flags    = 0;
242         DRIVE_CurDrive = 2;
243     }
244
245     /* Make sure the current drive is valid */
246     if (DRIVE_CurDrive == -1)
247     {
248         for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
249         {
250             if (drive->root && !(drive->flags & DRIVE_DISABLED))
251             {
252                 DRIVE_CurDrive = i;
253                 break;
254             }
255         }
256     }
257
258     return 1;
259 }
260
261
262 /***********************************************************************
263  *           DRIVE_IsValid
264  */
265 int DRIVE_IsValid( int drive )
266 {
267     if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
268     return (DOSDrives[drive].root &&
269             !(DOSDrives[drive].flags & DRIVE_DISABLED));
270 }
271
272
273 /***********************************************************************
274  *           DRIVE_GetCurrentDrive
275  */
276 int DRIVE_GetCurrentDrive(void)
277 {
278     TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
279     if (pTask && (pTask->curdrive & 0x80)) return pTask->curdrive & ~0x80;
280     return DRIVE_CurDrive;
281 }
282
283
284 /***********************************************************************
285  *           DRIVE_SetCurrentDrive
286  */
287 int DRIVE_SetCurrentDrive( int drive )
288 {
289     TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
290     if (!DRIVE_IsValid( drive ))
291     {
292         SetLastError( ERROR_INVALID_DRIVE );
293         return 0;
294     }
295     TRACE("%c:\n", 'A' + drive );
296     DRIVE_CurDrive = drive;
297     if (pTask) pTask->curdrive = drive | 0x80;
298     return 1;
299 }
300
301
302 /***********************************************************************
303  *           DRIVE_FindDriveRoot
304  *
305  * Find a drive for which the root matches the beginning of the given path.
306  * This can be used to translate a Unix path into a drive + DOS path.
307  * Return value is the drive, or -1 on error. On success, path is modified
308  * to point to the beginning of the DOS path.
309  */
310 int DRIVE_FindDriveRoot( const char **path )
311 {
312     /* idea: check at all '/' positions.
313      * If the device and inode of that path is identical with the
314      * device and inode of the current drive then we found a solution.
315      * If there is another drive pointing to a deeper position in
316      * the file tree, we want to find that one, not the earlier solution.
317      */
318     int drive, rootdrive = -1;
319     char buffer[MAX_PATHNAME_LEN];
320     char *next = buffer;
321     const char *p = *path;
322     struct stat st;
323
324     strcpy( buffer, "/" );
325     for (;;)
326     {
327         if (stat( buffer, &st ) || !S_ISDIR( st.st_mode )) break;
328
329         /* Find the drive */
330
331         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
332         {
333            if (!DOSDrives[drive].root ||
334                (DOSDrives[drive].flags & DRIVE_DISABLED)) continue;
335
336            if ((DOSDrives[drive].dev == st.st_dev) &&
337                (DOSDrives[drive].ino == st.st_ino))
338            {
339                rootdrive = drive;
340                *path = p;
341                break;
342            }
343         }
344
345         /* Get the next path component */
346
347         *next++ = '/';
348         while ((*p == '/') || (*p == '\\')) p++;
349         if (!*p) break;
350         while (!IS_END_OF_NAME(*p)) *next++ = *p++;
351         *next = 0;
352     }
353     *next = 0;
354
355     if (rootdrive != -1)
356         TRACE("%s -> drive %c:, root='%s', name='%s'\n",
357               buffer, 'A' + rootdrive, DOSDrives[rootdrive].root, *path );
358     return rootdrive;
359 }
360
361
362 /***********************************************************************
363  *           DRIVE_GetRoot
364  */
365 const char * DRIVE_GetRoot( int drive )
366 {
367     if (!DRIVE_IsValid( drive )) return NULL;
368     return DOSDrives[drive].root;
369 }
370
371
372 /***********************************************************************
373  *           DRIVE_GetDosCwd
374  */
375 const char * DRIVE_GetDosCwd( int drive )
376 {
377     TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
378     if (!DRIVE_IsValid( drive )) return NULL;
379
380     /* Check if we need to change the directory to the new task. */
381     if (pTask && (pTask->curdrive & 0x80) &&    /* The task drive is valid */
382         ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
383         (DRIVE_LastTask != GetCurrentTask()))   /* and the task changed */
384     {
385         /* Perform the task-switch */
386         if (!DRIVE_Chdir( drive, pTask->curdir )) DRIVE_Chdir( drive, "\\" );
387         DRIVE_LastTask = GetCurrentTask();
388     }
389     return DOSDrives[drive].dos_cwd;
390 }
391
392
393 /***********************************************************************
394  *           DRIVE_GetUnixCwd
395  */
396 const char * DRIVE_GetUnixCwd( int drive )
397 {
398     TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
399     if (!DRIVE_IsValid( drive )) return NULL;
400
401     /* Check if we need to change the directory to the new task. */
402     if (pTask && (pTask->curdrive & 0x80) &&    /* The task drive is valid */
403         ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
404         (DRIVE_LastTask != GetCurrentTask()))   /* and the task changed */
405     {
406         /* Perform the task-switch */
407         if (!DRIVE_Chdir( drive, pTask->curdir )) DRIVE_Chdir( drive, "\\" );
408         DRIVE_LastTask = GetCurrentTask();
409     }
410     return DOSDrives[drive].unix_cwd;
411 }
412
413
414 /***********************************************************************
415  *           DRIVE_GetDevice
416  */
417 const char * DRIVE_GetDevice( int drive )
418 {
419     return (DRIVE_IsValid( drive )) ? DOSDrives[drive].device : NULL;
420 }
421
422
423 /***********************************************************************
424  *           DRIVE_ReadSuperblock
425  *
426  * NOTE 
427  *      DRIVE_SetLabel and DRIVE_SetSerialNumber use this in order
428  * to check, that they are writing on a FAT filesystem !
429  */
430 int DRIVE_ReadSuperblock (int drive, char * buff)
431 {
432 #define DRIVE_SUPER 96
433     int fd;
434     off_t offs;
435
436     if (memset(buff,0,DRIVE_SUPER)!=buff) return -1;
437     if ((fd=open(DOSDrives[drive].device,O_RDONLY)) == -1)
438     {
439         struct stat st;
440         if (!DOSDrives[drive].device)
441             ERR("No device configured for drive %c: !\n", 'A'+drive);
442         else
443             ERR("Couldn't open device '%s' for drive %c: ! (%s)\n", DOSDrives[drive].device, 'A'+drive,
444                  (stat(DOSDrives[drive].device, &st)) ?
445                         "not available or symlink not valid ?" : "no permission");
446         ERR("Can't read drive volume info ! Either pre-set it or make sure the device to read it from is accessible !\n");
447         PROFILE_UsageWineIni();
448         return -1;
449     }
450
451     switch(DOSDrives[drive].type)
452     {
453         case TYPE_FLOPPY:
454         case TYPE_HD:
455             offs = 0;
456             break;
457         case TYPE_CDROM:
458         /* FIXME: Maybe we should search for the first data track on the CD,
459                   not just assume that it is the first track */
460             offs = (off_t)2048*(16+0);
461             break;
462                 default:
463                     offs = 0;
464                     break;
465     }
466
467     if ((offs) && (lseek(fd,offs,SEEK_SET)!=offs)) return -4;
468     if (read(fd,buff,DRIVE_SUPER)!=DRIVE_SUPER) return -2;
469
470     switch(DOSDrives[drive].type)
471     {
472         case TYPE_FLOPPY:
473         case TYPE_HD:
474             if ((buff[0x26]!=0x29) ||  /* Check for FAT present */
475                 /* FIXME: do really all Fat have their name beginning with
476                    "FAT" ? (At least FAT12, FAT16 and FAT32 have :) */
477                 memcmp( buff+0x36,"FAT",3))
478             {
479                 ERR("The filesystem is not FAT !! (device=%s)\n",
480                     DOSDrives[drive].device);
481                 return -3;
482             }
483             break;
484         case TYPE_CDROM:
485             if (strncmp(&buff[1],"CD001",5)) /* Check for iso9660 present */
486                 return -3;
487             /* FIXME: do we need to check for "CDROM", too ? (high sierra) */
488                 break;
489         default:
490                 return -3;
491                 break;
492     }
493
494     return close(fd);
495 }
496
497
498 /***********************************************************************
499  *           DRIVE_WriteSuperblockEntry
500  *
501  * NOTE
502  *      We are writing as little as possible (ie. not the whole SuperBlock)
503  * not to interfere with kernel. The drive can be mounted !
504  */
505 int DRIVE_WriteSuperblockEntry (int drive, off_t ofs, size_t len, char * buff)
506 {
507     int fd;
508
509     if ((fd=open(DOSDrives[drive].device,O_WRONLY))==-1) 
510     {
511         ERR("Cannot open the device %s (for writing)\n",
512             DOSDrives[drive].device); 
513         return -1;
514     }
515     if (lseek(fd,ofs,SEEK_SET)!=ofs)
516     {
517         ERR("lseek failed on device %s !\n",
518             DOSDrives[drive].device); 
519         close(fd);
520         return -2;
521     }
522     if (write(fd,buff,len)!=len) 
523     {
524         close(fd);
525         ERR("Cannot write on %s !\n",
526             DOSDrives[drive].device); 
527         return -3;
528     }
529     return close (fd);
530 }
531
532
533
534 /***********************************************************************
535  *           DRIVE_GetLabel
536  */
537 const char * DRIVE_GetLabel( int drive )
538 {
539     int read = 0;
540     char buff[DRIVE_SUPER];
541     int offs = -1;
542
543     if (!DRIVE_IsValid( drive )) return NULL;
544     if (DRIVE_GetType(drive) == TYPE_CDROM)
545     {
546         WINE_CDAUDIO wcda;
547
548         if (!(CDROM_Open(&wcda, drive)))
549         {
550             int media = CDROM_GetMediaType(&wcda);
551
552             if (media == CDS_AUDIO)
553             {
554                 strcpy(DOSDrives[drive].label_read, "Audio CD   ");
555                 read = 1;
556             }
557             else
558             if (media == CDS_NO_INFO)
559             {
560                 strcpy(DOSDrives[drive].label_read, "           ");
561                 read = 1;
562             }
563
564             CDROM_Close(&wcda);
565 }
566     }
567     if ((!read) && (DOSDrives[drive].read_volinfo))
568     {
569         if (DRIVE_ReadSuperblock(drive,(char *) buff))
570             ERR("Invalid or unreadable superblock on %s (%c:).\n",
571                 DOSDrives[drive].device, (char)(drive+'A'));
572         else {
573             if (DOSDrives[drive].type == TYPE_CDROM)
574                 offs = 40;
575             else
576             if (DOSDrives[drive].type == TYPE_FLOPPY ||
577                 DOSDrives[drive].type == TYPE_HD)
578                 offs = 0x2b;
579
580             /* FIXME: ISO9660 uses 32-bytes long label. Should we do also? */
581             if (offs != -1) memcpy(DOSDrives[drive].label_read,buff+offs,11);
582             DOSDrives[drive].label_read[11]='\0';
583             read = 1;
584         }
585     }
586
587     return (read) ?
588         DOSDrives[drive].label_read : DOSDrives[drive].label_conf;
589 }
590
591
592 /***********************************************************************
593  *           DRIVE_GetSerialNumber
594  */
595 DWORD DRIVE_GetSerialNumber( int drive )
596 {
597     DWORD serial = 0;
598 char buff[DRIVE_SUPER];
599
600     if (!DRIVE_IsValid( drive )) return 0;
601     
602     if (DOSDrives[drive].read_volinfo)
603     {
604         switch(DOSDrives[drive].type)
605         {
606             case TYPE_FLOPPY:
607             case TYPE_HD:
608       if (DRIVE_ReadSuperblock(drive,(char *) buff))
609         MESSAGE("Invalid or unreadable superblock on %s (%c:)."
610                         " Maybe not FAT?\n" ,
611                         DOSDrives[drive].device, 'A'+drive);
612       else
613                     serial = *((DWORD*)(buff+0x27));
614                 break;
615             case TYPE_CDROM:
616                 serial = CDROM_GetSerial(drive);
617                 break;
618             default:
619                 FIXME("Serial number reading from file system on drive %c: not supported yet.\n", drive+'A');
620         }
621     }
622                 
623     return (serial) ? serial : DOSDrives[drive].serial_conf;
624 }
625
626
627 /***********************************************************************
628  *           DRIVE_SetSerialNumber
629  */
630 int DRIVE_SetSerialNumber( int drive, DWORD serial )
631 {
632     char buff[DRIVE_SUPER];
633
634     if (!DRIVE_IsValid( drive )) return 0;
635
636     if (DOSDrives[drive].read_volinfo)
637     {
638         if ((DOSDrives[drive].type != TYPE_FLOPPY) &&
639             (DOSDrives[drive].type != TYPE_HD)) return 0;
640         /* check, if the drive has a FAT filesystem */ 
641         if (DRIVE_ReadSuperblock(drive, buff)) return 0;
642         if (DRIVE_WriteSuperblockEntry(drive, 0x27, 4, (char *) &serial)) return 0;
643         return 1;
644     }
645
646     if (DOSDrives[drive].type == TYPE_CDROM) return 0;
647     DOSDrives[drive].serial_conf = serial;
648     return 1;
649 }
650
651
652 /***********************************************************************
653  *           DRIVE_GetType
654  */
655 DRIVETYPE DRIVE_GetType( int drive )
656 {
657     if (!DRIVE_IsValid( drive )) return TYPE_INVALID;
658     return DOSDrives[drive].type;
659 }
660
661
662 /***********************************************************************
663  *           DRIVE_GetFlags
664  */
665 UINT DRIVE_GetFlags( int drive )
666 {
667     if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
668     return DOSDrives[drive].flags;
669 }
670
671
672 /***********************************************************************
673  *           DRIVE_Chdir
674  */
675 int DRIVE_Chdir( int drive, const char *path )
676 {
677     DOS_FULL_NAME full_name;
678     char buffer[MAX_PATHNAME_LEN];
679     LPSTR unix_cwd;
680     BY_HANDLE_FILE_INFORMATION info;
681     TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
682
683     strcpy( buffer, "A:" );
684     buffer[0] += drive;
685     TRACE("(%c:,%s)\n", buffer[0], path );
686     lstrcpynA( buffer + 2, path, sizeof(buffer) - 2 );
687
688     if (!DOSFS_GetFullName( buffer, TRUE, &full_name )) return 0;
689     if (!FILE_Stat( full_name.long_name, &info )) return 0;
690     if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
691     {
692         SetLastError( ERROR_FILE_NOT_FOUND );
693         return 0;
694     }
695     unix_cwd = full_name.long_name + strlen( DOSDrives[drive].root );
696     while (*unix_cwd == '/') unix_cwd++;
697
698     TRACE("(%c:): unix_cwd=%s dos_cwd=%s\n",
699                    'A' + drive, unix_cwd, full_name.short_name + 3 );
700
701     HeapFree( GetProcessHeap(), 0, DOSDrives[drive].dos_cwd );
702     HeapFree( GetProcessHeap(), 0, DOSDrives[drive].unix_cwd );
703     DOSDrives[drive].dos_cwd  = HEAP_strdupA( GetProcessHeap(), 0,
704                                               full_name.short_name + 3 );
705     DOSDrives[drive].unix_cwd = HEAP_strdupA( GetProcessHeap(), 0, unix_cwd );
706
707     if (pTask && (pTask->curdrive & 0x80) && 
708         ((pTask->curdrive & ~0x80) == drive))
709     {
710         lstrcpynA( pTask->curdir, full_name.short_name + 2,
711                      sizeof(pTask->curdir) );
712         DRIVE_LastTask = GetCurrentTask();
713     }
714     return 1;
715 }
716
717
718 /***********************************************************************
719  *           DRIVE_Disable
720  */
721 int DRIVE_Disable( int drive  )
722 {
723     if ((drive < 0) || (drive >= MAX_DOS_DRIVES) || !DOSDrives[drive].root)
724     {
725         SetLastError( ERROR_INVALID_DRIVE );
726         return 0;
727     }
728     DOSDrives[drive].flags |= DRIVE_DISABLED;
729     return 1;
730 }
731
732
733 /***********************************************************************
734  *           DRIVE_Enable
735  */
736 int DRIVE_Enable( int drive  )
737 {
738     if ((drive < 0) || (drive >= MAX_DOS_DRIVES) || !DOSDrives[drive].root)
739     {
740         SetLastError( ERROR_INVALID_DRIVE );
741         return 0;
742     }
743     DOSDrives[drive].flags &= ~DRIVE_DISABLED;
744     return 1;
745 }
746
747
748 /***********************************************************************
749  *           DRIVE_SetLogicalMapping
750  */
751 int DRIVE_SetLogicalMapping ( int existing_drive, int new_drive )
752 {
753  /* If new_drive is already valid, do nothing and return 0
754     otherwise, copy DOSDrives[existing_drive] to DOSDrives[new_drive] */
755   
756     DOSDRIVE *old, *new;
757     
758     old = DOSDrives + existing_drive;
759     new = DOSDrives + new_drive;
760
761     if ((existing_drive < 0) || (existing_drive >= MAX_DOS_DRIVES) ||
762         !old->root ||
763         (new_drive < 0) || (new_drive >= MAX_DOS_DRIVES))
764     {
765         SetLastError( ERROR_INVALID_DRIVE );
766         return 0;
767     }
768
769     if ( new->root )
770     {
771         TRACE("Can\'t map drive %c to drive %c - drive %c already exists\n",
772               'A' + existing_drive, 'A' + new_drive, 'A' + new_drive );
773         /* it is already mapped there, so return success */
774         if (!strcmp(old->root,new->root))
775             return 1;
776         return 0;
777     }
778
779     new->root = HEAP_strdupA( GetProcessHeap(), 0, old->root );
780     new->dos_cwd = HEAP_strdupA( GetProcessHeap(), 0, old->dos_cwd );
781     new->unix_cwd = HEAP_strdupA( GetProcessHeap(), 0, old->unix_cwd );
782     memcpy ( new->label_conf, old->label_conf, 12 );
783     new->serial_conf = old->serial_conf;
784     new->type = old->type;
785     new->flags = old->flags;
786     new->dev = old->dev;
787     new->ino = old->ino;
788
789     TRACE("Drive %c is now equal to drive %c\n",
790           'A' + new_drive, 'A' + existing_drive );
791
792     return 1;
793 }
794
795
796 /***********************************************************************
797  *           DRIVE_OpenDevice
798  *
799  * Open the drive raw device and return a Unix fd (or -1 on error).
800  */
801 int DRIVE_OpenDevice( int drive, int flags )
802 {
803     if (!DRIVE_IsValid( drive )) return -1;
804     return open( DOSDrives[drive].device, flags );
805 }
806
807
808 /***********************************************************************
809  *           DRIVE_RawRead
810  *
811  * Read raw sectors from a device
812  */
813 int DRIVE_RawRead(BYTE drive, DWORD begin, DWORD nr_sect, BYTE *dataptr, BOOL fake_success)
814 {
815     int fd;
816
817     if ((fd = DRIVE_OpenDevice( drive, O_RDONLY )) != -1)
818     {
819         lseek( fd, begin * 512, SEEK_SET );
820         /* FIXME: check errors */
821         read( fd, dataptr, nr_sect * 512 );
822         close( fd );
823     }
824     else
825     {
826         memset(dataptr, 0, nr_sect * 512);
827                 if (fake_success)
828         {
829                         if (begin == 0 && nr_sect > 1) *(dataptr + 512) = 0xf8;
830                         if (begin == 1) *dataptr = 0xf8;
831                 }
832                 else
833                 return 0;
834     }
835         return 1;
836 }
837
838
839 /***********************************************************************
840  *           DRIVE_RawWrite
841  *
842  * Write raw sectors to a device
843  */
844 int DRIVE_RawWrite(BYTE drive, DWORD begin, DWORD nr_sect, BYTE *dataptr, BOOL fake_success)
845 {
846         int fd;
847
848     if ((fd = DRIVE_OpenDevice( drive, O_RDONLY )) != -1)
849     {
850         lseek( fd, begin * 512, SEEK_SET );
851         /* FIXME: check errors */
852         write( fd, dataptr, nr_sect * 512 );
853         close( fd );
854     }
855     else
856         if (!(fake_success))
857                 return 0;
858
859         return 1;
860 }
861
862
863 /***********************************************************************
864  *           DRIVE_GetFreeSpace
865  */
866 static int DRIVE_GetFreeSpace( int drive, PULARGE_INTEGER size, 
867                                PULARGE_INTEGER available )
868 {
869     struct statfs info;
870     unsigned long long  bigsize,bigavail=0;
871
872     if (!DRIVE_IsValid(drive))
873     {
874         SetLastError( ERROR_INVALID_DRIVE );
875         return 0;
876     }
877
878 /* FIXME: add autoconf check for this */
879 #if defined(__svr4__) || defined(_SCO_DS) || defined(__sun)
880     if (statfs( DOSDrives[drive].root, &info, 0, 0) < 0)
881 #else
882     if (statfs( DOSDrives[drive].root, &info) < 0)
883 #endif
884     {
885         FILE_SetDosError();
886         WARN("cannot do statfs(%s)\n", DOSDrives[drive].root);
887         return 0;
888     }
889
890     bigsize = (unsigned long long)info.f_bsize 
891       * (unsigned long long)info.f_blocks;
892 #ifdef STATFS_HAS_BAVAIL
893     bigavail = (unsigned long long)info.f_bavail 
894       * (unsigned long long)info.f_bsize;
895 #else
896 # ifdef STATFS_HAS_BFREE
897     bigavail = (unsigned long long)info.f_bfree 
898       * (unsigned long long)info.f_bsize;
899 # else
900 #  error "statfs has no bfree/bavail member!"
901 # endif
902 #endif
903     size->s.LowPart = (DWORD)bigsize;
904     size->s.HighPart = (DWORD)(bigsize>>32);
905     available->s.LowPart = (DWORD)bigavail;
906     available->s.HighPart = (DWORD)(bigavail>>32);
907     return 1;
908 }
909
910 /***********************************************************************
911  *       DRIVE_GetCurrentDirectory
912  * Returns "X:\\path\\etc\\".
913  *
914  * Despite the API description, return required length including the 
915  * terminating null when buffer too small. This is the real behaviour.
916 */
917
918 static UINT DRIVE_GetCurrentDirectory( UINT buflen, LPSTR buf )
919 {
920     UINT ret;
921     const char *s = DRIVE_GetDosCwd( DRIVE_GetCurrentDrive() );
922
923     assert(s);
924     ret = strlen(s) + 3; /* length of WHOLE current directory */
925     if (ret >= buflen) return ret + 1;
926     lstrcpynA( buf, "A:\\", min( 4, buflen ) );
927     if (buflen) buf[0] += DRIVE_GetCurrentDrive();
928     if (buflen > 3) lstrcpynA( buf + 3, s, buflen - 3 );
929     return ret;
930 }
931
932 /***********************************************************************
933  *           GetDiskFreeSpace16   (KERNEL.422)
934  */
935 BOOL16 WINAPI GetDiskFreeSpace16( LPCSTR root, LPDWORD cluster_sectors,
936                                   LPDWORD sector_bytes, LPDWORD free_clusters,
937                                   LPDWORD total_clusters )
938 {
939     return GetDiskFreeSpaceA( root, cluster_sectors, sector_bytes,
940                                 free_clusters, total_clusters );
941 }
942
943
944 /***********************************************************************
945  *           GetDiskFreeSpaceA   (KERNEL32.206)
946  *
947  * Fails if expression resulting from current drive's dir and "root"
948  * is not a root dir of the target drive.
949  *
950  * UNDOC: setting some LPDWORDs to NULL is perfectly possible 
951  * if the corresponding info is unneeded.
952  *
953  * FIXME: needs to support UNC names from Win95 OSR2 on.
954  *
955  * Behaviour under Win95a:
956  * CurrDir     root   result
957  * "E:\\TEST"  "E:"   FALSE
958  * "E:\\"      "E:"   TRUE
959  * "E:\\"      "E"    FALSE
960  * "E:\\"      "\\"   TRUE
961  * "E:\\TEST"  "\\"   TRUE
962  * "E:\\TEST"  ":\\"  FALSE
963  * "E:\\TEST"  "E:\\" TRUE
964  * "E:\\TEST"  ""     FALSE
965  * "E:\\"      ""     FALSE (!)
966  * "E:\\"      0x0    TRUE
967  * "E:\\TEST"  0x0    TRUE  (!)
968  * "E:\\TEST"  "C:"   TRUE  (when CurrDir of "C:" set to "\\")
969  * "E:\\TEST"  "C:"   FALSE (when CurrDir of "C:" set to "\\TEST")
970  */
971 BOOL WINAPI GetDiskFreeSpaceA( LPCSTR root, LPDWORD cluster_sectors,
972                                    LPDWORD sector_bytes, LPDWORD free_clusters,
973                                    LPDWORD total_clusters )
974 {
975     int drive;
976     ULARGE_INTEGER size,available;
977     LPCSTR path;
978     DWORD cluster_sec;
979
980     if ((!root) || (strcmp(root,"\\") == 0))
981         drive = DRIVE_GetCurrentDrive();
982     else
983     if ( (strlen(root) >= 2) && (root[1] == ':')) /* root contains drive tag */
984     {
985         drive = toupper(root[0]) - 'A';
986         path = &root[2];
987         if (path[0] == '\0')
988             path = DRIVE_GetDosCwd(drive);
989         else
990         if (path[0] == '\\')
991             path++;
992         if (strlen(path)) /* oops, we are in a subdir */
993             return FALSE;
994     }
995     else
996         return FALSE;
997
998     if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
999
1000     /* Cap the size and available at 2GB as per specs.  */
1001     if ((size.s.HighPart) ||(size.s.LowPart > 0x7fffffff))
1002         {
1003           size.s.HighPart = 0;
1004           size.s.LowPart = 0x7fffffff;
1005         }
1006     if ((available.s.HighPart) ||(available.s.LowPart > 0x7fffffff))
1007       {
1008         available.s.HighPart =0;
1009         available.s.LowPart = 0x7fffffff;
1010       }
1011     if (DRIVE_GetType(drive)==TYPE_CDROM) {
1012         if (sector_bytes)
1013         *sector_bytes    = 2048;
1014         size.s.LowPart            /= 2048;
1015         available.s.LowPart       /= 2048;
1016     } else {
1017         if (sector_bytes)
1018         *sector_bytes    = 512;
1019         size.s.LowPart            /= 512;
1020         available.s.LowPart       /= 512;
1021     }
1022     /* fixme: probably have to adjust those variables too for CDFS */
1023     cluster_sec = 1;
1024     while (cluster_sec * 65536 < size.s.LowPart) cluster_sec *= 2;
1025
1026     if (cluster_sectors)
1027         *cluster_sectors = cluster_sec;
1028     if (free_clusters)
1029         *free_clusters   = available.s.LowPart / cluster_sec;
1030     if (total_clusters)
1031         *total_clusters  = size.s.LowPart / cluster_sec;
1032     return TRUE;
1033 }
1034
1035
1036 /***********************************************************************
1037  *           GetDiskFreeSpaceW   (KERNEL32.207)
1038  */
1039 BOOL WINAPI GetDiskFreeSpaceW( LPCWSTR root, LPDWORD cluster_sectors,
1040                                    LPDWORD sector_bytes, LPDWORD free_clusters,
1041                                    LPDWORD total_clusters )
1042 {
1043     LPSTR xroot;
1044     BOOL ret;
1045
1046     xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, root);
1047     ret = GetDiskFreeSpaceA( xroot,cluster_sectors, sector_bytes,
1048                                free_clusters, total_clusters );
1049     HeapFree( GetProcessHeap(), 0, xroot );
1050     return ret;
1051 }
1052
1053
1054 /***********************************************************************
1055  *           GetDiskFreeSpaceExA   (KERNEL32.871)
1056  *
1057  *  This function is used to aquire the size of the available and
1058  *  total space on a logical volume.
1059  *
1060  * RETURNS
1061  *
1062  *  Zero on failure, nonzero upon success. Use GetLastError to obtain
1063  *  detailed error information.
1064  *
1065  */
1066 BOOL WINAPI GetDiskFreeSpaceExA( LPCSTR root,
1067                                      PULARGE_INTEGER avail,
1068                                      PULARGE_INTEGER total,
1069                                      PULARGE_INTEGER totalfree)
1070 {
1071     int drive;
1072     ULARGE_INTEGER size,available;
1073
1074     if (!root) drive = DRIVE_GetCurrentDrive();
1075     else
1076     {
1077         if ((root[1]) && ((root[1] != ':') || (root[2] != '\\')))
1078         {
1079             FIXME("there are valid root names which are not supported yet\n");
1080             /* ..like UNC names, for instance. */
1081
1082             WARN("invalid root '%s'\n", root );
1083             return FALSE;
1084         }
1085         drive = toupper(root[0]) - 'A';
1086     }
1087
1088     if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
1089
1090     if (total)
1091     {
1092         total->s.HighPart = size.s.HighPart;
1093         total->s.LowPart = size.s.LowPart ;
1094     }
1095
1096     if (totalfree)
1097     {
1098         totalfree->s.HighPart = available.s.HighPart;
1099         totalfree->s.LowPart = available.s.LowPart ;
1100     }
1101
1102     if (avail)
1103     {
1104         if (FIXME_ON(dosfs))
1105         {
1106             /* On Windows2000, we need to check the disk quota
1107                allocated for the user owning the calling process. We
1108                don't want to be more obtrusive than necessary with the
1109                FIXME messages, so don't print the FIXME unless Wine is
1110                actually masquerading as Windows2000. */
1111
1112             OSVERSIONINFOA ovi;
1113             ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
1114             if (GetVersionExA(&ovi))
1115             {
1116               if (ovi.dwPlatformId == VER_PLATFORM_WIN32_NT && ovi.dwMajorVersion > 4)
1117                   FIXME("no per-user quota support yet\n");
1118             }
1119         }
1120
1121         /* Quick hack, should eventually be fixed to work 100% with
1122            Windows2000 (see comment above). */
1123         avail->s.HighPart = available.s.HighPart;
1124         avail->s.LowPart = available.s.LowPart ;
1125     }
1126
1127     return TRUE;
1128 }
1129
1130 /***********************************************************************
1131  *           GetDiskFreeSpaceExW   (KERNEL32.873)
1132  */
1133 BOOL WINAPI GetDiskFreeSpaceExW( LPCWSTR root, PULARGE_INTEGER avail,
1134                                      PULARGE_INTEGER total,
1135                                      PULARGE_INTEGER  totalfree)
1136 {
1137     LPSTR xroot;
1138     BOOL ret;
1139
1140     xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, root);
1141     ret = GetDiskFreeSpaceExA( xroot, avail, total, totalfree);
1142     HeapFree( GetProcessHeap(), 0, xroot );
1143     return ret;
1144 }
1145
1146 /***********************************************************************
1147  *           GetDriveType16   (KERNEL.136)
1148  * This function returns the type of a drive in Win16. 
1149  * Note that it returns DRIVE_REMOTE for CD-ROMs, since MSCDEX uses the
1150  * remote drive API. The return value DRIVE_REMOTE for CD-ROMs has been
1151  * verified on Win 3.11 and Windows 95. Some programs rely on it, so don't
1152  * do any pseudo-clever changes.
1153  *
1154  * RETURNS
1155  *      drivetype DRIVE_xxx
1156  */
1157 UINT16 WINAPI GetDriveType16(
1158         UINT16 drive    /* [in] number (NOT letter) of drive */
1159 ) {
1160     TRACE("(%c:)\n", 'A' + drive );
1161     switch(DRIVE_GetType(drive))
1162     {
1163     case TYPE_FLOPPY:  return DRIVE_REMOVABLE;
1164     case TYPE_HD:      return DRIVE_FIXED;
1165     case TYPE_CDROM:   return DRIVE_REMOTE;
1166     case TYPE_NETWORK: return DRIVE_REMOTE;
1167     case TYPE_INVALID:
1168     default:           return DRIVE_CANNOTDETERMINE;
1169     }
1170 }
1171
1172
1173 /***********************************************************************
1174  *           GetDriveTypeA   (KERNEL32.208)
1175  *
1176  * Returns the type of the disk drive specified.  If root is NULL the
1177  * root of the current directory is used.
1178  *
1179  * RETURNS
1180  *
1181  *  Type of drive (from Win32 SDK):
1182  *
1183  *   DRIVE_UNKNOWN     unable to find out anything about the drive
1184  *   DRIVE_NO_ROOT_DIR nonexistand root dir
1185  *   DRIVE_REMOVABLE   the disk can be removed from the machine
1186  *   DRIVE_FIXED       the disk can not be removed from the machine
1187  *   DRIVE_REMOTE      network disk
1188  *   DRIVE_CDROM       CDROM drive
1189  *   DRIVE_RAMDISK     virtual disk in ram
1190  *
1191  *   DRIVE_DOESNOTEXIST    XXX Not valid return value
1192  *   DRIVE_CANNOTDETERMINE XXX Not valid return value
1193  *   
1194  * BUGS
1195  *
1196  *  Currently returns DRIVE_DOESNOTEXIST and DRIVE_CANNOTDETERMINE
1197  *  when it really should return DRIVE_NO_ROOT_DIR and DRIVE_UNKNOWN.
1198  *  Why were the former defines used?
1199  *
1200  *  DRIVE_RAMDISK is unsupported.
1201  */
1202 UINT WINAPI GetDriveTypeA(LPCSTR root /* String describing drive */)
1203 {
1204     int drive;
1205     TRACE("(%s)\n", debugstr_a(root));
1206
1207     if (NULL == root) drive = DRIVE_GetCurrentDrive();
1208     else
1209     {
1210         if ((root[1]) && (root[1] != ':'))
1211         {
1212             WARN("invalid root '%s'\n", debugstr_a(root));
1213             return DRIVE_DOESNOTEXIST;
1214         }
1215         drive = toupper(root[0]) - 'A';
1216     }
1217     switch(DRIVE_GetType(drive))
1218     {
1219     case TYPE_FLOPPY:  return DRIVE_REMOVABLE;
1220     case TYPE_HD:      return DRIVE_FIXED;
1221     case TYPE_CDROM:   return DRIVE_CDROM;
1222     case TYPE_NETWORK: return DRIVE_REMOTE;
1223     case TYPE_INVALID: return DRIVE_DOESNOTEXIST;
1224     default:           return DRIVE_CANNOTDETERMINE;
1225     }
1226 }
1227
1228
1229 /***********************************************************************
1230  *           GetDriveTypeW   (KERNEL32.209)
1231  */
1232 UINT WINAPI GetDriveTypeW( LPCWSTR root )
1233 {
1234     LPSTR xpath = HEAP_strdupWtoA( GetProcessHeap(), 0, root );
1235     UINT ret = GetDriveTypeA( xpath );
1236     HeapFree( GetProcessHeap(), 0, xpath );
1237     return ret;
1238 }
1239
1240
1241 /***********************************************************************
1242  *           GetCurrentDirectory16   (KERNEL.411)
1243  */
1244 UINT16 WINAPI GetCurrentDirectory16( UINT16 buflen, LPSTR buf )
1245 {
1246     return (UINT16)DRIVE_GetCurrentDirectory(buflen, buf);
1247 }
1248
1249
1250 /***********************************************************************
1251  *           GetCurrentDirectoryA   (KERNEL32.196)
1252  */
1253 UINT WINAPI GetCurrentDirectoryA( UINT buflen, LPSTR buf )
1254 {
1255     UINT ret;
1256     char longname[MAX_PATHNAME_LEN];
1257     char shortname[MAX_PATHNAME_LEN];
1258     ret = DRIVE_GetCurrentDirectory(MAX_PATHNAME_LEN, shortname);
1259     if ( ret > MAX_PATHNAME_LEN ) {
1260       ERR_(file)("pathnamelength (%d) > MAX_PATHNAME_LEN!\n", ret );
1261       return ret;
1262     }
1263     GetLongPathNameA(shortname, longname, MAX_PATHNAME_LEN);
1264     ret = lstrlenA( longname ) + 1;
1265     if (ret > buflen) return ret;
1266     lstrcpyA(buf, longname);
1267     return ret - 1;
1268 }
1269
1270 /***********************************************************************
1271  *           GetCurrentDirectoryW   (KERNEL32.197)
1272  */
1273 UINT WINAPI GetCurrentDirectoryW( UINT buflen, LPWSTR buf )
1274 {
1275     LPSTR xpath = HeapAlloc( GetProcessHeap(), 0, buflen+1 );
1276     UINT ret = GetCurrentDirectoryA( buflen, xpath );
1277     if (ret < buflen) lstrcpyAtoW ( buf, xpath );
1278     HeapFree( GetProcessHeap(), 0, xpath );
1279     return ret;
1280 }
1281
1282
1283 /***********************************************************************
1284  *           SetCurrentDirectory   (KERNEL.412)
1285  */
1286 BOOL16 WINAPI SetCurrentDirectory16( LPCSTR dir )
1287 {
1288     return SetCurrentDirectoryA( dir );
1289 }
1290
1291
1292 /***********************************************************************
1293  *           SetCurrentDirectoryA   (KERNEL32.479)
1294  */
1295 BOOL WINAPI SetCurrentDirectoryA( LPCSTR dir )
1296 {
1297     int olddrive, drive = DRIVE_GetCurrentDrive();
1298
1299     if (!dir) {
1300         ERR_(file)("(NULL)!\n");
1301         return FALSE;
1302     }
1303     if (dir[0] && (dir[1]==':'))
1304     {
1305         drive = tolower( *dir ) - 'a';
1306         dir += 2;
1307     }
1308
1309     /* WARNING: we need to set the drive before the dir, as DRIVE_Chdir
1310        sets pTask->curdir only if pTask->curdrive is drive */
1311     olddrive = drive; /* in case DRIVE_Chdir fails */
1312     if (!(DRIVE_SetCurrentDrive( drive )))
1313         return FALSE;
1314     /* FIXME: what about empty strings? Add a \\ ? */
1315     if (!DRIVE_Chdir( drive, dir )) {
1316         DRIVE_SetCurrentDrive(olddrive);
1317         return FALSE;
1318     }
1319     return TRUE;
1320 }
1321
1322
1323 /***********************************************************************
1324  *           SetCurrentDirectoryW   (KERNEL32.480)
1325  */
1326 BOOL WINAPI SetCurrentDirectoryW( LPCWSTR dirW )
1327 {
1328     LPSTR dir = HEAP_strdupWtoA( GetProcessHeap(), 0, dirW );
1329     BOOL res = SetCurrentDirectoryA( dir );
1330     HeapFree( GetProcessHeap(), 0, dir );
1331     return res;
1332 }
1333
1334
1335 /***********************************************************************
1336  *           GetLogicalDriveStringsA   (KERNEL32.231)
1337  */
1338 UINT WINAPI GetLogicalDriveStringsA( UINT len, LPSTR buffer )
1339 {
1340     int drive, count;
1341
1342     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1343         if (DRIVE_IsValid(drive)) count++;
1344     if ((count * 4) + 1 <= len)
1345     {
1346         LPSTR p = buffer;
1347         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1348             if (DRIVE_IsValid(drive))
1349             {
1350                 *p++ = 'a' + drive;
1351                 *p++ = ':';
1352                 *p++ = '\\';
1353                 *p++ = '\0';
1354             }
1355         *p = '\0';
1356        return count * 4;
1357     }
1358     else
1359       return (count * 4) + 1;/* account for terminating null */
1360     /* The API tells about these different return values */
1361 }
1362
1363
1364 /***********************************************************************
1365  *           GetLogicalDriveStringsW   (KERNEL32.232)
1366  */
1367 UINT WINAPI GetLogicalDriveStringsW( UINT len, LPWSTR buffer )
1368 {
1369     int drive, count;
1370
1371     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1372         if (DRIVE_IsValid(drive)) count++;
1373     if (count * 4 * sizeof(WCHAR) <= len)
1374     {
1375         LPWSTR p = buffer;
1376         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1377             if (DRIVE_IsValid(drive))
1378             {
1379                 *p++ = (WCHAR)('a' + drive);
1380                 *p++ = (WCHAR)':';
1381                 *p++ = (WCHAR)'\\';
1382                 *p++ = (WCHAR)'\0';
1383             }
1384         *p = (WCHAR)'\0';
1385     }
1386     return count * 4 * sizeof(WCHAR);
1387 }
1388
1389
1390 /***********************************************************************
1391  *           GetLogicalDrives   (KERNEL32.233)
1392  */
1393 DWORD WINAPI GetLogicalDrives(void)
1394 {
1395     DWORD ret = 0;
1396     int drive;
1397
1398     for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1399     {
1400         if ( (DRIVE_IsValid(drive)) ||
1401             (DOSDrives[drive].type == TYPE_CDROM)) /* audio CD is also valid */
1402             ret |= (1 << drive);
1403     }
1404     return ret;
1405 }
1406
1407
1408 /***********************************************************************
1409  *           GetVolumeInformationA   (KERNEL32.309)
1410  */
1411 BOOL WINAPI GetVolumeInformationA( LPCSTR root, LPSTR label,
1412                                        DWORD label_len, DWORD *serial,
1413                                        DWORD *filename_len, DWORD *flags,
1414                                        LPSTR fsname, DWORD fsname_len )
1415 {
1416     int drive;
1417     char *cp;
1418
1419     /* FIXME, SetLastError()s missing */
1420
1421     if (!root) drive = DRIVE_GetCurrentDrive();
1422     else
1423     {
1424         if ((root[1]) && (root[1] != ':'))
1425         {
1426             WARN("invalid root '%s'\n",root);
1427             return FALSE;
1428         }
1429         drive = toupper(root[0]) - 'A';
1430     }
1431     if (!DRIVE_IsValid( drive )) return FALSE;
1432     if (label)
1433     {
1434        lstrcpynA( label, DRIVE_GetLabel(drive), label_len );
1435        for (cp = label; *cp; cp++);
1436        while (cp != label && *(cp-1) == ' ') cp--;
1437        *cp = '\0';
1438     }
1439     if (serial) *serial = DRIVE_GetSerialNumber(drive);
1440
1441     /* Set the filesystem information */
1442     /* Note: we only emulate a FAT fs at present */
1443
1444     if (filename_len) {
1445         if (DOSDrives[drive].flags & DRIVE_SHORT_NAMES)
1446             *filename_len = 12;
1447         else
1448             *filename_len = 255;
1449     }
1450     if (flags)
1451       {
1452        *flags=0;
1453        if (DOSDrives[drive].flags & DRIVE_CASE_SENSITIVE)
1454          *flags|=FS_CASE_SENSITIVE;
1455        if (DOSDrives[drive].flags & DRIVE_CASE_PRESERVING)
1456          *flags|=FS_CASE_IS_PRESERVED;
1457       }
1458     if (fsname) {
1459         /* Diablo checks that return code ... */
1460         if (DRIVE_GetType(drive)==TYPE_CDROM)
1461             lstrcpynA( fsname, "CDFS", fsname_len );
1462         else
1463             lstrcpynA( fsname, "FAT", fsname_len );
1464     }
1465     return TRUE;
1466 }
1467
1468
1469 /***********************************************************************
1470  *           GetVolumeInformationW   (KERNEL32.310)
1471  */
1472 BOOL WINAPI GetVolumeInformationW( LPCWSTR root, LPWSTR label,
1473                                        DWORD label_len, DWORD *serial,
1474                                        DWORD *filename_len, DWORD *flags,
1475                                        LPWSTR fsname, DWORD fsname_len )
1476 {
1477     LPSTR xroot    = HEAP_strdupWtoA( GetProcessHeap(), 0, root );
1478     LPSTR xvolname = label ? HeapAlloc(GetProcessHeap(),0,label_len) : NULL;
1479     LPSTR xfsname  = fsname ? HeapAlloc(GetProcessHeap(),0,fsname_len) : NULL;
1480     BOOL ret = GetVolumeInformationA( xroot, xvolname, label_len, serial,
1481                                           filename_len, flags, xfsname,
1482                                           fsname_len );
1483     if (ret)
1484     {
1485         if (label) lstrcpyAtoW( label, xvolname );
1486         if (fsname) lstrcpyAtoW( fsname, xfsname );
1487     }
1488     HeapFree( GetProcessHeap(), 0, xroot );
1489     HeapFree( GetProcessHeap(), 0, xvolname );
1490     HeapFree( GetProcessHeap(), 0, xfsname );
1491     return ret;
1492 }
1493
1494 /***********************************************************************
1495  *           SetVolumeLabelA   (KERNEL32.675)
1496  */
1497 BOOL WINAPI SetVolumeLabelA( LPCSTR root, LPCSTR volname )
1498 {
1499     int drive;
1500     
1501     /* FIXME, SetLastErrors missing */
1502
1503     if (!root) drive = DRIVE_GetCurrentDrive();
1504     else
1505     {
1506         if ((root[1]) && (root[1] != ':'))
1507         {
1508             WARN("invalid root '%s'\n",root);
1509             return FALSE;
1510         }
1511         drive = toupper(root[0]) - 'A';
1512     }
1513     if (!DRIVE_IsValid( drive )) return FALSE;
1514
1515     /* some copy protection stuff check this */
1516     if (DRIVE_GetType( drive ) == TYPE_CDROM) return FALSE;
1517
1518     FIXME("(%s,%s),stub!\n", root, volname);
1519     return TRUE;
1520 }
1521
1522 /***********************************************************************
1523  *           SetVolumeLabelW   (KERNEL32.676)
1524  */
1525 BOOL WINAPI SetVolumeLabelW(LPCWSTR rootpath,LPCWSTR volname)
1526 {
1527     LPSTR xroot, xvol;
1528     BOOL ret;
1529
1530     xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, rootpath);
1531     xvol = HEAP_strdupWtoA( GetProcessHeap(), 0, volname);
1532     ret = SetVolumeLabelA( xroot, xvol );
1533     HeapFree( GetProcessHeap(), 0, xroot );
1534     HeapFree( GetProcessHeap(), 0, xvol );
1535     return ret;
1536 }