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