- SHBindToParent(): request correct COM interface from desktop.
[wine/wine-kai.git] / files / drive.c
blob7fc1378bc8cdb433540e39921c32c954ef09f00c
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 "file.h"
59 #include "wine/unicode.h"
60 #include "wine/library.h"
61 #include "wine/server.h"
62 #include "wine/debug.h"
64 WINE_DEFAULT_DEBUG_CHANNEL(dosfs);
65 WINE_DECLARE_DEBUG_CHANNEL(file);
67 typedef struct
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;
79 static const WCHAR DRIVE_Types[][8] =
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 */
90 #define MAX_DOS_DRIVES 26
92 static DOSDRIVE DOSDrives[MAX_DOS_DRIVES];
93 static int DRIVE_CurDrive = -1;
95 static HTASK16 DRIVE_LastTask = 0;
97 /* strdup on the process heap */
98 inline static char *heap_strdup( const char *str )
100 INT len = strlen(str) + 1;
101 LPSTR p = HeapAlloc( GetProcessHeap(), 0, len );
102 if (p) memcpy( p, str, len );
103 return p;
106 extern void CDROM_InitRegistry(int dev);
108 /***********************************************************************
109 * DRIVE_GetDriveType
111 static inline UINT DRIVE_GetDriveType( INT drive, LPCWSTR value )
113 int i;
115 for (i = 0; i < sizeof(DRIVE_Types)/sizeof(DRIVE_Types[0]); i++)
117 if (!strcmpiW( value, DRIVE_Types[i] )) return i;
119 MESSAGE("Drive %c: unknown drive type %s, defaulting to 'hd'.\n",
120 'A' + drive, debugstr_w(value) );
121 return DRIVE_FIXED;
125 /***********************************************************************
126 * DRIVE_Init
128 int DRIVE_Init(void)
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();
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};
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;
158 /* get the root of the drives from symlinks */
160 root = NULL;
161 for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
163 if (!root)
165 root = HeapAlloc( GetProcessHeap(), 0, strlen(config_dir) + sizeof("/dosdevices/a:") );
166 strcpy( root, config_dir );
167 strcat( root, "/dosdevices/a:" );
169 root[strlen(root)-2] = 'a' + i;
170 if (stat( root, &drive_stat_buffer ))
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;
177 if (!S_ISDIR(drive_stat_buffer.st_mode))
179 MESSAGE("%s is not a directory, ignoring drive %c:\n", root, 'a' + i );
180 continue;
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++;
192 if (root) HeapFree( GetProcessHeap(), 0, root );
194 /* now get the parameters from the config file */
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 if (!symlink_count)
205 RtlInitUnicodeString( &nameW, PathW );
206 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
208 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
209 ExpandEnvironmentStringsW( data, path, sizeof(path)/sizeof(WCHAR) );
211 p = path + strlenW(path) - 1;
212 while ((p > path) && (*p == '/')) *p-- = '\0';
214 if (path[0] == '/')
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);
220 else
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);
232 if (stat( drive->root, &drive_stat_buffer ))
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;
240 if (!S_ISDIR(drive_stat_buffer.st_mode))
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;
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;
258 if (drive->root)
260 /* Get the drive type */
261 RtlInitUnicodeString( &nameW, TypeW );
262 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
264 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
265 drive->type = DRIVE_GetDriveType( i, data );
268 /* Get the device */
269 RtlInitUnicodeString( &nameW, DeviceW );
270 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
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);
277 if (drive->type == DRIVE_CDROM)
279 int cd_fd;
280 if ((cd_fd = open(drive->device, O_RDONLY|O_NONBLOCK)) != -1)
282 CDROM_InitRegistry(cd_fd);
283 close(cd_fd);
288 /* Make the first hard disk the current drive */
289 if ((DRIVE_CurDrive == -1) && (drive->type == DRIVE_FIXED))
290 DRIVE_CurDrive = i;
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 );
298 next:
299 NtClose( hkey );
302 if (!count && !symlink_count)
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;
314 /* Make sure the current drive is valid */
315 if (DRIVE_CurDrive == -1)
317 for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
319 if (drive->root)
321 DRIVE_CurDrive = i;
322 break;
327 /* get current working directory info for all drives */
328 for (i = 0; i < MAX_DOS_DRIVES; i++, drive_env[1]++)
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 );
335 return 1;
339 /***********************************************************************
340 * DRIVE_IsValid
342 int DRIVE_IsValid( int drive )
344 if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
345 return (DOSDrives[drive].root != NULL);
349 /***********************************************************************
350 * DRIVE_GetCurrentDrive
352 int DRIVE_GetCurrentDrive(void)
354 TDB *pTask = GlobalLock16(GetCurrentTask());
355 if (pTask && (pTask->curdrive & 0x80)) return pTask->curdrive & ~0x80;
356 return DRIVE_CurDrive;
360 /***********************************************************************
361 * DRIVE_SetCurrentDrive
363 static int DRIVE_SetCurrentDrive( int drive )
365 TDB *pTask = GlobalLock16(GetCurrentTask());
366 if (!DRIVE_IsValid( drive ))
368 SetLastError( ERROR_INVALID_DRIVE );
369 return 0;
371 TRACE("%c:\n", 'A' + drive );
372 DRIVE_CurDrive = drive;
373 if (pTask) pTask->curdrive = drive | 0x80;
374 return 1;
378 /***********************************************************************
379 * DRIVE_FindDriveRoot
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.
386 * Note: path must be in the encoding of the underlying Unix file system.
388 int DRIVE_FindDriveRoot( const char **path )
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.
395 int drive, level, len;
396 char buffer[MAX_PATHNAME_LEN];
397 char *p;
398 struct stat st;
400 strcpy( buffer, *path );
401 for (p = buffer; *p; p++) if (*p == '\\') *p = '/';
402 len = p - buffer;
404 /* strip off trailing slashes */
405 while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
407 for (;;)
409 /* Find the drive */
410 if (stat( buffer, &st ) == 0 && S_ISDIR( st.st_mode ))
412 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
414 if (!DOSDrives[drive].root) continue;
416 if ((DOSDrives[drive].dev == st.st_dev) &&
417 (DOSDrives[drive].ino == st.st_ino))
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;
428 if (len <= 1) return -1; /* reached root */
430 level = 0;
431 while (level < 1)
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;
447 /***********************************************************************
448 * DRIVE_FindDriveRootW
450 * Unicode version of DRIVE_FindDriveRoot.
452 int DRIVE_FindDriveRootW( LPCWSTR *path )
454 int drive, level, len;
455 WCHAR buffer[MAX_PATHNAME_LEN];
456 WCHAR *p;
457 struct stat st;
459 strcpyW( buffer, *path );
460 for (p = buffer; *p; p++) if (*p == '\\') *p = '/';
461 len = p - buffer;
463 /* strip off trailing slashes */
464 while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
466 for (;;)
468 char buffA[MAX_PATHNAME_LEN];
470 WideCharToMultiByte( CP_UNIXCP, 0, buffer, -1, buffA, sizeof(buffA), NULL, NULL );
471 if (stat( buffA, &st ) == 0 && S_ISDIR( st.st_mode ))
473 /* Find the drive */
474 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
476 if (!DOSDrives[drive].root) continue;
478 if ((DOSDrives[drive].dev == st.st_dev) &&
479 (DOSDrives[drive].ino == st.st_ino))
481 static const WCHAR rootW[] = {'\\',0};
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;
492 if (len <= 1) return -1; /* reached root */
494 level = 0;
495 while (level < 1)
497 static const WCHAR dotW[] = {'.',0};
498 static const WCHAR dotdotW[] = {'.','.',0};
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;
514 /***********************************************************************
515 * DRIVE_GetRoot
517 const char * DRIVE_GetRoot( int drive )
519 if (!DRIVE_IsValid( drive )) return NULL;
520 return DOSDrives[drive].root;
524 /***********************************************************************
525 * DRIVE_GetDosCwd
527 LPCWSTR DRIVE_GetDosCwd( int drive )
529 TDB *pTask = GlobalLock16(GetCurrentTask());
530 if (!DRIVE_IsValid( drive )) return NULL;
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 */
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();
544 return DOSDrives[drive].dos_cwd;
548 /***********************************************************************
549 * DRIVE_GetUnixCwd
551 const char * DRIVE_GetUnixCwd( int drive )
553 TDB *pTask = GlobalLock16(GetCurrentTask());
554 if (!DRIVE_IsValid( drive )) return NULL;
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 */
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();
568 return DOSDrives[drive].unix_cwd;
572 /***********************************************************************
573 * DRIVE_GetDevice
575 const char * DRIVE_GetDevice( int drive )
577 return (DRIVE_IsValid( drive )) ? DOSDrives[drive].device : NULL;
580 /***********************************************************************
581 * DRIVE_GetType
583 static UINT DRIVE_GetType( int drive )
585 if (!DRIVE_IsValid( drive )) return DRIVE_NO_ROOT_DIR;
586 return DOSDrives[drive].type;
590 /***********************************************************************
591 * DRIVE_Chdir
593 int DRIVE_Chdir( int drive, LPCWSTR path )
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());
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 */
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))
612 SetLastError( ERROR_FILE_NOT_FOUND );
613 return 0;
615 unix_cwd = full_name.long_name + strlen( DOSDrives[drive].root );
616 while (*unix_cwd == '/') unix_cwd++;
618 TRACE("(%c:): unix_cwd=%s dos_cwd=%s\n",
619 'A' + drive, unix_cwd, debugstr_w(full_name.short_name + 3) );
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 );
627 if (drive == DRIVE_CurDrive)
629 UNICODE_STRING dirW;
631 RtlInitUnicodeString( &dirW, full_name.short_name );
632 RtlSetCurrentDirectory_U( &dirW );
635 if (pTask && (pTask->curdrive & 0x80) &&
636 ((pTask->curdrive & ~0x80) == drive))
638 WideCharToMultiByte(CP_ACP, 0, full_name.short_name + 2, -1,
639 pTask->curdir, sizeof(pTask->curdir), NULL, NULL);
640 DRIVE_LastTask = GetCurrentTask();
642 return 1;
646 /***********************************************************************
647 * DRIVE_GetFreeSpace
649 static int DRIVE_GetFreeSpace( int drive, PULARGE_INTEGER size,
650 PULARGE_INTEGER available )
652 struct statvfs info;
654 if (!DRIVE_IsValid(drive))
656 SetLastError( ERROR_PATH_NOT_FOUND );
657 return 0;
660 if (statvfs( DOSDrives[drive].root, &info ) < 0)
662 FILE_SetDosError();
663 WARN("cannot do statvfs(%s)\n", DOSDrives[drive].root);
664 return 0;
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 );
672 return 1;
675 /***********************************************************************
676 * DRIVE_GetCurrentDirectory
677 * Returns "X:\\path\\etc\\".
679 * Despite the API description, return required length including the
680 * terminating null when buffer too small. This is the real behaviour.
682 static UINT DRIVE_GetCurrentDirectory( UINT buflen, LPWSTR buf )
684 UINT ret;
685 LPCWSTR dos_cwd = DRIVE_GetDosCwd( DRIVE_GetCurrentDrive() );
686 static const WCHAR driveA_rootW[] = {'A',':','\\',0};
688 ret = strlenW(dos_cwd) + 3; /* length of WHOLE current directory */
689 if (ret >= buflen) return ret + 1;
691 strcpyW( buf, driveA_rootW );
692 buf[0] += DRIVE_GetCurrentDrive();
693 strcatW( buf, dos_cwd );
694 return ret;
698 /***********************************************************************
699 * DRIVE_BuildEnv
701 * Build the environment array containing the drives' current directories.
702 * Resulting pointer must be freed with HeapFree.
704 WCHAR *DRIVE_BuildEnv(void)
706 int i, length = 0;
707 LPCWSTR cwd[MAX_DOS_DRIVES];
708 WCHAR *env, *p;
710 for (i = 0; i < MAX_DOS_DRIVES; i++)
712 if ((cwd[i] = DRIVE_GetDosCwd(i)) && cwd[i][0])
713 length += strlenW(cwd[i]) + 8;
715 if (!(env = HeapAlloc( GetProcessHeap(), 0, (length+1) * sizeof(WCHAR) ))) return NULL;
716 for (i = 0, p = env; i < MAX_DOS_DRIVES; i++)
718 if (cwd[i] && cwd[i][0])
720 *p++ = '='; *p++ = 'A' + i; *p++ = ':';
721 *p++ = '='; *p++ = 'A' + i; *p++ = ':'; *p++ = '\\';
722 strcpyW( p, cwd[i] );
723 p += strlenW(p) + 1;
726 *p = 0;
727 return env;
731 /***********************************************************************
732 * GetDiskFreeSpaceW (KERNEL32.@)
734 * Fails if expression resulting from current drive's dir and "root"
735 * is not a root dir of the target drive.
737 * UNDOC: setting some LPDWORDs to NULL is perfectly possible
738 * if the corresponding info is unneeded.
740 * FIXME: needs to support UNC names from Win95 OSR2 on.
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")
758 BOOL WINAPI GetDiskFreeSpaceW( LPCWSTR root, LPDWORD cluster_sectors,
759 LPDWORD sector_bytes, LPDWORD free_clusters,
760 LPDWORD total_clusters )
762 int drive, sec_size;
763 ULARGE_INTEGER size,available;
764 LPCWSTR path;
765 DWORD cluster_sec;
767 TRACE("%s,%p,%p,%p,%p\n", debugstr_w(root), cluster_sectors, sector_bytes,
768 free_clusters, total_clusters);
770 if (!root || root[0] == '\\' || root[0] == '/')
771 drive = DRIVE_GetCurrentDrive();
772 else
773 if (root[0] && root[1] == ':') /* root contains drive tag */
775 drive = toupperW(root[0]) - 'A';
776 path = &root[2];
777 if (path[0] == '\0')
779 path = DRIVE_GetDosCwd(drive);
780 if (!path)
782 SetLastError(ERROR_PATH_NOT_FOUND);
783 return FALSE;
786 else
787 if (path[0] == '\\')
788 path++;
790 if (path[0]) /* oops, we are in a subdir */
792 SetLastError(ERROR_INVALID_NAME);
793 return FALSE;
796 else
798 if (!root[0])
799 SetLastError(ERROR_PATH_NOT_FOUND);
800 else
801 SetLastError(ERROR_INVALID_NAME);
802 return FALSE;
805 if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
807 /* Cap the size and available at 2GB as per specs. */
808 if ((size.u.HighPart) ||(size.u.LowPart > 0x7fffffff))
810 size.u.HighPart = 0;
811 size.u.LowPart = 0x7fffffff;
813 if ((available.u.HighPart) ||(available.u.LowPart > 0x7fffffff))
815 available.u.HighPart =0;
816 available.u.LowPart = 0x7fffffff;
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;
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;
837 /***********************************************************************
838 * GetDiskFreeSpaceA (KERNEL32.@)
840 BOOL WINAPI GetDiskFreeSpaceA( LPCSTR root, LPDWORD cluster_sectors,
841 LPDWORD sector_bytes, LPDWORD free_clusters,
842 LPDWORD total_clusters )
844 UNICODE_STRING rootW;
845 BOOL ret = FALSE;
847 if (root)
849 if(!RtlCreateUnicodeStringFromAsciiz(&rootW, root))
851 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
852 return FALSE;
855 else
856 rootW.Buffer = NULL;
858 ret = GetDiskFreeSpaceW(rootW.Buffer, cluster_sectors, sector_bytes,
859 free_clusters, total_clusters );
860 RtlFreeUnicodeString(&rootW);
862 return ret;
866 /***********************************************************************
867 * GetDiskFreeSpaceExW (KERNEL32.@)
869 * This function is used to acquire the size of the available and
870 * total space on a logical volume.
872 * RETURNS
874 * Zero on failure, nonzero upon success. Use GetLastError to obtain
875 * detailed error information.
878 BOOL WINAPI GetDiskFreeSpaceExW( LPCWSTR root,
879 PULARGE_INTEGER avail,
880 PULARGE_INTEGER total,
881 PULARGE_INTEGER totalfree)
883 int drive;
884 ULARGE_INTEGER size,available;
886 if (!root) drive = DRIVE_GetCurrentDrive();
887 else
888 { /* C: always works for GetDiskFreeSpaceEx */
889 if ((root[1]) && ((root[1] != ':') || (root[2] && root[2] != '\\')))
891 FIXME("there are valid root names which are not supported yet\n");
892 /* ..like UNC names, for instance. */
894 WARN("invalid root '%s'\n", debugstr_w(root));
895 return FALSE;
897 drive = toupperW(root[0]) - 'A';
900 if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
902 if (total)
904 total->u.HighPart = size.u.HighPart;
905 total->u.LowPart = size.u.LowPart;
908 if (totalfree)
910 totalfree->u.HighPart = available.u.HighPart;
911 totalfree->u.LowPart = available.u.LowPart;
914 if (avail)
916 if (FIXME_ON(dosfs))
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. */
924 RTL_OSVERSIONINFOEXW ovi;
925 ovi.dwOSVersionInfoSize = sizeof(ovi);
926 if (RtlGetVersion(&ovi))
928 if (ovi.dwPlatformId == VER_PLATFORM_WIN32_NT && ovi.dwMajorVersion > 4)
929 FIXME("no per-user quota support yet\n");
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;
939 return TRUE;
942 /***********************************************************************
943 * GetDiskFreeSpaceExA (KERNEL32.@)
945 BOOL WINAPI GetDiskFreeSpaceExA( LPCSTR root, PULARGE_INTEGER avail,
946 PULARGE_INTEGER total,
947 PULARGE_INTEGER totalfree)
949 UNICODE_STRING rootW;
950 BOOL ret;
952 if (root) RtlCreateUnicodeStringFromAsciiz(&rootW, root);
953 else rootW.Buffer = NULL;
955 ret = GetDiskFreeSpaceExW( rootW.Buffer, avail, total, totalfree);
957 RtlFreeUnicodeString(&rootW);
958 return ret;
961 /***********************************************************************
962 * GetDriveTypeW (KERNEL32.@)
964 * Returns the type of the disk drive specified. If root is NULL the
965 * root of the current directory is used.
967 * RETURNS
969 * Type of drive (from Win32 SDK):
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
979 UINT WINAPI GetDriveTypeW(LPCWSTR root) /* [in] String describing drive */
981 int drive;
982 TRACE("(%s)\n", debugstr_w(root));
984 if (NULL == root) drive = DRIVE_GetCurrentDrive();
985 else
987 if ((root[1]) && (root[1] != ':'))
989 WARN("invalid root %s\n", debugstr_w(root));
990 return DRIVE_NO_ROOT_DIR;
992 drive = toupperW(root[0]) - 'A';
994 return DRIVE_GetType(drive);
998 /***********************************************************************
999 * GetDriveTypeA (KERNEL32.@)
1001 UINT WINAPI GetDriveTypeA( LPCSTR root )
1003 UNICODE_STRING rootW;
1004 UINT ret = 0;
1006 if (root)
1008 if( !RtlCreateUnicodeStringFromAsciiz(&rootW, root))
1010 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1011 return 0;
1014 else
1015 rootW.Buffer = NULL;
1017 ret = GetDriveTypeW(rootW.Buffer);
1019 RtlFreeUnicodeString(&rootW);
1020 return ret;
1025 /***********************************************************************
1026 * GetCurrentDirectory (KERNEL.411)
1028 UINT16 WINAPI GetCurrentDirectory16( UINT16 buflen, LPSTR buf )
1030 WCHAR cur_dirW[MAX_PATH];
1032 DRIVE_GetCurrentDirectory(MAX_PATH, cur_dirW);
1033 return (UINT16)WideCharToMultiByte(CP_ACP, 0, cur_dirW, -1, buf, buflen, NULL, NULL);
1037 /***********************************************************************
1038 * GetCurrentDirectoryW (KERNEL32.@)
1040 UINT WINAPI GetCurrentDirectoryW( UINT buflen, LPWSTR buf )
1042 UINT ret;
1043 WCHAR longname[MAX_PATHNAME_LEN];
1044 WCHAR shortname[MAX_PATHNAME_LEN];
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;
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;
1058 /***********************************************************************
1059 * GetCurrentDirectoryA (KERNEL32.@)
1061 UINT WINAPI GetCurrentDirectoryA( UINT buflen, LPSTR buf )
1063 WCHAR bufferW[MAX_PATH];
1064 DWORD ret, retW;
1066 retW = GetCurrentDirectoryW(MAX_PATH, bufferW);
1068 if (!retW)
1069 ret = 0;
1070 else if (retW > MAX_PATH)
1072 SetLastError(ERROR_FILENAME_EXCED_RANGE);
1073 ret = 0;
1075 else
1077 ret = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
1078 if (buflen >= ret)
1080 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buf, buflen, NULL, NULL);
1081 ret--; /* length without 0 */
1084 return ret;
1088 /***********************************************************************
1089 * SetCurrentDirectoryW (KERNEL32.@)
1091 BOOL WINAPI SetCurrentDirectoryW( LPCWSTR dir )
1093 int drive, olddrive = DRIVE_GetCurrentDrive();
1095 if (!dir)
1097 SetLastError(ERROR_INVALID_PARAMETER);
1098 return FALSE;
1100 if (dir[0] && (dir[1]==':'))
1102 drive = toupperW( *dir ) - 'A';
1103 dir += 2;
1105 else
1106 drive = olddrive;
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;
1113 /* FIXME: what about empty strings? Add a \\ ? */
1114 if (!DRIVE_Chdir( drive, dir )) {
1115 DRIVE_SetCurrentDrive(olddrive);
1116 return FALSE;
1118 return TRUE;
1122 /***********************************************************************
1123 * SetCurrentDirectoryA (KERNEL32.@)
1125 BOOL WINAPI SetCurrentDirectoryA( LPCSTR dir )
1127 UNICODE_STRING dirW;
1128 BOOL ret = FALSE;
1130 if (!dir)
1132 SetLastError(ERROR_INVALID_PARAMETER);
1133 return FALSE;
1136 if (RtlCreateUnicodeStringFromAsciiz(&dirW, dir))
1138 ret = SetCurrentDirectoryW(dirW.Buffer);
1139 RtlFreeUnicodeString(&dirW);
1141 else
1142 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1143 return ret;
1147 /***********************************************************************
1148 * GetLogicalDriveStringsA (KERNEL32.@)
1150 UINT WINAPI GetLogicalDriveStringsA( UINT len, LPSTR buffer )
1152 int drive, count;
1154 for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1155 if (DRIVE_IsValid(drive)) count++;
1156 if ((count * 4) + 1 <= len)
1158 LPSTR p = buffer;
1159 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1160 if (DRIVE_IsValid(drive))
1162 *p++ = 'a' + drive;
1163 *p++ = ':';
1164 *p++ = '\\';
1165 *p++ = '\0';
1167 *p = '\0';
1168 return count * 4;
1170 else
1171 return (count * 4) + 1; /* account for terminating null */
1172 /* The API tells about these different return values */
1176 /***********************************************************************
1177 * GetLogicalDriveStringsW (KERNEL32.@)
1179 UINT WINAPI GetLogicalDriveStringsW( UINT len, LPWSTR buffer )
1181 int drive, count;
1183 for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1184 if (DRIVE_IsValid(drive)) count++;
1185 if (count * 4 * sizeof(WCHAR) <= len)
1187 LPWSTR p = buffer;
1188 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1189 if (DRIVE_IsValid(drive))
1191 *p++ = (WCHAR)('a' + drive);
1192 *p++ = (WCHAR)':';
1193 *p++ = (WCHAR)'\\';
1194 *p++ = (WCHAR)'\0';
1196 *p = (WCHAR)'\0';
1198 return count * 4 * sizeof(WCHAR);
1202 /***********************************************************************
1203 * GetLogicalDrives (KERNEL32.@)
1205 DWORD WINAPI GetLogicalDrives(void)
1207 DWORD ret = 0;
1208 int drive;
1210 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1212 if ( (DRIVE_IsValid(drive)) ||
1213 (DOSDrives[drive].type == DRIVE_CDROM)) /* audio CD is also valid */
1214 ret |= (1 << drive);
1216 return ret;