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