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