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