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