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