Rewrote handling of COM and LPT devices to use symlinks in
[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 #ifdef HAVE_SYS_STATVFS_H
43 # include <sys/statvfs.h>
44 #endif
45
46 #define NONAMELESSUNION
47 #define NONAMELESSSTRUCT
48 #include "ntstatus.h"
49 #include "windef.h"
50 #include "winbase.h"
51 #include "winreg.h"
52 #include "winternl.h"
53 #include "wine/winbase16.h"   /* for GetCurrentTask */
54 #include "winerror.h"
55 #include "winioctl.h"
56 #include "ntddstor.h"
57 #include "ntddcdrm.h"
58 #include "file.h"
59 #include "wine/unicode.h"
60 #include "wine/library.h"
61 #include "wine/server.h"
62 #include "wine/debug.h"
63
64 WINE_DEFAULT_DEBUG_CHANNEL(dosfs);
65 WINE_DECLARE_DEBUG_CHANNEL(file);
66
67 typedef struct
68 {
69     char     *root;      /* root dir in Unix format without trailing / */
70     LPWSTR    dos_cwd;   /* cwd in DOS format without leading or trailing \ */
71     char     *unix_cwd;  /* cwd in Unix format without leading or trailing / */
72     char     *device;    /* raw device path */
73     UINT      type;      /* drive type */
74     UINT      flags;     /* drive flags */
75     dev_t     dev;       /* unix device number */
76     ino_t     ino;       /* unix inode number */
77 } DOSDRIVE;
78
79
80 static const WCHAR DRIVE_Types[][8] =
81 {
82     { 0 }, /* DRIVE_UNKNOWN */
83     { 0 }, /* DRIVE_NO_ROOT_DIR */
84     {'f','l','o','p','p','y',0}, /* DRIVE_REMOVABLE */
85     {'h','d',0}, /* DRIVE_FIXED */
86     {'n','e','t','w','o','r','k',0}, /* DRIVE_REMOTE */
87     {'c','d','r','o','m',0}, /* DRIVE_CDROM */
88     {'r','a','m','d','i','s','k',0} /* DRIVE_RAMDISK */
89 };
90
91 #define MAX_DOS_DRIVES  26
92
93 static DOSDRIVE DOSDrives[MAX_DOS_DRIVES];
94 static int DRIVE_CurDrive = -1;
95
96 static HTASK16 DRIVE_LastTask = 0;
97
98 /* strdup on the process heap */
99 inline static char *heap_strdup( const char *str )
100 {
101     INT len = strlen(str) + 1;
102     LPSTR p = HeapAlloc( GetProcessHeap(), 0, len );
103     if (p) memcpy( p, str, len );
104     return p;
105 }
106
107 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
108
109 extern void CDROM_InitRegistry(int dev);
110
111 /***********************************************************************
112  *           DRIVE_GetDriveType
113  */
114 static inline UINT DRIVE_GetDriveType( INT drive, LPCWSTR value )
115 {
116     int i;
117
118     for (i = 0; i < sizeof(DRIVE_Types)/sizeof(DRIVE_Types[0]); i++)
119     {
120         if (!strcmpiW( value, DRIVE_Types[i] )) return i;
121     }
122     MESSAGE("Drive %c: unknown drive type %s, defaulting to 'hd'.\n",
123             'A' + drive, debugstr_w(value) );
124     return DRIVE_FIXED;
125 }
126
127
128 /***********************************************************************
129  *           DRIVE_Init
130  */
131 int DRIVE_Init(void)
132 {
133     int i, len, count = 0;
134     WCHAR driveW[] = {'M','a','c','h','i','n','e','\\','S','o','f','t','w','a','r','e','\\',
135                       'W','i','n','e','\\','W','i','n','e','\\',
136                       'C','o','n','f','i','g','\\','D','r','i','v','e',' ','A',0};
137     WCHAR drive_env[] = {'=','A',':',0};
138     WCHAR path[MAX_PATHNAME_LEN];
139     char tmp[MAX_PATHNAME_LEN*sizeof(WCHAR) + sizeof(KEY_VALUE_PARTIAL_INFORMATION)];
140     struct stat drive_stat_buffer;
141     WCHAR *p;
142     DOSDRIVE *drive;
143     HKEY hkey;
144     DWORD dummy;
145     OBJECT_ATTRIBUTES attr;
146     UNICODE_STRING nameW;
147
148     static const WCHAR PathW[] = {'P','a','t','h',0};
149     static const WCHAR TypeW[] = {'T','y','p','e',0};
150     static const WCHAR DeviceW[] = {'D','e','v','i','c','e',0};
151     static const WCHAR FailReadOnlyW[] = {'F','a','i','l','R','e','a','d','O','n','l','y',0};
152
153     attr.Length = sizeof(attr);
154     attr.RootDirectory = 0;
155     attr.ObjectName = &nameW;
156     attr.Attributes = 0;
157     attr.SecurityDescriptor = NULL;
158     attr.SecurityQualityOfService = NULL;
159
160     for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
161     {
162         RtlInitUnicodeString( &nameW, driveW );
163         nameW.Buffer[(nameW.Length / sizeof(WCHAR)) - 1] = 'A' + i;
164         if (NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr ) != STATUS_SUCCESS) continue;
165
166         /* Get the root path */
167         RtlInitUnicodeString( &nameW, PathW );
168         if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
169         {
170             WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
171             ExpandEnvironmentStringsW( data, path, sizeof(path)/sizeof(WCHAR) );
172
173             p = path + strlenW(path) - 1;
174             while ((p > path) && (*p == '/')) *p-- = '\0';
175
176             if (path[0] == '/')
177             {
178                 len = WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL);
179                 drive->root = HeapAlloc(GetProcessHeap(), 0, len);
180                 WideCharToMultiByte(CP_UNIXCP, 0, path, -1, drive->root, len, NULL, NULL);
181             }
182             else
183             {
184                 /* relative paths are relative to config dir */
185                 const char *config = wine_get_config_dir();
186                 len = strlen(config);
187                 len += WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL) + 2;
188                 drive->root = HeapAlloc( GetProcessHeap(), 0, len );
189                 len -= sprintf( drive->root, "%s/", config );
190                 WideCharToMultiByte(CP_UNIXCP, 0, path, -1,
191                                     drive->root + strlen(drive->root), len, NULL, NULL);
192             }
193
194             if (stat( drive->root, &drive_stat_buffer ))
195             {
196                 MESSAGE("Could not stat %s (%s), ignoring drive %c:\n",
197                         drive->root, strerror(errno), 'A' + i);
198                 HeapFree( GetProcessHeap(), 0, drive->root );
199                 drive->root = NULL;
200                 goto next;
201             }
202             if (!S_ISDIR(drive_stat_buffer.st_mode))
203             {
204                 MESSAGE("%s is not a directory, ignoring drive %c:\n",
205                         drive->root, 'A' + i );
206                 HeapFree( GetProcessHeap(), 0, drive->root );
207                 drive->root = NULL;
208                 goto next;
209             }
210
211             drive->dos_cwd  = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(drive->dos_cwd[0]));
212             drive->unix_cwd = heap_strdup( "" );
213             drive->device   = NULL;
214             drive->flags    = 0;
215             drive->dev      = drive_stat_buffer.st_dev;
216             drive->ino      = drive_stat_buffer.st_ino;
217
218             /* Get the drive type */
219             RtlInitUnicodeString( &nameW, TypeW );
220             if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
221             {
222                 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
223                 drive->type = DRIVE_GetDriveType( i, data );
224             }
225             else drive->type = DRIVE_FIXED;
226
227             /* Get the device */
228             RtlInitUnicodeString( &nameW, DeviceW );
229             if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
230             {
231                 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
232                 len = WideCharToMultiByte(CP_UNIXCP, 0, data, -1, NULL, 0, NULL, NULL);
233                 drive->device = HeapAlloc(GetProcessHeap(), 0, len);
234                 WideCharToMultiByte(CP_UNIXCP, 0, data, -1, drive->device, len, NULL, NULL);
235
236                 if (drive->type == DRIVE_CDROM)
237                 {
238                     int cd_fd;
239                     if ((cd_fd = open(drive->device, O_RDONLY|O_NONBLOCK)) != -1)
240                     {
241                         CDROM_InitRegistry(cd_fd);
242                         close(cd_fd);
243                     }
244                 }
245             }
246
247             /* Get the FailReadOnly flag */
248             RtlInitUnicodeString( &nameW, FailReadOnlyW );
249             if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
250             {
251                 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
252                 if (IS_OPTION_TRUE(data[0])) drive->flags |= DRIVE_FAIL_READ_ONLY;
253             }
254
255             /* Make the first hard disk the current drive */
256             if ((DRIVE_CurDrive == -1) && (drive->type == DRIVE_FIXED))
257                 DRIVE_CurDrive = i;
258
259             count++;
260             TRACE("Drive %c: path=%s type=%s flags=%08x dev=%x ino=%x\n",
261                   'A' + i, drive->root, debugstr_w(DRIVE_Types[drive->type]),
262                   drive->flags, (int)drive->dev, (int)drive->ino );
263         }
264
265     next:
266         NtClose( hkey );
267     }
268
269     if (!count)
270     {
271         MESSAGE("Warning: no valid DOS drive found, check your configuration file.\n" );
272         /* Create a C drive pointing to Unix root dir */
273         DOSDrives[2].root     = heap_strdup( "/" );
274         DOSDrives[2].dos_cwd  = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DOSDrives[2].dos_cwd[0]));
275         DOSDrives[2].unix_cwd = heap_strdup( "" );
276         DOSDrives[2].type     = DRIVE_FIXED;
277         DOSDrives[2].device   = NULL;
278         DOSDrives[2].flags    = 0;
279         DRIVE_CurDrive = 2;
280     }
281
282     /* Make sure the current drive is valid */
283     if (DRIVE_CurDrive == -1)
284     {
285         for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
286         {
287             if (drive->root)
288             {
289                 DRIVE_CurDrive = i;
290                 break;
291             }
292         }
293     }
294
295     /* get current working directory info for all drives */
296     for (i = 0; i < MAX_DOS_DRIVES; i++, drive_env[1]++)
297     {
298         if (!GetEnvironmentVariableW(drive_env, path, MAX_PATHNAME_LEN)) continue;
299         /* sanity check */
300         if (toupperW(path[0]) != drive_env[1] || path[1] != ':') continue;
301         DRIVE_Chdir( i, path + 2 );
302     }
303     return 1;
304 }
305
306
307 /***********************************************************************
308  *           DRIVE_IsValid
309  */
310 int DRIVE_IsValid( int drive )
311 {
312     if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
313     return (DOSDrives[drive].root != NULL);
314 }
315
316
317 /***********************************************************************
318  *           DRIVE_GetCurrentDrive
319  */
320 int DRIVE_GetCurrentDrive(void)
321 {
322     TDB *pTask = GlobalLock16(GetCurrentTask());
323     if (pTask && (pTask->curdrive & 0x80)) return pTask->curdrive & ~0x80;
324     return DRIVE_CurDrive;
325 }
326
327
328 /***********************************************************************
329  *           DRIVE_SetCurrentDrive
330  */
331 int DRIVE_SetCurrentDrive( int drive )
332 {
333     TDB *pTask = GlobalLock16(GetCurrentTask());
334     if (!DRIVE_IsValid( drive ))
335     {
336         SetLastError( ERROR_INVALID_DRIVE );
337         return 0;
338     }
339     TRACE("%c:\n", 'A' + drive );
340     DRIVE_CurDrive = drive;
341     if (pTask) pTask->curdrive = drive | 0x80;
342     return 1;
343 }
344
345
346 /***********************************************************************
347  *           DRIVE_FindDriveRoot
348  *
349  * Find a drive for which the root matches the beginning of the given path.
350  * This can be used to translate a Unix path into a drive + DOS path.
351  * Return value is the drive, or -1 on error. On success, path is modified
352  * to point to the beginning of the DOS path.
353  *
354  * Note: path must be in the encoding of the underlying Unix file system.
355  */
356 int DRIVE_FindDriveRoot( const char **path )
357 {
358     /* Starting with the full path, check if the device and inode match any of
359      * the wine 'drives'. If not then remove the last path component and try
360      * again. If the last component was a '..' then skip a normal component
361      * since it's a directory that's ascended back out of.
362      */
363     int drive, level, len;
364     char buffer[MAX_PATHNAME_LEN];
365     char *p;
366     struct stat st;
367
368     strcpy( buffer, *path );
369     for (p = buffer; *p; p++) if (*p == '\\') *p = '/';
370     len = p - buffer;
371
372     /* strip off trailing slashes */
373     while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
374
375     for (;;)
376     {
377         /* Find the drive */
378         if (stat( buffer, &st ) == 0 && S_ISDIR( st.st_mode ))
379         {
380             for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
381             {
382                if (!DOSDrives[drive].root) continue;
383
384                if ((DOSDrives[drive].dev == st.st_dev) &&
385                    (DOSDrives[drive].ino == st.st_ino))
386                {
387                    if (len == 1) len = 0;  /* preserve root slash in returned path */
388                    TRACE( "%s -> drive %c:, root='%s', name='%s'\n",
389                        *path, 'A' + drive, buffer, *path + len);
390                    *path += len;
391                    if (!**path) *path = "\\";
392                    return drive;
393                }
394             }
395         }
396         if (len <= 1) return -1;  /* reached root */
397
398         level = 0;
399         while (level < 1)
400         {
401             /* find start of the last path component */
402             while (len > 1 && buffer[len - 1] != '/') len--;
403             if (!buffer[len]) break;  /* empty component -> reached root */
404             /* does removing it take us up a level? */
405             if (strcmp( buffer + len, "." ) != 0)
406                 level += strcmp( buffer + len, ".." ) ? 1 : -1;
407             buffer[len] = 0;
408             /* strip off trailing slashes */
409             while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
410         }
411     }
412 }
413
414
415 /***********************************************************************
416  *           DRIVE_FindDriveRootW
417  *
418  * Unicode version of DRIVE_FindDriveRoot.
419  */
420 int DRIVE_FindDriveRootW( LPCWSTR *path )
421 {
422     int drive, level, len;
423     WCHAR buffer[MAX_PATHNAME_LEN];
424     WCHAR *p;
425     struct stat st;
426
427     strcpyW( buffer, *path );
428     for (p = buffer; *p; p++) if (*p == '\\') *p = '/';
429     len = p - buffer;
430
431     /* strip off trailing slashes */
432     while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
433
434     for (;;)
435     {
436         char buffA[MAX_PATHNAME_LEN];
437
438         WideCharToMultiByte( CP_UNIXCP, 0, buffer, -1, buffA, sizeof(buffA), NULL, NULL );
439         if (stat( buffA, &st ) == 0 && S_ISDIR( st.st_mode ))
440         {
441             /* Find the drive */
442             for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
443             {
444                 if (!DOSDrives[drive].root) continue;
445
446                 if ((DOSDrives[drive].dev == st.st_dev) &&
447                     (DOSDrives[drive].ino == st.st_ino))
448                 {
449                     static const WCHAR rootW[] = {'\\',0};
450
451                     if (len == 1) len = 0;  /* preserve root slash in returned path */
452                     TRACE( "%s -> drive %c:, root=%s, name=%s\n",
453                            debugstr_w(*path), 'A' + drive, debugstr_w(buffer), debugstr_w(*path + len));
454                     *path += len;
455                     if (!**path) *path = rootW;
456                     return drive;
457                 }
458             }
459         }
460         if (len <= 1) return -1;  /* reached root */
461
462         level = 0;
463         while (level < 1)
464         {
465             static const WCHAR dotW[] = {'.',0};
466             static const WCHAR dotdotW[] = {'.','.',0};
467
468             /* find start of the last path component */
469             while (len > 1 && buffer[len - 1] != '/') len--;
470             if (!buffer[len]) break;  /* empty component -> reached root */
471             /* does removing it take us up a level? */
472             if (strcmpW( buffer + len, dotW ) != 0)
473                 level += strcmpW( buffer + len, dotdotW ) ? 1 : -1;
474             buffer[len] = 0;
475             /* strip off trailing slashes */
476             while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
477         }
478     }
479 }
480
481
482 /***********************************************************************
483  *           DRIVE_GetRoot
484  */
485 const char * DRIVE_GetRoot( int drive )
486 {
487     if (!DRIVE_IsValid( drive )) return NULL;
488     return DOSDrives[drive].root;
489 }
490
491
492 /***********************************************************************
493  *           DRIVE_GetDosCwd
494  */
495 LPCWSTR DRIVE_GetDosCwd( int drive )
496 {
497     TDB *pTask = GlobalLock16(GetCurrentTask());
498     if (!DRIVE_IsValid( drive )) return NULL;
499
500     /* Check if we need to change the directory to the new task. */
501     if (pTask && (pTask->curdrive & 0x80) &&    /* The task drive is valid */
502         ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
503         (DRIVE_LastTask != GetCurrentTask()))   /* and the task changed */
504     {
505         static const WCHAR rootW[] = {'\\',0};
506         WCHAR curdirW[MAX_PATH];
507         MultiByteToWideChar(CP_ACP, 0, pTask->curdir, -1, curdirW, MAX_PATH);
508         /* Perform the task-switch */
509         if (!DRIVE_Chdir( drive, curdirW )) DRIVE_Chdir( drive, rootW );
510         DRIVE_LastTask = GetCurrentTask();
511     }
512     return DOSDrives[drive].dos_cwd;
513 }
514
515
516 /***********************************************************************
517  *           DRIVE_GetUnixCwd
518  */
519 const char * DRIVE_GetUnixCwd( int drive )
520 {
521     TDB *pTask = GlobalLock16(GetCurrentTask());
522     if (!DRIVE_IsValid( drive )) return NULL;
523
524     /* Check if we need to change the directory to the new task. */
525     if (pTask && (pTask->curdrive & 0x80) &&    /* The task drive is valid */
526         ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
527         (DRIVE_LastTask != GetCurrentTask()))   /* and the task changed */
528     {
529         static const WCHAR rootW[] = {'\\',0};
530         WCHAR curdirW[MAX_PATH];
531         MultiByteToWideChar(CP_ACP, 0, pTask->curdir, -1, curdirW, MAX_PATH);
532         /* Perform the task-switch */
533         if (!DRIVE_Chdir( drive, curdirW )) DRIVE_Chdir( drive, rootW );
534         DRIVE_LastTask = GetCurrentTask();
535     }
536     return DOSDrives[drive].unix_cwd;
537 }
538
539
540 /***********************************************************************
541  *           DRIVE_GetDevice
542  */
543 const char * DRIVE_GetDevice( int drive )
544 {
545     return (DRIVE_IsValid( drive )) ? DOSDrives[drive].device : NULL;
546 }
547
548 /***********************************************************************
549  *           DRIVE_GetType
550  */
551 static UINT DRIVE_GetType( int drive )
552 {
553     if (!DRIVE_IsValid( drive )) return DRIVE_NO_ROOT_DIR;
554     return DOSDrives[drive].type;
555 }
556
557
558 /***********************************************************************
559  *           DRIVE_GetFlags
560  */
561 UINT DRIVE_GetFlags( int drive )
562 {
563     if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
564     return DOSDrives[drive].flags;
565 }
566
567 /***********************************************************************
568  *           DRIVE_Chdir
569  */
570 int DRIVE_Chdir( int drive, LPCWSTR path )
571 {
572     DOS_FULL_NAME full_name;
573     WCHAR buffer[MAX_PATHNAME_LEN];
574     LPSTR unix_cwd;
575     BY_HANDLE_FILE_INFORMATION info;
576     TDB *pTask = GlobalLock16(GetCurrentTask());
577
578     buffer[0] = 'A' + drive;
579     buffer[1] = ':';
580     buffer[2] = 0;
581     TRACE("(%s,%s)\n", debugstr_w(buffer), debugstr_w(path) );
582     strncpyW( buffer + 2, path, MAX_PATHNAME_LEN - 2 );
583     buffer[MAX_PATHNAME_LEN - 1] = 0; /* ensure 0 termination */
584
585     if (!DOSFS_GetFullName( buffer, TRUE, &full_name )) return 0;
586     if (!FILE_Stat( full_name.long_name, &info, NULL )) return 0;
587     if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
588     {
589         SetLastError( ERROR_FILE_NOT_FOUND );
590         return 0;
591     }
592     unix_cwd = full_name.long_name + strlen( DOSDrives[drive].root );
593     while (*unix_cwd == '/') unix_cwd++;
594
595     TRACE("(%c:): unix_cwd=%s dos_cwd=%s\n",
596             'A' + drive, unix_cwd, debugstr_w(full_name.short_name + 3) );
597
598     HeapFree( GetProcessHeap(), 0, DOSDrives[drive].dos_cwd );
599     HeapFree( GetProcessHeap(), 0, DOSDrives[drive].unix_cwd );
600     DOSDrives[drive].dos_cwd  = HeapAlloc(GetProcessHeap(), 0, (strlenW(full_name.short_name) - 2) * sizeof(WCHAR));
601     strcpyW(DOSDrives[drive].dos_cwd, full_name.short_name + 3);
602     DOSDrives[drive].unix_cwd = heap_strdup( unix_cwd );
603
604     if (drive == DRIVE_CurDrive)
605     {
606         UNICODE_STRING dirW;
607
608         RtlInitUnicodeString( &dirW, full_name.short_name );
609         RtlSetCurrentDirectory_U( &dirW );
610     }
611
612     if (pTask && (pTask->curdrive & 0x80) &&
613         ((pTask->curdrive & ~0x80) == drive))
614     {
615         WideCharToMultiByte(CP_ACP, 0, full_name.short_name + 2, -1,
616                             pTask->curdir, sizeof(pTask->curdir), NULL, NULL);
617         DRIVE_LastTask = GetCurrentTask();
618     }
619     return 1;
620 }
621
622
623 /***********************************************************************
624  *           DRIVE_GetFreeSpace
625  */
626 static int DRIVE_GetFreeSpace( int drive, PULARGE_INTEGER size,
627                                PULARGE_INTEGER available )
628 {
629     struct statvfs info;
630
631     if (!DRIVE_IsValid(drive))
632     {
633         SetLastError( ERROR_PATH_NOT_FOUND );
634         return 0;
635     }
636
637     if (statvfs( DOSDrives[drive].root, &info ) < 0)
638     {
639         FILE_SetDosError();
640         WARN("cannot do statvfs(%s)\n", DOSDrives[drive].root);
641         return 0;
642     }
643     size->QuadPart = RtlEnlargedUnsignedMultiply( info.f_frsize, info.f_blocks );
644     if (DOSDrives[drive].type == DRIVE_CDROM)
645         available->QuadPart = 0; /* ALWAYS 0, even if no real CD-ROM mounted there !! */
646     else
647         available->QuadPart = RtlEnlargedUnsignedMultiply( info.f_frsize, info.f_bavail );
648
649     return 1;
650 }
651
652 /***********************************************************************
653  *       DRIVE_GetCurrentDirectory
654  * Returns "X:\\path\\etc\\".
655  *
656  * Despite the API description, return required length including the
657  * terminating null when buffer too small. This is the real behaviour.
658 */
659 static UINT DRIVE_GetCurrentDirectory( UINT buflen, LPWSTR buf )
660 {
661     UINT ret;
662     LPCWSTR dos_cwd = DRIVE_GetDosCwd( DRIVE_GetCurrentDrive() );
663     static const WCHAR driveA_rootW[] = {'A',':','\\',0};
664
665     ret = strlenW(dos_cwd) + 3; /* length of WHOLE current directory */
666     if (ret >= buflen) return ret + 1;
667
668     strcpyW( buf, driveA_rootW );
669     buf[0] += DRIVE_GetCurrentDrive();
670     strcatW( buf, dos_cwd );
671     return ret;
672 }
673
674
675 /***********************************************************************
676  *           DRIVE_BuildEnv
677  *
678  * Build the environment array containing the drives' current directories.
679  * Resulting pointer must be freed with HeapFree.
680  */
681 WCHAR *DRIVE_BuildEnv(void)
682 {
683     int i, length = 0;
684     LPCWSTR cwd[MAX_DOS_DRIVES];
685     WCHAR *env, *p;
686
687     for (i = 0; i < MAX_DOS_DRIVES; i++)
688     {
689         if ((cwd[i] = DRIVE_GetDosCwd(i)) && cwd[i][0])
690             length += strlenW(cwd[i]) + 8;
691     }
692     if (!(env = HeapAlloc( GetProcessHeap(), 0, (length+1) * sizeof(WCHAR) ))) return NULL;
693     for (i = 0, p = env; i < MAX_DOS_DRIVES; i++)
694     {
695         if (cwd[i] && cwd[i][0])
696         {
697             *p++ = '='; *p++ = 'A' + i; *p++ = ':';
698             *p++ = '='; *p++ = 'A' + i; *p++ = ':'; *p++ = '\\';
699             strcpyW( p, cwd[i] );
700             p += strlenW(p) + 1;
701         }
702     }
703     *p = 0;
704     return env;
705 }
706
707
708 /***********************************************************************
709  *           GetDiskFreeSpaceW   (KERNEL32.@)
710  *
711  * Fails if expression resulting from current drive's dir and "root"
712  * is not a root dir of the target drive.
713  *
714  * UNDOC: setting some LPDWORDs to NULL is perfectly possible
715  * if the corresponding info is unneeded.
716  *
717  * FIXME: needs to support UNC names from Win95 OSR2 on.
718  *
719  * Behaviour under Win95a:
720  * CurrDir     root   result
721  * "E:\\TEST"  "E:"   FALSE
722  * "E:\\"      "E:"   TRUE
723  * "E:\\"      "E"    FALSE
724  * "E:\\"      "\\"   TRUE
725  * "E:\\TEST"  "\\"   TRUE
726  * "E:\\TEST"  ":\\"  FALSE
727  * "E:\\TEST"  "E:\\" TRUE
728  * "E:\\TEST"  ""     FALSE
729  * "E:\\"      ""     FALSE (!)
730  * "E:\\"      0x0    TRUE
731  * "E:\\TEST"  0x0    TRUE  (!)
732  * "E:\\TEST"  "C:"   TRUE  (when CurrDir of "C:" set to "\\")
733  * "E:\\TEST"  "C:"   FALSE (when CurrDir of "C:" set to "\\TEST")
734  */
735 BOOL WINAPI GetDiskFreeSpaceW( LPCWSTR root, LPDWORD cluster_sectors,
736                                    LPDWORD sector_bytes, LPDWORD free_clusters,
737                                    LPDWORD total_clusters )
738 {
739     int drive, sec_size;
740     ULARGE_INTEGER size,available;
741     LPCWSTR path;
742     DWORD cluster_sec;
743
744     TRACE("%s,%p,%p,%p,%p\n", debugstr_w(root), cluster_sectors, sector_bytes,
745           free_clusters, total_clusters);
746
747     if (!root || root[0] == '\\' || root[0] == '/')
748         drive = DRIVE_GetCurrentDrive();
749     else
750     if (root[0] && root[1] == ':') /* root contains drive tag */
751     {
752         drive = toupperW(root[0]) - 'A';
753         path = &root[2];
754         if (path[0] == '\0')
755         {
756             path = DRIVE_GetDosCwd(drive);
757             if (!path)
758             {
759                 SetLastError(ERROR_PATH_NOT_FOUND);
760                 return FALSE;
761             }
762         }
763         else
764         if (path[0] == '\\')
765             path++;
766
767         if (path[0]) /* oops, we are in a subdir */
768         {
769             SetLastError(ERROR_INVALID_NAME);
770             return FALSE;
771         }
772     }
773     else
774     {
775         if (!root[0])
776             SetLastError(ERROR_PATH_NOT_FOUND);
777         else
778             SetLastError(ERROR_INVALID_NAME);
779         return FALSE;
780     }
781
782     if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
783
784     /* Cap the size and available at 2GB as per specs.  */
785     if ((size.u.HighPart) ||(size.u.LowPart > 0x7fffffff))
786     {
787         size.u.HighPart = 0;
788         size.u.LowPart = 0x7fffffff;
789     }
790     if ((available.u.HighPart) ||(available.u.LowPart > 0x7fffffff))
791     {
792         available.u.HighPart =0;
793         available.u.LowPart = 0x7fffffff;
794     }
795     sec_size = (DRIVE_GetType(drive)==DRIVE_CDROM) ? 2048 : 512;
796     size.u.LowPart            /= sec_size;
797     available.u.LowPart       /= sec_size;
798     /* FIXME: probably have to adjust those variables too for CDFS */
799     cluster_sec = 1;
800     while (cluster_sec * 65536 < size.u.LowPart) cluster_sec *= 2;
801
802     if (cluster_sectors)
803         *cluster_sectors = cluster_sec;
804     if (sector_bytes)
805         *sector_bytes    = sec_size;
806     if (free_clusters)
807         *free_clusters   = available.u.LowPart / cluster_sec;
808     if (total_clusters)
809         *total_clusters  = size.u.LowPart / cluster_sec;
810     return TRUE;
811 }
812
813
814 /***********************************************************************
815  *           GetDiskFreeSpaceA   (KERNEL32.@)
816  */
817 BOOL WINAPI GetDiskFreeSpaceA( LPCSTR root, LPDWORD cluster_sectors,
818                                    LPDWORD sector_bytes, LPDWORD free_clusters,
819                                    LPDWORD total_clusters )
820 {
821     UNICODE_STRING rootW;
822     BOOL ret = FALSE;
823
824     if (root)
825     {
826         if(!RtlCreateUnicodeStringFromAsciiz(&rootW, root))
827         {
828             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
829             return FALSE;
830         }
831     }
832     else
833         rootW.Buffer = NULL;
834
835     ret = GetDiskFreeSpaceW(rootW.Buffer, cluster_sectors, sector_bytes,
836                             free_clusters, total_clusters );
837     RtlFreeUnicodeString(&rootW);
838
839     return ret;
840 }
841
842
843 /***********************************************************************
844  *           GetDiskFreeSpaceExW   (KERNEL32.@)
845  *
846  *  This function is used to acquire the size of the available and
847  *  total space on a logical volume.
848  *
849  * RETURNS
850  *
851  *  Zero on failure, nonzero upon success. Use GetLastError to obtain
852  *  detailed error information.
853  *
854  */
855 BOOL WINAPI GetDiskFreeSpaceExW( LPCWSTR root,
856                                      PULARGE_INTEGER avail,
857                                      PULARGE_INTEGER total,
858                                      PULARGE_INTEGER totalfree)
859 {
860     int drive;
861     ULARGE_INTEGER size,available;
862
863     if (!root) drive = DRIVE_GetCurrentDrive();
864     else
865     { /* C: always works for GetDiskFreeSpaceEx */
866         if ((root[1]) && ((root[1] != ':') || (root[2] && root[2] != '\\')))
867         {
868             FIXME("there are valid root names which are not supported yet\n");
869             /* ..like UNC names, for instance. */
870
871             WARN("invalid root '%s'\n", debugstr_w(root));
872             return FALSE;
873         }
874         drive = toupperW(root[0]) - 'A';
875     }
876
877     if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
878
879     if (total)
880     {
881         total->u.HighPart = size.u.HighPart;
882         total->u.LowPart = size.u.LowPart;
883     }
884
885     if (totalfree)
886     {
887         totalfree->u.HighPart = available.u.HighPart;
888         totalfree->u.LowPart = available.u.LowPart;
889     }
890
891     if (avail)
892     {
893         if (FIXME_ON(dosfs))
894         {
895             /* On Windows2000, we need to check the disk quota
896                allocated for the user owning the calling process. We
897                don't want to be more obtrusive than necessary with the
898                FIXME messages, so don't print the FIXME unless Wine is
899                actually masquerading as Windows2000. */
900
901             RTL_OSVERSIONINFOEXW ovi;
902             ovi.dwOSVersionInfoSize = sizeof(ovi);
903             if (RtlGetVersion(&ovi))
904             {
905               if (ovi.dwPlatformId == VER_PLATFORM_WIN32_NT && ovi.dwMajorVersion > 4)
906                   FIXME("no per-user quota support yet\n");
907             }
908         }
909
910         /* Quick hack, should eventually be fixed to work 100% with
911            Windows2000 (see comment above). */
912         avail->u.HighPart = available.u.HighPart;
913         avail->u.LowPart = available.u.LowPart;
914     }
915
916     return TRUE;
917 }
918
919 /***********************************************************************
920  *           GetDiskFreeSpaceExA   (KERNEL32.@)
921  */
922 BOOL WINAPI GetDiskFreeSpaceExA( LPCSTR root, PULARGE_INTEGER avail,
923                                      PULARGE_INTEGER total,
924                                      PULARGE_INTEGER  totalfree)
925 {
926     UNICODE_STRING rootW;
927     BOOL ret;
928
929     if (root) RtlCreateUnicodeStringFromAsciiz(&rootW, root);
930     else rootW.Buffer = NULL;
931
932     ret = GetDiskFreeSpaceExW( rootW.Buffer, avail, total, totalfree);
933
934     RtlFreeUnicodeString(&rootW);
935     return ret;
936 }
937
938 /***********************************************************************
939  *           GetDriveTypeW   (KERNEL32.@)
940  *
941  * Returns the type of the disk drive specified. If root is NULL the
942  * root of the current directory is used.
943  *
944  * RETURNS
945  *
946  *  Type of drive (from Win32 SDK):
947  *
948  *   DRIVE_UNKNOWN     unable to find out anything about the drive
949  *   DRIVE_NO_ROOT_DIR nonexistent root dir
950  *   DRIVE_REMOVABLE   the disk can be removed from the machine
951  *   DRIVE_FIXED       the disk can not be removed from the machine
952  *   DRIVE_REMOTE      network disk
953  *   DRIVE_CDROM       CDROM drive
954  *   DRIVE_RAMDISK     virtual disk in RAM
955  */
956 UINT WINAPI GetDriveTypeW(LPCWSTR root) /* [in] String describing drive */
957 {
958     int drive;
959     TRACE("(%s)\n", debugstr_w(root));
960
961     if (NULL == root) drive = DRIVE_GetCurrentDrive();
962     else
963     {
964         if ((root[1]) && (root[1] != ':'))
965         {
966             WARN("invalid root %s\n", debugstr_w(root));
967             return DRIVE_NO_ROOT_DIR;
968         }
969         drive = toupperW(root[0]) - 'A';
970     }
971     return DRIVE_GetType(drive);
972 }
973
974
975 /***********************************************************************
976  *           GetDriveTypeA   (KERNEL32.@)
977  */
978 UINT WINAPI GetDriveTypeA( LPCSTR root )
979 {
980     UNICODE_STRING rootW;
981     UINT ret = 0;
982
983     if (root)
984     {
985         if( !RtlCreateUnicodeStringFromAsciiz(&rootW, root))
986         {
987             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
988             return 0;
989         }
990     }
991     else
992         rootW.Buffer = NULL;
993
994     ret = GetDriveTypeW(rootW.Buffer);
995
996     RtlFreeUnicodeString(&rootW);
997     return ret;
998
999 }
1000
1001
1002 /***********************************************************************
1003  *           GetCurrentDirectory   (KERNEL.411)
1004  */
1005 UINT16 WINAPI GetCurrentDirectory16( UINT16 buflen, LPSTR buf )
1006 {
1007     WCHAR cur_dirW[MAX_PATH];
1008
1009     DRIVE_GetCurrentDirectory(MAX_PATH, cur_dirW);
1010     return (UINT16)WideCharToMultiByte(CP_ACP, 0, cur_dirW, -1, buf, buflen, NULL, NULL);
1011 }
1012
1013
1014 /***********************************************************************
1015  *           GetCurrentDirectoryW   (KERNEL32.@)
1016  */
1017 UINT WINAPI GetCurrentDirectoryW( UINT buflen, LPWSTR buf )
1018 {
1019     UINT ret;
1020     WCHAR longname[MAX_PATHNAME_LEN];
1021     WCHAR shortname[MAX_PATHNAME_LEN];
1022
1023     ret = DRIVE_GetCurrentDirectory(MAX_PATHNAME_LEN, shortname);
1024     if ( ret > MAX_PATHNAME_LEN ) {
1025       ERR_(file)("pathnamelength (%d) > MAX_PATHNAME_LEN!\n", ret );
1026       return ret;
1027     }
1028     GetLongPathNameW(shortname, longname, MAX_PATHNAME_LEN);
1029     ret = strlenW( longname ) + 1;
1030     if (ret > buflen) return ret;
1031     strcpyW(buf, longname);
1032     return ret - 1;
1033 }
1034
1035 /***********************************************************************
1036  *           GetCurrentDirectoryA   (KERNEL32.@)
1037  */
1038 UINT WINAPI GetCurrentDirectoryA( UINT buflen, LPSTR buf )
1039 {
1040     WCHAR bufferW[MAX_PATH];
1041     DWORD ret, retW;
1042
1043     retW = GetCurrentDirectoryW(MAX_PATH, bufferW);
1044
1045     if (!retW)
1046         ret = 0;
1047     else if (retW > MAX_PATH)
1048     {
1049         SetLastError(ERROR_FILENAME_EXCED_RANGE);
1050         ret = 0;
1051     }
1052     else
1053     {
1054         ret = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
1055         if (buflen >= ret)
1056         {
1057             WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buf, buflen, NULL, NULL);
1058             ret--; /* length without 0 */
1059         }
1060     }
1061     return ret;
1062 }
1063
1064
1065 /***********************************************************************
1066  *           SetCurrentDirectoryW   (KERNEL32.@)
1067  */
1068 BOOL WINAPI SetCurrentDirectoryW( LPCWSTR dir )
1069 {
1070     int drive, olddrive = DRIVE_GetCurrentDrive();
1071
1072     if (!dir)
1073     {
1074         SetLastError(ERROR_INVALID_PARAMETER);
1075         return FALSE;
1076     }
1077     if (dir[0] && (dir[1]==':'))
1078     {
1079         drive = toupperW( *dir ) - 'A';
1080         dir += 2;
1081     }
1082     else
1083         drive = olddrive;
1084
1085     /* WARNING: we need to set the drive before the dir, as DRIVE_Chdir
1086        sets pTask->curdir only if pTask->curdrive is drive */
1087     if (!(DRIVE_SetCurrentDrive( drive )))
1088         return FALSE;
1089
1090     /* FIXME: what about empty strings? Add a \\ ? */
1091     if (!DRIVE_Chdir( drive, dir )) {
1092         DRIVE_SetCurrentDrive(olddrive);
1093         return FALSE;
1094     }
1095     return TRUE;
1096 }
1097
1098
1099 /***********************************************************************
1100  *           SetCurrentDirectoryA   (KERNEL32.@)
1101  */
1102 BOOL WINAPI SetCurrentDirectoryA( LPCSTR dir )
1103 {
1104     UNICODE_STRING dirW;
1105     BOOL ret = FALSE;
1106
1107     if (!dir)
1108     {
1109         SetLastError(ERROR_INVALID_PARAMETER);
1110         return FALSE;
1111     }
1112
1113     if (RtlCreateUnicodeStringFromAsciiz(&dirW, dir))
1114     {
1115         ret = SetCurrentDirectoryW(dirW.Buffer);
1116         RtlFreeUnicodeString(&dirW);
1117     }
1118     else
1119         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1120     return ret;
1121 }
1122
1123
1124 /***********************************************************************
1125  *           GetLogicalDriveStringsA   (KERNEL32.@)
1126  */
1127 UINT WINAPI GetLogicalDriveStringsA( UINT len, LPSTR buffer )
1128 {
1129     int drive, count;
1130
1131     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1132         if (DRIVE_IsValid(drive)) count++;
1133     if ((count * 4) + 1 <= len)
1134     {
1135         LPSTR p = buffer;
1136         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1137             if (DRIVE_IsValid(drive))
1138             {
1139                 *p++ = 'a' + drive;
1140                 *p++ = ':';
1141                 *p++ = '\\';
1142                 *p++ = '\0';
1143             }
1144         *p = '\0';
1145         return count * 4;
1146     }
1147     else
1148         return (count * 4) + 1; /* account for terminating null */
1149     /* The API tells about these different return values */
1150 }
1151
1152
1153 /***********************************************************************
1154  *           GetLogicalDriveStringsW   (KERNEL32.@)
1155  */
1156 UINT WINAPI GetLogicalDriveStringsW( UINT len, LPWSTR buffer )
1157 {
1158     int drive, count;
1159
1160     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1161         if (DRIVE_IsValid(drive)) count++;
1162     if (count * 4 * sizeof(WCHAR) <= len)
1163     {
1164         LPWSTR p = buffer;
1165         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1166             if (DRIVE_IsValid(drive))
1167             {
1168                 *p++ = (WCHAR)('a' + drive);
1169                 *p++ = (WCHAR)':';
1170                 *p++ = (WCHAR)'\\';
1171                 *p++ = (WCHAR)'\0';
1172             }
1173         *p = (WCHAR)'\0';
1174     }
1175     return count * 4 * sizeof(WCHAR);
1176 }
1177
1178
1179 /***********************************************************************
1180  *           GetLogicalDrives   (KERNEL32.@)
1181  */
1182 DWORD WINAPI GetLogicalDrives(void)
1183 {
1184     DWORD ret = 0;
1185     int drive;
1186
1187     for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1188     {
1189         if ( (DRIVE_IsValid(drive)) ||
1190             (DOSDrives[drive].type == DRIVE_CDROM)) /* audio CD is also valid */
1191             ret |= (1 << drive);
1192     }
1193     return ret;
1194 }