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