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