Fixed a couple of crashes.
[wine.git] / files / drive.c
blob8a875453ddeba52b40c818f03f359cb5977d709b
1 /*
2 * DOS drives handling functions
4 * Copyright 1993 Erik Bos
5 * Copyright 1996 Alexandre Julliard
7 * Label & serial number read support.
8 * (c) 1999 Petr Tomasek <tomasek@etf.cuni.cz>
9 * (c) 2000 Andreas Mohr (changes)
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.
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.
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
26 #include "config.h"
27 #include "wine/port.h"
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
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 "drive.h"
59 #include "file.h"
60 #include "wine/unicode.h"
61 #include "wine/library.h"
62 #include "wine/server.h"
63 #include "wine/debug.h"
65 WINE_DEFAULT_DEBUG_CHANNEL(dosfs);
66 WINE_DECLARE_DEBUG_CHANNEL(file);
68 typedef struct
70 char *root; /* root dir in Unix format without trailing / */
71 LPWSTR dos_cwd; /* cwd in DOS format without leading or trailing \ */
72 char *unix_cwd; /* cwd in Unix format without leading or trailing / */
73 char *device; /* raw device path */
74 UINT type; /* drive type */
75 UINT flags; /* drive flags */
76 dev_t dev; /* unix device number */
77 ino_t ino; /* unix inode number */
78 } DOSDRIVE;
81 static const WCHAR DRIVE_Types[][8] =
83 { 0 }, /* DRIVE_UNKNOWN */
84 { 0 }, /* DRIVE_NO_ROOT_DIR */
85 {'f','l','o','p','p','y',0}, /* DRIVE_REMOVABLE */
86 {'h','d',0}, /* DRIVE_FIXED */
87 {'n','e','t','w','o','r','k',0}, /* DRIVE_REMOTE */
88 {'c','d','r','o','m',0}, /* DRIVE_CDROM */
89 {'r','a','m','d','i','s','k',0} /* DRIVE_RAMDISK */
93 /* Known filesystem types */
95 typedef struct
97 const WCHAR name[6];
98 UINT flags;
99 } FS_DESCR;
101 static const FS_DESCR DRIVE_Filesystems[] =
103 { {'u','n','i','x',0}, DRIVE_CASE_SENSITIVE | DRIVE_CASE_PRESERVING },
104 { {'m','s','d','o','s',0}, DRIVE_SHORT_NAMES },
105 { {'d','o','s',0}, DRIVE_SHORT_NAMES },
106 { {'f','a','t',0}, DRIVE_SHORT_NAMES },
107 { {'v','f','a','t',0}, DRIVE_CASE_PRESERVING },
108 { {'w','i','n','9','5',0}, DRIVE_CASE_PRESERVING },
109 { { 0 }, 0 }
113 static DOSDRIVE DOSDrives[MAX_DOS_DRIVES];
114 static int DRIVE_CurDrive = -1;
116 static HTASK16 DRIVE_LastTask = 0;
118 /* strdup on the process heap */
119 inline static char *heap_strdup( const char *str )
121 INT len = strlen(str) + 1;
122 LPSTR p = HeapAlloc( GetProcessHeap(), 0, len );
123 if (p) memcpy( p, str, len );
124 return p;
127 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
129 extern void CDROM_InitRegistry(int dev);
131 /***********************************************************************
132 * DRIVE_GetDriveType
134 static inline UINT DRIVE_GetDriveType( INT drive, LPCWSTR value )
136 int i;
138 for (i = 0; i < sizeof(DRIVE_Types)/sizeof(DRIVE_Types[0]); i++)
140 if (!strcmpiW( value, DRIVE_Types[i] )) return i;
142 MESSAGE("Drive %c: unknown drive type %s, defaulting to 'hd'.\n",
143 'A' + drive, debugstr_w(value) );
144 return DRIVE_FIXED;
148 /***********************************************************************
149 * DRIVE_GetFSFlags
151 static UINT DRIVE_GetFSFlags( INT drive, LPCWSTR value )
153 const FS_DESCR *descr;
155 for (descr = DRIVE_Filesystems; *descr->name; descr++)
156 if (!strcmpiW( value, descr->name )) return descr->flags;
157 MESSAGE("Drive %c: unknown filesystem type %s, defaulting to 'win95'.\n",
158 'A' + drive, debugstr_w(value) );
159 return DRIVE_CASE_PRESERVING;
163 /***********************************************************************
164 * DRIVE_Init
166 int DRIVE_Init(void)
168 int i, len, count = 0;
169 WCHAR driveW[] = {'M','a','c','h','i','n','e','\\','S','o','f','t','w','a','r','e','\\',
170 'W','i','n','e','\\','W','i','n','e','\\',
171 'C','o','n','f','i','g','\\','D','r','i','v','e',' ','A',0};
172 WCHAR drive_env[] = {'=','A',':',0};
173 WCHAR path[MAX_PATHNAME_LEN];
174 char tmp[MAX_PATHNAME_LEN*sizeof(WCHAR) + sizeof(KEY_VALUE_PARTIAL_INFORMATION)];
175 struct stat drive_stat_buffer;
176 WCHAR *p;
177 DOSDRIVE *drive;
178 HKEY hkey;
179 DWORD dummy;
180 OBJECT_ATTRIBUTES attr;
181 UNICODE_STRING nameW;
183 static const WCHAR PathW[] = {'P','a','t','h',0};
184 static const WCHAR TypeW[] = {'T','y','p','e',0};
185 static const WCHAR FilesystemW[] = {'F','i','l','e','s','y','s','t','e','m',0};
186 static const WCHAR DeviceW[] = {'D','e','v','i','c','e',0};
187 static const WCHAR FailReadOnlyW[] = {'F','a','i','l','R','e','a','d','O','n','l','y',0};
189 attr.Length = sizeof(attr);
190 attr.RootDirectory = 0;
191 attr.ObjectName = &nameW;
192 attr.Attributes = 0;
193 attr.SecurityDescriptor = NULL;
194 attr.SecurityQualityOfService = NULL;
196 for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
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;
202 /* Get the root path */
203 RtlInitUnicodeString( &nameW, PathW );
204 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
206 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
207 ExpandEnvironmentStringsW( data, path, sizeof(path)/sizeof(WCHAR) );
209 p = path + strlenW(path) - 1;
210 while ((p > path) && (*p == '/')) *p-- = '\0';
212 if (path[0] == '/')
214 len = WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL);
215 drive->root = HeapAlloc(GetProcessHeap(), 0, len);
216 WideCharToMultiByte(CP_UNIXCP, 0, path, -1, drive->root, len, NULL, NULL);
218 else
220 /* relative paths are relative to config dir */
221 const char *config = wine_get_config_dir();
222 len = strlen(config);
223 len += WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL) + 2;
224 drive->root = HeapAlloc( GetProcessHeap(), 0, len );
225 len -= sprintf( drive->root, "%s/", config );
226 WideCharToMultiByte(CP_UNIXCP, 0, path, -1,
227 drive->root + strlen(drive->root), len, NULL, NULL);
230 if (stat( drive->root, &drive_stat_buffer ))
232 MESSAGE("Could not stat %s (%s), ignoring drive %c:\n",
233 drive->root, strerror(errno), 'A' + i);
234 HeapFree( GetProcessHeap(), 0, drive->root );
235 drive->root = NULL;
236 goto next;
238 if (!S_ISDIR(drive_stat_buffer.st_mode))
240 MESSAGE("%s is not a directory, ignoring drive %c:\n",
241 drive->root, 'A' + i );
242 HeapFree( GetProcessHeap(), 0, drive->root );
243 drive->root = NULL;
244 goto next;
247 drive->dos_cwd = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(drive->dos_cwd[0]));
248 drive->unix_cwd = heap_strdup( "" );
249 drive->device = NULL;
250 drive->flags = 0;
251 drive->dev = drive_stat_buffer.st_dev;
252 drive->ino = drive_stat_buffer.st_ino;
254 /* Get the drive type */
255 RtlInitUnicodeString( &nameW, TypeW );
256 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
258 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
259 drive->type = DRIVE_GetDriveType( i, data );
261 else drive->type = DRIVE_FIXED;
263 /* Get the filesystem type */
264 RtlInitUnicodeString( &nameW, FilesystemW );
265 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
267 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
268 drive->flags = DRIVE_GetFSFlags( i, data );
270 else drive->flags = DRIVE_CASE_PRESERVING;
272 /* Get the device */
273 RtlInitUnicodeString( &nameW, DeviceW );
274 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
276 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
277 len = WideCharToMultiByte(CP_UNIXCP, 0, data, -1, NULL, 0, NULL, NULL);
278 drive->device = HeapAlloc(GetProcessHeap(), 0, len);
279 WideCharToMultiByte(CP_UNIXCP, 0, data, -1, drive->device, len, NULL, NULL);
281 if (drive->type == DRIVE_CDROM)
283 int cd_fd;
284 if ((cd_fd = open(drive->device, O_RDONLY|O_NONBLOCK)) != -1)
286 CDROM_InitRegistry(cd_fd);
287 close(cd_fd);
292 /* Get the FailReadOnly flag */
293 RtlInitUnicodeString( &nameW, FailReadOnlyW );
294 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
296 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
297 if (IS_OPTION_TRUE(data[0])) drive->flags |= DRIVE_FAIL_READ_ONLY;
300 /* Make the first hard disk the current drive */
301 if ((DRIVE_CurDrive == -1) && (drive->type == DRIVE_FIXED))
302 DRIVE_CurDrive = i;
304 count++;
305 TRACE("Drive %c: path=%s type=%s flags=%08x dev=%x ino=%x\n",
306 'A' + i, drive->root, debugstr_w(DRIVE_Types[drive->type]),
307 drive->flags, (int)drive->dev, (int)drive->ino );
310 next:
311 NtClose( hkey );
314 if (!count)
316 MESSAGE("Warning: no valid DOS drive found, check your configuration file.\n" );
317 /* Create a C drive pointing to Unix root dir */
318 DOSDrives[2].root = heap_strdup( "/" );
319 DOSDrives[2].dos_cwd = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DOSDrives[2].dos_cwd[0]));
320 DOSDrives[2].unix_cwd = heap_strdup( "" );
321 DOSDrives[2].type = DRIVE_FIXED;
322 DOSDrives[2].device = NULL;
323 DOSDrives[2].flags = 0;
324 DRIVE_CurDrive = 2;
327 /* Make sure the current drive is valid */
328 if (DRIVE_CurDrive == -1)
330 for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
332 if (drive->root && !(drive->flags & DRIVE_DISABLED))
334 DRIVE_CurDrive = i;
335 break;
340 /* get current working directory info for all drives */
341 for (i = 0; i < MAX_DOS_DRIVES; i++, drive_env[1]++)
343 if (!GetEnvironmentVariableW(drive_env, path, MAX_PATHNAME_LEN)) continue;
344 /* sanity check */
345 if (toupperW(path[0]) != drive_env[1] || path[1] != ':') continue;
346 DRIVE_Chdir( i, path + 2 );
348 return 1;
352 /***********************************************************************
353 * DRIVE_IsValid
355 int DRIVE_IsValid( int drive )
357 if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
358 return (DOSDrives[drive].root &&
359 !(DOSDrives[drive].flags & DRIVE_DISABLED));
363 /***********************************************************************
364 * DRIVE_GetCurrentDrive
366 int DRIVE_GetCurrentDrive(void)
368 TDB *pTask = GlobalLock16(GetCurrentTask());
369 if (pTask && (pTask->curdrive & 0x80)) return pTask->curdrive & ~0x80;
370 return DRIVE_CurDrive;
374 /***********************************************************************
375 * DRIVE_SetCurrentDrive
377 int DRIVE_SetCurrentDrive( int drive )
379 TDB *pTask = GlobalLock16(GetCurrentTask());
380 if (!DRIVE_IsValid( drive ))
382 SetLastError( ERROR_INVALID_DRIVE );
383 return 0;
385 TRACE("%c:\n", 'A' + drive );
386 DRIVE_CurDrive = drive;
387 if (pTask) pTask->curdrive = drive | 0x80;
388 return 1;
392 /***********************************************************************
393 * DRIVE_FindDriveRoot
395 * Find a drive for which the root matches the beginning of the given path.
396 * This can be used to translate a Unix path into a drive + DOS path.
397 * Return value is the drive, or -1 on error. On success, path is modified
398 * to point to the beginning of the DOS path.
400 * Note: path must be in the encoding of the underlying Unix file system.
402 int DRIVE_FindDriveRoot( const char **path )
404 /* Starting with the full path, check if the device and inode match any of
405 * the wine 'drives'. If not then remove the last path component and try
406 * again. If the last component was a '..' then skip a normal component
407 * since it's a directory that's ascended back out of.
409 int drive, level, len;
410 char buffer[MAX_PATHNAME_LEN];
411 char *p;
412 struct stat st;
414 strcpy( buffer, *path );
415 for (p = buffer; *p; p++) if (*p == '\\') *p = '/';
416 len = p - buffer;
418 /* strip off trailing slashes */
419 while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
421 for (;;)
423 /* Find the drive */
424 if (stat( buffer, &st ) == 0 && S_ISDIR( st.st_mode ))
426 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
428 if (!DOSDrives[drive].root ||
429 (DOSDrives[drive].flags & DRIVE_DISABLED))
430 continue;
432 if ((DOSDrives[drive].dev == st.st_dev) &&
433 (DOSDrives[drive].ino == st.st_ino))
435 if (len == 1) len = 0; /* preserve root slash in returned path */
436 TRACE( "%s -> drive %c:, root='%s', name='%s'\n",
437 *path, 'A' + drive, buffer, *path + len);
438 *path += len;
439 if (!**path) *path = "\\";
440 return drive;
444 if (len <= 1) return -1; /* reached root */
446 level = 0;
447 while (level < 1)
449 /* find start of the last path component */
450 while (len > 1 && buffer[len - 1] != '/') len--;
451 if (!buffer[len]) break; /* empty component -> reached root */
452 /* does removing it take us up a level? */
453 if (strcmp( buffer + len, "." ) != 0)
454 level += strcmp( buffer + len, ".." ) ? 1 : -1;
455 buffer[len] = 0;
456 /* strip off trailing slashes */
457 while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
463 /***********************************************************************
464 * DRIVE_FindDriveRootW
466 * Unicode version of DRIVE_FindDriveRoot.
468 int DRIVE_FindDriveRootW( LPCWSTR *path )
470 int drive, level, len;
471 WCHAR buffer[MAX_PATHNAME_LEN];
472 WCHAR *p;
473 struct stat st;
475 strcpyW( buffer, *path );
476 for (p = buffer; *p; p++) if (*p == '\\') *p = '/';
477 len = p - buffer;
479 /* strip off trailing slashes */
480 while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
482 for (;;)
484 char buffA[MAX_PATHNAME_LEN];
486 WideCharToMultiByte( CP_UNIXCP, 0, buffer, -1, buffA, sizeof(buffA), NULL, NULL );
487 if (stat( buffA, &st ) == 0 && S_ISDIR( st.st_mode ))
489 /* Find the drive */
490 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
492 if (!DOSDrives[drive].root ||
493 (DOSDrives[drive].flags & DRIVE_DISABLED))
494 continue;
496 if ((DOSDrives[drive].dev == st.st_dev) &&
497 (DOSDrives[drive].ino == st.st_ino))
499 static const WCHAR rootW[] = {'\\',0};
501 if (len == 1) len = 0; /* preserve root slash in returned path */
502 TRACE( "%s -> drive %c:, root=%s, name=%s\n",
503 debugstr_w(*path), 'A' + drive, debugstr_w(buffer), debugstr_w(*path + len));
504 *path += len;
505 if (!**path) *path = rootW;
506 return drive;
510 if (len <= 1) return -1; /* reached root */
512 level = 0;
513 while (level < 1)
515 static const WCHAR dotW[] = {'.',0};
516 static const WCHAR dotdotW[] = {'.','.',0};
518 /* find start of the last path component */
519 while (len > 1 && buffer[len - 1] != '/') len--;
520 if (!buffer[len]) break; /* empty component -> reached root */
521 /* does removing it take us up a level? */
522 if (strcmpW( buffer + len, dotW ) != 0)
523 level += strcmpW( buffer + len, dotdotW ) ? 1 : -1;
524 buffer[len] = 0;
525 /* strip off trailing slashes */
526 while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
532 /***********************************************************************
533 * DRIVE_GetRoot
535 const char * DRIVE_GetRoot( int drive )
537 if (!DRIVE_IsValid( drive )) return NULL;
538 return DOSDrives[drive].root;
542 /***********************************************************************
543 * DRIVE_GetDosCwd
545 LPCWSTR DRIVE_GetDosCwd( int drive )
547 TDB *pTask = GlobalLock16(GetCurrentTask());
548 if (!DRIVE_IsValid( drive )) return NULL;
550 /* Check if we need to change the directory to the new task. */
551 if (pTask && (pTask->curdrive & 0x80) && /* The task drive is valid */
552 ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
553 (DRIVE_LastTask != GetCurrentTask())) /* and the task changed */
555 static const WCHAR rootW[] = {'\\',0};
556 WCHAR curdirW[MAX_PATH];
557 MultiByteToWideChar(CP_ACP, 0, pTask->curdir, -1, curdirW, MAX_PATH);
558 /* Perform the task-switch */
559 if (!DRIVE_Chdir( drive, curdirW )) DRIVE_Chdir( drive, rootW );
560 DRIVE_LastTask = GetCurrentTask();
562 return DOSDrives[drive].dos_cwd;
566 /***********************************************************************
567 * DRIVE_GetUnixCwd
569 const char * DRIVE_GetUnixCwd( int drive )
571 TDB *pTask = GlobalLock16(GetCurrentTask());
572 if (!DRIVE_IsValid( drive )) return NULL;
574 /* Check if we need to change the directory to the new task. */
575 if (pTask && (pTask->curdrive & 0x80) && /* The task drive is valid */
576 ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
577 (DRIVE_LastTask != GetCurrentTask())) /* and the task changed */
579 static const WCHAR rootW[] = {'\\',0};
580 WCHAR curdirW[MAX_PATH];
581 MultiByteToWideChar(CP_ACP, 0, pTask->curdir, -1, curdirW, MAX_PATH);
582 /* Perform the task-switch */
583 if (!DRIVE_Chdir( drive, curdirW )) DRIVE_Chdir( drive, rootW );
584 DRIVE_LastTask = GetCurrentTask();
586 return DOSDrives[drive].unix_cwd;
590 /***********************************************************************
591 * DRIVE_GetDevice
593 const char * DRIVE_GetDevice( int drive )
595 return (DRIVE_IsValid( drive )) ? DOSDrives[drive].device : NULL;
598 /***********************************************************************
599 * DRIVE_GetType
601 static UINT DRIVE_GetType( int drive )
603 if (!DRIVE_IsValid( drive )) return DRIVE_NO_ROOT_DIR;
604 return DOSDrives[drive].type;
608 /***********************************************************************
609 * DRIVE_GetFlags
611 UINT DRIVE_GetFlags( int drive )
613 if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
614 return DOSDrives[drive].flags;
617 /***********************************************************************
618 * DRIVE_Chdir
620 int DRIVE_Chdir( int drive, LPCWSTR path )
622 DOS_FULL_NAME full_name;
623 WCHAR buffer[MAX_PATHNAME_LEN];
624 LPSTR unix_cwd;
625 BY_HANDLE_FILE_INFORMATION info;
626 TDB *pTask = GlobalLock16(GetCurrentTask());
628 buffer[0] = 'A' + drive;
629 buffer[1] = ':';
630 buffer[2] = 0;
631 TRACE("(%s,%s)\n", debugstr_w(buffer), debugstr_w(path) );
632 strncpyW( buffer + 2, path, MAX_PATHNAME_LEN - 2 );
633 buffer[MAX_PATHNAME_LEN - 1] = 0; /* ensure 0 termination */
635 if (!DOSFS_GetFullName( buffer, TRUE, &full_name )) return 0;
636 if (!FILE_Stat( full_name.long_name, &info, NULL )) return 0;
637 if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
639 SetLastError( ERROR_FILE_NOT_FOUND );
640 return 0;
642 unix_cwd = full_name.long_name + strlen( DOSDrives[drive].root );
643 while (*unix_cwd == '/') unix_cwd++;
645 TRACE("(%c:): unix_cwd=%s dos_cwd=%s\n",
646 'A' + drive, unix_cwd, debugstr_w(full_name.short_name + 3) );
648 HeapFree( GetProcessHeap(), 0, DOSDrives[drive].dos_cwd );
649 HeapFree( GetProcessHeap(), 0, DOSDrives[drive].unix_cwd );
650 DOSDrives[drive].dos_cwd = HeapAlloc(GetProcessHeap(), 0, (strlenW(full_name.short_name) - 2) * sizeof(WCHAR));
651 strcpyW(DOSDrives[drive].dos_cwd, full_name.short_name + 3);
652 DOSDrives[drive].unix_cwd = heap_strdup( unix_cwd );
654 if (drive == DRIVE_CurDrive)
656 UNICODE_STRING dirW;
658 RtlInitUnicodeString( &dirW, full_name.short_name );
659 RtlSetCurrentDirectory_U( &dirW );
662 if (pTask && (pTask->curdrive & 0x80) &&
663 ((pTask->curdrive & ~0x80) == drive))
665 WideCharToMultiByte(CP_ACP, 0, full_name.short_name + 2, -1,
666 pTask->curdir, sizeof(pTask->curdir), NULL, NULL);
667 DRIVE_LastTask = GetCurrentTask();
669 return 1;
673 /***********************************************************************
674 * DefineDosDeviceA (KERNEL32.@)
676 BOOL WINAPI DefineDosDeviceA(DWORD flags,LPCSTR devname,LPCSTR targetpath)
678 UNICODE_STRING d, t;
679 BOOL ret;
681 if (!RtlCreateUnicodeStringFromAsciiz(&d, devname))
683 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
684 return FALSE;
686 if (!RtlCreateUnicodeStringFromAsciiz(&t, targetpath))
688 RtlFreeUnicodeString(&d);
689 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
690 return FALSE;
692 ret = DefineDosDeviceW(flags, d.Buffer, t.Buffer);
693 RtlFreeUnicodeString(&d);
694 RtlFreeUnicodeString(&t);
695 return ret;
699 /***********************************************************************
700 * DefineDosDeviceA (KERNEL32.@)
702 BOOL WINAPI DefineDosDeviceW(DWORD flags,LPCWSTR devname,LPCWSTR targetpath)
704 DOSDRIVE *old, *new;
706 /* this is a temporary hack for int21 support. better implementation has to be done */
707 if (flags != DDD_RAW_TARGET_PATH ||
708 !(toupperW(devname[0]) >= 'A' && toupperW(devname[0]) <= 'Z') ||
709 devname[1] != ':' || devname[2] != 0 ||
710 !(toupperW(targetpath[0]) >= 'A' && toupperW(targetpath[0]) <= 'Z') ||
711 targetpath[1] != ':' || targetpath[2] != '\\' || targetpath[3] != 0)
713 FIXME("(0x%08lx,%s,%s),stub!\n", flags, debugstr_w(devname), debugstr_w(targetpath));
714 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
715 return FALSE;
718 old = DOSDrives + devname[0] - 'A';
719 new = DOSDrives + targetpath[0] - 'A';
721 if (!old->root)
723 SetLastError( ERROR_INVALID_DRIVE );
724 return 0;
727 if ( new->root )
729 TRACE("Can't map drive %c: to already existing drive %c:\n",
730 devname[0], targetpath[0] );
731 /* it is already mapped there, so return success */
732 if (!strcmp(old->root,new->root))
733 return 1;
734 return 0;
737 new->root = heap_strdup( old->root );
738 new->dos_cwd = HeapAlloc(GetProcessHeap(), 0, (strlenW(old->dos_cwd) + 1) * sizeof(WCHAR));
739 strcpyW(new->dos_cwd, old->dos_cwd);
740 new->unix_cwd = heap_strdup( old->unix_cwd );
741 new->device = heap_strdup( old->device );
742 new->type = old->type;
743 new->flags = old->flags;
744 new->dev = old->dev;
745 new->ino = old->ino;
747 TRACE("Drive %c: is now equal to drive %c:\n",
748 targetpath[0], devname[0] );
750 return 1;
754 /***********************************************************************
755 * DRIVE_GetFreeSpace
757 static int DRIVE_GetFreeSpace( int drive, PULARGE_INTEGER size,
758 PULARGE_INTEGER available )
760 struct statvfs info;
762 if (!DRIVE_IsValid(drive))
764 SetLastError( ERROR_PATH_NOT_FOUND );
765 return 0;
768 if (statvfs( DOSDrives[drive].root, &info ) < 0)
770 FILE_SetDosError();
771 WARN("cannot do statvfs(%s)\n", DOSDrives[drive].root);
772 return 0;
774 size->QuadPart = RtlEnlargedUnsignedMultiply( info.f_frsize, info.f_blocks );
775 if (DOSDrives[drive].type == DRIVE_CDROM)
776 available->QuadPart = 0; /* ALWAYS 0, even if no real CD-ROM mounted there !! */
777 else
778 available->QuadPart = RtlEnlargedUnsignedMultiply( info.f_frsize, info.f_bavail );
780 return 1;
783 /***********************************************************************
784 * DRIVE_GetCurrentDirectory
785 * Returns "X:\\path\\etc\\".
787 * Despite the API description, return required length including the
788 * terminating null when buffer too small. This is the real behaviour.
790 static UINT DRIVE_GetCurrentDirectory( UINT buflen, LPWSTR buf )
792 UINT ret;
793 LPCWSTR dos_cwd = DRIVE_GetDosCwd( DRIVE_GetCurrentDrive() );
794 static const WCHAR driveA_rootW[] = {'A',':','\\',0};
796 ret = strlenW(dos_cwd) + 3; /* length of WHOLE current directory */
797 if (ret >= buflen) return ret + 1;
799 strcpyW( buf, driveA_rootW );
800 buf[0] += DRIVE_GetCurrentDrive();
801 strcatW( buf, dos_cwd );
802 return ret;
806 /***********************************************************************
807 * DRIVE_BuildEnv
809 * Build the environment array containing the drives' current directories.
810 * Resulting pointer must be freed with HeapFree.
812 WCHAR *DRIVE_BuildEnv(void)
814 int i, length = 0;
815 LPCWSTR cwd[MAX_DOS_DRIVES];
816 WCHAR *env, *p;
818 for (i = 0; i < MAX_DOS_DRIVES; i++)
820 if ((cwd[i] = DRIVE_GetDosCwd(i)) && cwd[i][0])
821 length += strlenW(cwd[i]) + 8;
823 if (!(env = HeapAlloc( GetProcessHeap(), 0, (length+1) * sizeof(WCHAR) ))) return NULL;
824 for (i = 0, p = env; i < MAX_DOS_DRIVES; i++)
826 if (cwd[i] && cwd[i][0])
828 *p++ = '='; *p++ = 'A' + i; *p++ = ':';
829 *p++ = '='; *p++ = 'A' + i; *p++ = ':'; *p++ = '\\';
830 strcpyW( p, cwd[i] );
831 p += strlenW(p) + 1;
834 *p = 0;
835 return env;
839 /***********************************************************************
840 * GetDiskFreeSpace (KERNEL.422)
842 BOOL16 WINAPI GetDiskFreeSpace16( LPCSTR root, LPDWORD cluster_sectors,
843 LPDWORD sector_bytes, LPDWORD free_clusters,
844 LPDWORD total_clusters )
846 return GetDiskFreeSpaceA( root, cluster_sectors, sector_bytes,
847 free_clusters, total_clusters );
851 /***********************************************************************
852 * GetDiskFreeSpaceW (KERNEL32.@)
854 * Fails if expression resulting from current drive's dir and "root"
855 * is not a root dir of the target drive.
857 * UNDOC: setting some LPDWORDs to NULL is perfectly possible
858 * if the corresponding info is unneeded.
860 * FIXME: needs to support UNC names from Win95 OSR2 on.
862 * Behaviour under Win95a:
863 * CurrDir root result
864 * "E:\\TEST" "E:" FALSE
865 * "E:\\" "E:" TRUE
866 * "E:\\" "E" FALSE
867 * "E:\\" "\\" TRUE
868 * "E:\\TEST" "\\" TRUE
869 * "E:\\TEST" ":\\" FALSE
870 * "E:\\TEST" "E:\\" TRUE
871 * "E:\\TEST" "" FALSE
872 * "E:\\" "" FALSE (!)
873 * "E:\\" 0x0 TRUE
874 * "E:\\TEST" 0x0 TRUE (!)
875 * "E:\\TEST" "C:" TRUE (when CurrDir of "C:" set to "\\")
876 * "E:\\TEST" "C:" FALSE (when CurrDir of "C:" set to "\\TEST")
878 BOOL WINAPI GetDiskFreeSpaceW( LPCWSTR root, LPDWORD cluster_sectors,
879 LPDWORD sector_bytes, LPDWORD free_clusters,
880 LPDWORD total_clusters )
882 int drive, sec_size;
883 ULARGE_INTEGER size,available;
884 LPCWSTR path;
885 DWORD cluster_sec;
887 TRACE("%s,%p,%p,%p,%p\n", debugstr_w(root), cluster_sectors, sector_bytes,
888 free_clusters, total_clusters);
890 if (!root || root[0] == '\\' || root[0] == '/')
891 drive = DRIVE_GetCurrentDrive();
892 else
893 if (root[0] && root[1] == ':') /* root contains drive tag */
895 drive = toupperW(root[0]) - 'A';
896 path = &root[2];
897 if (path[0] == '\0')
899 path = DRIVE_GetDosCwd(drive);
900 if (!path)
902 SetLastError(ERROR_PATH_NOT_FOUND);
903 return FALSE;
906 else
907 if (path[0] == '\\')
908 path++;
910 if (path[0]) /* oops, we are in a subdir */
912 SetLastError(ERROR_INVALID_NAME);
913 return FALSE;
916 else
918 if (!root[0])
919 SetLastError(ERROR_PATH_NOT_FOUND);
920 else
921 SetLastError(ERROR_INVALID_NAME);
922 return FALSE;
925 if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
927 /* Cap the size and available at 2GB as per specs. */
928 if ((size.u.HighPart) ||(size.u.LowPart > 0x7fffffff))
930 size.u.HighPart = 0;
931 size.u.LowPart = 0x7fffffff;
933 if ((available.u.HighPart) ||(available.u.LowPart > 0x7fffffff))
935 available.u.HighPart =0;
936 available.u.LowPart = 0x7fffffff;
938 sec_size = (DRIVE_GetType(drive)==DRIVE_CDROM) ? 2048 : 512;
939 size.u.LowPart /= sec_size;
940 available.u.LowPart /= sec_size;
941 /* FIXME: probably have to adjust those variables too for CDFS */
942 cluster_sec = 1;
943 while (cluster_sec * 65536 < size.u.LowPart) cluster_sec *= 2;
945 if (cluster_sectors)
946 *cluster_sectors = cluster_sec;
947 if (sector_bytes)
948 *sector_bytes = sec_size;
949 if (free_clusters)
950 *free_clusters = available.u.LowPart / cluster_sec;
951 if (total_clusters)
952 *total_clusters = size.u.LowPart / cluster_sec;
953 return TRUE;
957 /***********************************************************************
958 * GetDiskFreeSpaceA (KERNEL32.@)
960 BOOL WINAPI GetDiskFreeSpaceA( LPCSTR root, LPDWORD cluster_sectors,
961 LPDWORD sector_bytes, LPDWORD free_clusters,
962 LPDWORD total_clusters )
964 UNICODE_STRING rootW;
965 BOOL ret = FALSE;
967 if (root)
969 if(!RtlCreateUnicodeStringFromAsciiz(&rootW, root))
971 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
972 return FALSE;
975 else
976 rootW.Buffer = NULL;
978 ret = GetDiskFreeSpaceW(rootW.Buffer, cluster_sectors, sector_bytes,
979 free_clusters, total_clusters );
980 RtlFreeUnicodeString(&rootW);
982 return ret;
986 /***********************************************************************
987 * GetDiskFreeSpaceExW (KERNEL32.@)
989 * This function is used to acquire the size of the available and
990 * total space on a logical volume.
992 * RETURNS
994 * Zero on failure, nonzero upon success. Use GetLastError to obtain
995 * detailed error information.
998 BOOL WINAPI GetDiskFreeSpaceExW( LPCWSTR root,
999 PULARGE_INTEGER avail,
1000 PULARGE_INTEGER total,
1001 PULARGE_INTEGER totalfree)
1003 int drive;
1004 ULARGE_INTEGER size,available;
1006 if (!root) drive = DRIVE_GetCurrentDrive();
1007 else
1008 { /* C: always works for GetDiskFreeSpaceEx */
1009 if ((root[1]) && ((root[1] != ':') || (root[2] && root[2] != '\\')))
1011 FIXME("there are valid root names which are not supported yet\n");
1012 /* ..like UNC names, for instance. */
1014 WARN("invalid root '%s'\n", debugstr_w(root));
1015 return FALSE;
1017 drive = toupperW(root[0]) - 'A';
1020 if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
1022 if (total)
1024 total->u.HighPart = size.u.HighPart;
1025 total->u.LowPart = size.u.LowPart;
1028 if (totalfree)
1030 totalfree->u.HighPart = available.u.HighPart;
1031 totalfree->u.LowPart = available.u.LowPart;
1034 if (avail)
1036 if (FIXME_ON(dosfs))
1038 /* On Windows2000, we need to check the disk quota
1039 allocated for the user owning the calling process. We
1040 don't want to be more obtrusive than necessary with the
1041 FIXME messages, so don't print the FIXME unless Wine is
1042 actually masquerading as Windows2000. */
1044 RTL_OSVERSIONINFOEXW ovi;
1045 ovi.dwOSVersionInfoSize = sizeof(ovi);
1046 if (RtlGetVersion(&ovi))
1048 if (ovi.dwPlatformId == VER_PLATFORM_WIN32_NT && ovi.dwMajorVersion > 4)
1049 FIXME("no per-user quota support yet\n");
1053 /* Quick hack, should eventually be fixed to work 100% with
1054 Windows2000 (see comment above). */
1055 avail->u.HighPart = available.u.HighPart;
1056 avail->u.LowPart = available.u.LowPart;
1059 return TRUE;
1062 /***********************************************************************
1063 * GetDiskFreeSpaceExA (KERNEL32.@)
1065 BOOL WINAPI GetDiskFreeSpaceExA( LPCSTR root, PULARGE_INTEGER avail,
1066 PULARGE_INTEGER total,
1067 PULARGE_INTEGER totalfree)
1069 UNICODE_STRING rootW;
1070 BOOL ret;
1072 if (root) RtlCreateUnicodeStringFromAsciiz(&rootW, root);
1073 else rootW.Buffer = NULL;
1075 ret = GetDiskFreeSpaceExW( rootW.Buffer, avail, total, totalfree);
1077 RtlFreeUnicodeString(&rootW);
1078 return ret;
1081 /***********************************************************************
1082 * GetDriveType (KERNEL.136)
1083 * This function returns the type of a drive in Win16.
1084 * Note that it returns DRIVE_REMOTE for CD-ROMs, since MSCDEX uses the
1085 * remote drive API. The return value DRIVE_REMOTE for CD-ROMs has been
1086 * verified on Win 3.11 and Windows 95. Some programs rely on it, so don't
1087 * do any pseudo-clever changes.
1089 * RETURNS
1090 * drivetype DRIVE_xxx
1092 UINT16 WINAPI GetDriveType16( UINT16 drive ) /* [in] number (NOT letter) of drive */
1094 UINT type = DRIVE_GetType(drive);
1095 TRACE("(%c:)\n", 'A' + drive );
1096 if (type == DRIVE_CDROM) type = DRIVE_REMOTE;
1097 return type;
1101 /***********************************************************************
1102 * GetDriveTypeW (KERNEL32.@)
1104 * Returns the type of the disk drive specified. If root is NULL the
1105 * root of the current directory is used.
1107 * RETURNS
1109 * Type of drive (from Win32 SDK):
1111 * DRIVE_UNKNOWN unable to find out anything about the drive
1112 * DRIVE_NO_ROOT_DIR nonexistent root dir
1113 * DRIVE_REMOVABLE the disk can be removed from the machine
1114 * DRIVE_FIXED the disk can not be removed from the machine
1115 * DRIVE_REMOTE network disk
1116 * DRIVE_CDROM CDROM drive
1117 * DRIVE_RAMDISK virtual disk in RAM
1119 UINT WINAPI GetDriveTypeW(LPCWSTR root) /* [in] String describing drive */
1121 int drive;
1122 TRACE("(%s)\n", debugstr_w(root));
1124 if (NULL == root) drive = DRIVE_GetCurrentDrive();
1125 else
1127 if ((root[1]) && (root[1] != ':'))
1129 WARN("invalid root %s\n", debugstr_w(root));
1130 return DRIVE_NO_ROOT_DIR;
1132 drive = toupperW(root[0]) - 'A';
1134 return DRIVE_GetType(drive);
1138 /***********************************************************************
1139 * GetDriveTypeA (KERNEL32.@)
1141 UINT WINAPI GetDriveTypeA( LPCSTR root )
1143 UNICODE_STRING rootW;
1144 UINT ret = 0;
1146 if (root)
1148 if( !RtlCreateUnicodeStringFromAsciiz(&rootW, root))
1150 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1151 return 0;
1154 else
1155 rootW.Buffer = NULL;
1157 ret = GetDriveTypeW(rootW.Buffer);
1159 RtlFreeUnicodeString(&rootW);
1160 return ret;
1165 /***********************************************************************
1166 * GetCurrentDirectory (KERNEL.411)
1168 UINT16 WINAPI GetCurrentDirectory16( UINT16 buflen, LPSTR buf )
1170 WCHAR cur_dirW[MAX_PATH];
1172 DRIVE_GetCurrentDirectory(MAX_PATH, cur_dirW);
1173 return (UINT16)WideCharToMultiByte(CP_ACP, 0, cur_dirW, -1, buf, buflen, NULL, NULL);
1177 /***********************************************************************
1178 * GetCurrentDirectoryW (KERNEL32.@)
1180 UINT WINAPI GetCurrentDirectoryW( UINT buflen, LPWSTR buf )
1182 UINT ret;
1183 WCHAR longname[MAX_PATHNAME_LEN];
1184 WCHAR shortname[MAX_PATHNAME_LEN];
1186 ret = DRIVE_GetCurrentDirectory(MAX_PATHNAME_LEN, shortname);
1187 if ( ret > MAX_PATHNAME_LEN ) {
1188 ERR_(file)("pathnamelength (%d) > MAX_PATHNAME_LEN!\n", ret );
1189 return ret;
1191 GetLongPathNameW(shortname, longname, MAX_PATHNAME_LEN);
1192 ret = strlenW( longname ) + 1;
1193 if (ret > buflen) return ret;
1194 strcpyW(buf, longname);
1195 return ret - 1;
1198 /***********************************************************************
1199 * GetCurrentDirectoryA (KERNEL32.@)
1201 UINT WINAPI GetCurrentDirectoryA( UINT buflen, LPSTR buf )
1203 WCHAR bufferW[MAX_PATH];
1204 DWORD ret, retW;
1206 retW = GetCurrentDirectoryW(MAX_PATH, bufferW);
1208 if (!retW)
1209 ret = 0;
1210 else if (retW > MAX_PATH)
1212 SetLastError(ERROR_FILENAME_EXCED_RANGE);
1213 ret = 0;
1215 else
1217 ret = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
1218 if (buflen >= ret)
1220 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buf, buflen, NULL, NULL);
1221 ret--; /* length without 0 */
1224 return ret;
1228 /***********************************************************************
1229 * SetCurrentDirectory (KERNEL.412)
1231 BOOL16 WINAPI SetCurrentDirectory16( LPCSTR dir )
1233 return SetCurrentDirectoryA( dir );
1237 /***********************************************************************
1238 * SetCurrentDirectoryW (KERNEL32.@)
1240 BOOL WINAPI SetCurrentDirectoryW( LPCWSTR dir )
1242 int drive, olddrive = DRIVE_GetCurrentDrive();
1244 if (!dir)
1246 SetLastError(ERROR_INVALID_PARAMETER);
1247 return FALSE;
1249 if (dir[0] && (dir[1]==':'))
1251 drive = toupperW( *dir ) - 'A';
1252 dir += 2;
1254 else
1255 drive = olddrive;
1257 /* WARNING: we need to set the drive before the dir, as DRIVE_Chdir
1258 sets pTask->curdir only if pTask->curdrive is drive */
1259 if (!(DRIVE_SetCurrentDrive( drive )))
1260 return FALSE;
1262 /* FIXME: what about empty strings? Add a \\ ? */
1263 if (!DRIVE_Chdir( drive, dir )) {
1264 DRIVE_SetCurrentDrive(olddrive);
1265 return FALSE;
1267 return TRUE;
1271 /***********************************************************************
1272 * SetCurrentDirectoryA (KERNEL32.@)
1274 BOOL WINAPI SetCurrentDirectoryA( LPCSTR dir )
1276 UNICODE_STRING dirW;
1277 BOOL ret = FALSE;
1279 if (!dir)
1281 SetLastError(ERROR_INVALID_PARAMETER);
1282 return FALSE;
1285 if (RtlCreateUnicodeStringFromAsciiz(&dirW, dir))
1287 ret = SetCurrentDirectoryW(dirW.Buffer);
1288 RtlFreeUnicodeString(&dirW);
1290 else
1291 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1292 return ret;
1296 /***********************************************************************
1297 * GetLogicalDriveStringsA (KERNEL32.@)
1299 UINT WINAPI GetLogicalDriveStringsA( UINT len, LPSTR buffer )
1301 int drive, count;
1303 for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1304 if (DRIVE_IsValid(drive)) count++;
1305 if ((count * 4) + 1 <= len)
1307 LPSTR p = buffer;
1308 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1309 if (DRIVE_IsValid(drive))
1311 *p++ = 'a' + drive;
1312 *p++ = ':';
1313 *p++ = '\\';
1314 *p++ = '\0';
1316 *p = '\0';
1317 return count * 4;
1319 else
1320 return (count * 4) + 1; /* account for terminating null */
1321 /* The API tells about these different return values */
1325 /***********************************************************************
1326 * GetLogicalDriveStringsW (KERNEL32.@)
1328 UINT WINAPI GetLogicalDriveStringsW( UINT len, LPWSTR buffer )
1330 int drive, count;
1332 for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1333 if (DRIVE_IsValid(drive)) count++;
1334 if (count * 4 * sizeof(WCHAR) <= len)
1336 LPWSTR p = buffer;
1337 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1338 if (DRIVE_IsValid(drive))
1340 *p++ = (WCHAR)('a' + drive);
1341 *p++ = (WCHAR)':';
1342 *p++ = (WCHAR)'\\';
1343 *p++ = (WCHAR)'\0';
1345 *p = (WCHAR)'\0';
1347 return count * 4 * sizeof(WCHAR);
1351 /***********************************************************************
1352 * GetLogicalDrives (KERNEL32.@)
1354 DWORD WINAPI GetLogicalDrives(void)
1356 DWORD ret = 0;
1357 int drive;
1359 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1361 if ( (DRIVE_IsValid(drive)) ||
1362 (DOSDrives[drive].type == DRIVE_CDROM)) /* audio CD is also valid */
1363 ret |= (1 << drive);
1365 return ret;