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