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