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