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