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