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