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