wine/files/drive.c

2166 lines
66 KiB
C

/*
* DOS drives handling functions
*
* Copyright 1993 Erik Bos
* Copyright 1996 Alexandre Julliard
*
* Label & serial number read support.
* (c) 1999 Petr Tomasek <tomasek@etf.cuni.cz>
* (c) 2000 Andreas Mohr (changes)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include "wine/port.h"
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif
#ifdef STATFS_DEFINED_BY_SYS_VFS
# include <sys/vfs.h>
#else
# ifdef STATFS_DEFINED_BY_SYS_MOUNT
# include <sys/mount.h>
# else
# ifdef STATFS_DEFINED_BY_SYS_STATFS
# include <sys/statfs.h>
# endif
# endif
#endif
#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "ntstatus.h"
#include "windef.h"
#include "winbase.h"
#include "winreg.h"
#include "winternl.h"
#include "wine/winbase16.h" /* for GetCurrentTask */
#include "winerror.h"
#include "winioctl.h"
#include "ntddstor.h"
#include "ntddcdrm.h"
#include "drive.h"
#include "file.h"
#include "msdos.h"
#include "task.h"
#include "wine/unicode.h"
#include "wine/library.h"
#include "wine/server.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(dosfs);
WINE_DECLARE_DEBUG_CHANNEL(file);
typedef struct
{
char *root; /* root dir in Unix format without trailing / */
LPWSTR dos_cwd; /* cwd in DOS format without leading or trailing \ */
char *unix_cwd; /* cwd in Unix format without leading or trailing / */
char *device; /* raw device path */
WCHAR label_conf[12]; /* drive label as cfg'd in wine config */
WCHAR label_read[12]; /* drive label as read from device */
DWORD serial_conf; /* drive serial number as cfg'd in wine config */
UINT type; /* drive type */
UINT flags; /* drive flags */
UINT codepage; /* drive code page */
dev_t dev; /* unix device number */
ino_t ino; /* unix inode number */
} DOSDRIVE;
static const WCHAR DRIVE_Types[][8] =
{
{ 0 }, /* DRIVE_UNKNOWN */
{ 0 }, /* DRIVE_NO_ROOT_DIR */
{'f','l','o','p','p','y',0}, /* DRIVE_REMOVABLE */
{'h','d',0}, /* DRIVE_FIXED */
{'n','e','t','w','o','r','k',0}, /* DRIVE_REMOTE */
{'c','d','r','o','m',0}, /* DRIVE_CDROM */
{'r','a','m','d','i','s','k',0} /* DRIVE_RAMDISK */
};
/* Known filesystem types */
typedef struct
{
const WCHAR name[6];
UINT flags;
} FS_DESCR;
static const FS_DESCR DRIVE_Filesystems[] =
{
{ {'u','n','i','x',0}, DRIVE_CASE_SENSITIVE | DRIVE_CASE_PRESERVING },
{ {'m','s','d','o','s',0}, DRIVE_SHORT_NAMES },
{ {'d','o','s',0}, DRIVE_SHORT_NAMES },
{ {'f','a','t',0}, DRIVE_SHORT_NAMES },
{ {'v','f','a','t',0}, DRIVE_CASE_PRESERVING },
{ {'w','i','n','9','5',0}, DRIVE_CASE_PRESERVING },
{ { 0 }, 0 }
};
static DOSDRIVE DOSDrives[MAX_DOS_DRIVES];
static int DRIVE_CurDrive = -1;
static HTASK16 DRIVE_LastTask = 0;
/* strdup on the process heap */
inline static char *heap_strdup( const char *str )
{
INT len = strlen(str) + 1;
LPSTR p = HeapAlloc( GetProcessHeap(), 0, len );
if (p) memcpy( p, str, len );
return p;
}
#define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
extern void CDROM_InitRegistry(int dev);
/***********************************************************************
* DRIVE_GetDriveType
*/
static inline UINT DRIVE_GetDriveType( INT drive, LPCWSTR value )
{
int i;
for (i = 0; i < sizeof(DRIVE_Types)/sizeof(DRIVE_Types[0]); i++)
{
if (!strcmpiW( value, DRIVE_Types[i] )) return i;
}
MESSAGE("Drive %c: unknown drive type %s, defaulting to 'hd'.\n",
'A' + drive, debugstr_w(value) );
return DRIVE_FIXED;
}
/***********************************************************************
* DRIVE_GetFSFlags
*/
static UINT DRIVE_GetFSFlags( INT drive, LPCWSTR value )
{
const FS_DESCR *descr;
for (descr = DRIVE_Filesystems; *descr->name; descr++)
if (!strcmpiW( value, descr->name )) return descr->flags;
MESSAGE("Drive %c: unknown filesystem type %s, defaulting to 'win95'.\n",
'A' + drive, debugstr_w(value) );
return DRIVE_CASE_PRESERVING;
}
/***********************************************************************
* DRIVE_Init
*/
int DRIVE_Init(void)
{
int i, len, count = 0;
WCHAR driveW[] = {'M','a','c','h','i','n','e','\\','S','o','f','t','w','a','r','e','\\',
'W','i','n','e','\\','W','i','n','e','\\',
'C','o','n','f','i','g','\\','D','r','i','v','e',' ','A',0};
WCHAR drive_env[] = {'=','A',':',0};
WCHAR path[MAX_PATHNAME_LEN];
char tmp[MAX_PATHNAME_LEN*sizeof(WCHAR) + sizeof(KEY_VALUE_PARTIAL_INFORMATION)];
struct stat drive_stat_buffer;
WCHAR *p;
DOSDRIVE *drive;
HKEY hkey;
DWORD dummy;
OBJECT_ATTRIBUTES attr;
UNICODE_STRING nameW;
static const WCHAR PathW[] = {'P','a','t','h',0};
static const WCHAR CodepageW[] = {'C','o','d','e','p','a','g','e',0};
static const WCHAR LabelW[] = {'L','a','b','e','l',0};
static const WCHAR SerialW[] = {'S','e','r','i','a','l',0};
static const WCHAR TypeW[] = {'T','y','p','e',0};
static const WCHAR FilesystemW[] = {'F','i','l','e','s','y','s','t','e','m',0};
static const WCHAR DeviceW[] = {'D','e','v','i','c','e',0};
static const WCHAR ReadVolInfoW[] = {'R','e','a','d','V','o','l','I','n','f','o',0};
static const WCHAR FailReadOnlyW[] = {'F','a','i','l','R','e','a','d','O','n','l','y',0};
static const WCHAR driveC_labelW[] = {'D','r','i','v','e',' ','C',' ',' ',' ',' ',0};
attr.Length = sizeof(attr);
attr.RootDirectory = 0;
attr.ObjectName = &nameW;
attr.Attributes = 0;
attr.SecurityDescriptor = NULL;
attr.SecurityQualityOfService = NULL;
for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
{
RtlInitUnicodeString( &nameW, driveW );
nameW.Buffer[(nameW.Length / sizeof(WCHAR)) - 1] = 'A' + i;
if (NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr ) != STATUS_SUCCESS) continue;
/* Get the code page number */
RtlInitUnicodeString( &nameW, CodepageW );
if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
{
WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
drive->codepage = strtolW( data, NULL, 10 );
}
/* Get the root path */
RtlInitUnicodeString( &nameW, PathW );
if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
{
WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
ExpandEnvironmentStringsW( data, path, sizeof(path)/sizeof(WCHAR) );
p = path + strlenW(path) - 1;
while ((p > path) && (*p == '/')) *p-- = '\0';
if (path[0] == '/')
{
len = WideCharToMultiByte(drive->codepage, 0, path, -1, NULL, 0, NULL, NULL);
drive->root = HeapAlloc(GetProcessHeap(), 0, len);
WideCharToMultiByte(drive->codepage, 0, path, -1, drive->root, len, NULL, NULL);
}
else
{
/* relative paths are relative to config dir */
const char *config = wine_get_config_dir();
len = strlen(config);
len += WideCharToMultiByte(drive->codepage, 0, path, -1, NULL, 0, NULL, NULL) + 2;
drive->root = HeapAlloc( GetProcessHeap(), 0, len );
len -= sprintf( drive->root, "%s/", config );
WideCharToMultiByte(drive->codepage, 0, path, -1,
drive->root + strlen(drive->root), len, NULL, NULL);
}
if (stat( drive->root, &drive_stat_buffer ))
{
MESSAGE("Could not stat %s (%s), ignoring drive %c:\n",
drive->root, strerror(errno), 'A' + i);
HeapFree( GetProcessHeap(), 0, drive->root );
drive->root = NULL;
goto next;
}
if (!S_ISDIR(drive_stat_buffer.st_mode))
{
MESSAGE("%s is not a directory, ignoring drive %c:\n",
drive->root, 'A' + i );
HeapFree( GetProcessHeap(), 0, drive->root );
drive->root = NULL;
goto next;
}
drive->dos_cwd = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(drive->dos_cwd[0]));
drive->unix_cwd = heap_strdup( "" );
drive->device = NULL;
drive->flags = 0;
drive->dev = drive_stat_buffer.st_dev;
drive->ino = drive_stat_buffer.st_ino;
/* Get the drive type */
RtlInitUnicodeString( &nameW, TypeW );
if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
{
WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
drive->type = DRIVE_GetDriveType( i, data );
}
else drive->type = DRIVE_FIXED;
/* Get the drive label */
RtlInitUnicodeString( &nameW, LabelW );
if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
{
WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
lstrcpynW( drive->label_conf, data, 12 );
}
if ((len = strlenW(drive->label_conf)) < 11)
{
/* Pad label with spaces */
while(len < 11) drive->label_conf[len++] = ' ';
drive->label_conf[11] = '\0';
}
/* Get the serial number */
RtlInitUnicodeString( &nameW, SerialW );
if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
{
WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
drive->serial_conf = strtoulW( data, NULL, 16 );
}
else drive->serial_conf = 12345678;
/* Get the filesystem type */
RtlInitUnicodeString( &nameW, FilesystemW );
if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
{
WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
drive->flags = DRIVE_GetFSFlags( i, data );
}
else drive->flags = DRIVE_CASE_PRESERVING;
/* Get the device */
RtlInitUnicodeString( &nameW, DeviceW );
if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
{
WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
len = WideCharToMultiByte(CP_ACP, 0, data, -1, NULL, 0, NULL, NULL);
drive->device = HeapAlloc(GetProcessHeap(), 0, len);
WideCharToMultiByte(drive->codepage, 0, data, -1, drive->device, len, NULL, NULL);
RtlInitUnicodeString( &nameW, ReadVolInfoW );
if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
{
WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
if (IS_OPTION_TRUE(data[0])) drive->flags |= DRIVE_READ_VOL_INFO;
}
else drive->flags |= DRIVE_READ_VOL_INFO;
if (drive->type == DRIVE_CDROM)
{
int cd_fd;
if ((cd_fd = open(drive->device, O_RDONLY|O_NONBLOCK)) != -1)
{
CDROM_InitRegistry(cd_fd);
close(cd_fd);
}
}
}
/* Get the FailReadOnly flag */
RtlInitUnicodeString( &nameW, FailReadOnlyW );
if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
{
WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
if (IS_OPTION_TRUE(data[0])) drive->flags |= DRIVE_FAIL_READ_ONLY;
}
/* Make the first hard disk the current drive */
if ((DRIVE_CurDrive == -1) && (drive->type == DRIVE_FIXED))
DRIVE_CurDrive = i;
count++;
TRACE("Drive %c: path=%s type=%s label=%s serial=%08lx "
"flags=%08x codepage=%u dev=%x ino=%x\n",
'A' + i, drive->root, debugstr_w(DRIVE_Types[drive->type]),
debugstr_w(drive->label_conf), drive->serial_conf, drive->flags,
drive->codepage, (int)drive->dev, (int)drive->ino );
}
next:
NtClose( hkey );
}
if (!count)
{
MESSAGE("Warning: no valid DOS drive found, check your configuration file.\n" );
/* Create a C drive pointing to Unix root dir */
DOSDrives[2].root = heap_strdup( "/" );
DOSDrives[2].dos_cwd = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DOSDrives[2].dos_cwd[0]));
DOSDrives[2].unix_cwd = heap_strdup( "" );
strcpyW( DOSDrives[2].label_conf, driveC_labelW );
DOSDrives[2].serial_conf = 12345678;
DOSDrives[2].type = DRIVE_FIXED;
DOSDrives[2].device = NULL;
DOSDrives[2].flags = 0;
DRIVE_CurDrive = 2;
}
/* Make sure the current drive is valid */
if (DRIVE_CurDrive == -1)
{
for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
{
if (drive->root && !(drive->flags & DRIVE_DISABLED))
{
DRIVE_CurDrive = i;
break;
}
}
}
/* get current working directory info for all drives */
for (i = 0; i < MAX_DOS_DRIVES; i++, drive_env[1]++)
{
if (!GetEnvironmentVariableW(drive_env, path, MAX_PATHNAME_LEN)) continue;
/* sanity check */
if (toupperW(path[0]) != drive_env[1] || path[1] != ':') continue;
DRIVE_Chdir( i, path + 2 );
}
return 1;
}
/***********************************************************************
* DRIVE_IsValid
*/
int DRIVE_IsValid( int drive )
{
if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
return (DOSDrives[drive].root &&
!(DOSDrives[drive].flags & DRIVE_DISABLED));
}
/***********************************************************************
* DRIVE_GetCurrentDrive
*/
int DRIVE_GetCurrentDrive(void)
{
TDB *pTask = GlobalLock16(GetCurrentTask());
if (pTask && (pTask->curdrive & 0x80)) return pTask->curdrive & ~0x80;
return DRIVE_CurDrive;
}
/***********************************************************************
* DRIVE_SetCurrentDrive
*/
int DRIVE_SetCurrentDrive( int drive )
{
TDB *pTask = GlobalLock16(GetCurrentTask());
if (!DRIVE_IsValid( drive ))
{
SetLastError( ERROR_INVALID_DRIVE );
return 0;
}
TRACE("%c:\n", 'A' + drive );
DRIVE_CurDrive = drive;
if (pTask) pTask->curdrive = drive | 0x80;
return 1;
}
/***********************************************************************
* DRIVE_FindDriveRoot
*
* Find a drive for which the root matches the beginning of the given path.
* This can be used to translate a Unix path into a drive + DOS path.
* Return value is the drive, or -1 on error. On success, path is modified
* to point to the beginning of the DOS path.
*
* Note: path must be in the encoding of the underlying Unix file system.
*/
int DRIVE_FindDriveRoot( const char **path )
{
/* Starting with the full path, check if the device and inode match any of
* the wine 'drives'. If not then remove the last path component and try
* again. If the last component was a '..' then skip a normal component
* since it's a directory that's ascended back out of.
*/
int drive, level, len;
char buffer[MAX_PATHNAME_LEN];
char *p;
struct stat st;
strcpy( buffer, *path );
while ((p = strchr( buffer, '\\' )) != NULL)
*p = '/';
len = strlen(buffer);
/* strip off trailing slashes */
while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
for (;;)
{
/* Find the drive */
if (stat( buffer, &st ) == 0 && S_ISDIR( st.st_mode ))
{
for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
{
if (!DOSDrives[drive].root ||
(DOSDrives[drive].flags & DRIVE_DISABLED))
continue;
if ((DOSDrives[drive].dev == st.st_dev) &&
(DOSDrives[drive].ino == st.st_ino))
{
if (len == 1) len = 0; /* preserve root slash in returned path */
TRACE( "%s -> drive %c:, root='%s', name='%s'\n",
*path, 'A' + drive, buffer, *path + len);
*path += len;
if (!**path) *path = "\\";
return drive;
}
}
}
if (len <= 1) return -1; /* reached root */
level = 0;
while (level < 1)
{
/* find start of the last path component */
while (len > 1 && buffer[len - 1] != '/') len--;
if (!buffer[len]) break; /* empty component -> reached root */
/* does removing it take us up a level? */
if (strcmp( buffer + len, "." ) != 0)
level += strcmp( buffer + len, ".." ) ? 1 : -1;
buffer[len] = 0;
/* strip off trailing slashes */
while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
}
}
}
/***********************************************************************
* DRIVE_FindDriveRootW
*
* Unicode version of DRIVE_FindDriveRoot.
*/
int DRIVE_FindDriveRootW( LPCWSTR *path )
{
int drive, level, len;
WCHAR buffer[MAX_PATHNAME_LEN];
WCHAR *p;
struct stat st;
strcpyW( buffer, *path );
while ((p = strchrW( buffer, '\\' )) != NULL)
*p = '/';
len = strlenW(buffer);
/* strip off trailing slashes */
while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
for (;;)
{
int codepage = -1;
/* Find the drive */
for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
{
char buffA[MAX_PATHNAME_LEN];
if (!DOSDrives[drive].root ||
(DOSDrives[drive].flags & DRIVE_DISABLED))
continue;
if (codepage != DOSDrives[drive].codepage)
{
WideCharToMultiByte( DOSDrives[drive].codepage, 0, buffer, -1,
buffA, sizeof(buffA), NULL, NULL );
if (stat( buffA, &st ) == -1 || !S_ISDIR( st.st_mode ))
{
codepage = -1;
continue;
}
codepage = DOSDrives[drive].codepage;
}
if ((DOSDrives[drive].dev == st.st_dev) &&
(DOSDrives[drive].ino == st.st_ino))
{
static const WCHAR rootW[] = {'\\',0};
if (len == 1) len = 0; /* preserve root slash in returned path */
TRACE( "%s -> drive %c:, root=%s, name=%s\n",
debugstr_w(*path), 'A' + drive, debugstr_w(buffer), debugstr_w(*path + len));
*path += len;
if (!**path) *path = rootW;
return drive;
}
}
if (len <= 1) return -1; /* reached root */
level = 0;
while (level < 1)
{
static const WCHAR dotW[] = {'.',0};
static const WCHAR dotdotW[] = {'.','.',0};
/* find start of the last path component */
while (len > 1 && buffer[len - 1] != '/') len--;
if (!buffer[len]) break; /* empty component -> reached root */
/* does removing it take us up a level? */
if (strcmpW( buffer + len, dotW ) != 0)
level += strcmpW( buffer + len, dotdotW ) ? 1 : -1;
buffer[len] = 0;
/* strip off trailing slashes */
while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
}
}
}
/***********************************************************************
* DRIVE_GetRoot
*/
const char * DRIVE_GetRoot( int drive )
{
if (!DRIVE_IsValid( drive )) return NULL;
return DOSDrives[drive].root;
}
/***********************************************************************
* DRIVE_GetDosCwd
*/
LPCWSTR DRIVE_GetDosCwd( int drive )
{
TDB *pTask = GlobalLock16(GetCurrentTask());
if (!DRIVE_IsValid( drive )) return NULL;
/* Check if we need to change the directory to the new task. */
if (pTask && (pTask->curdrive & 0x80) && /* The task drive is valid */
((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
(DRIVE_LastTask != GetCurrentTask())) /* and the task changed */
{
static const WCHAR rootW[] = {'\\',0};
WCHAR curdirW[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, pTask->curdir, -1, curdirW, MAX_PATH);
/* Perform the task-switch */
if (!DRIVE_Chdir( drive, curdirW )) DRIVE_Chdir( drive, rootW );
DRIVE_LastTask = GetCurrentTask();
}
return DOSDrives[drive].dos_cwd;
}
/***********************************************************************
* DRIVE_GetUnixCwd
*/
const char * DRIVE_GetUnixCwd( int drive )
{
TDB *pTask = GlobalLock16(GetCurrentTask());
if (!DRIVE_IsValid( drive )) return NULL;
/* Check if we need to change the directory to the new task. */
if (pTask && (pTask->curdrive & 0x80) && /* The task drive is valid */
((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
(DRIVE_LastTask != GetCurrentTask())) /* and the task changed */
{
static const WCHAR rootW[] = {'\\',0};
WCHAR curdirW[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, pTask->curdir, -1, curdirW, MAX_PATH);
/* Perform the task-switch */
if (!DRIVE_Chdir( drive, curdirW )) DRIVE_Chdir( drive, rootW );
DRIVE_LastTask = GetCurrentTask();
}
return DOSDrives[drive].unix_cwd;
}
/***********************************************************************
* DRIVE_GetDevice
*/
const char * DRIVE_GetDevice( int drive )
{
return (DRIVE_IsValid( drive )) ? DOSDrives[drive].device : NULL;
}
/******************************************************************
* static WORD CDROM_Data_FindBestVoldesc
*
*
*/
static WORD CDROM_Data_FindBestVoldesc(int fd)
{
BYTE cur_vd_type, max_vd_type = 0;
unsigned int offs, best_offs = 0, extra_offs = 0;
char sig[3];
for (offs = 0x8000; offs <= 0x9800; offs += 0x800)
{
/* if 'CDROM' occurs at position 8, this is a pre-iso9660 cd, and
* the volume label is displaced forward by 8
*/
lseek(fd, offs + 11, SEEK_SET); /* check for non-ISO9660 signature */
read(fd, &sig, 3);
if ((sig[0] == 'R') && (sig[1] == 'O') && (sig[2]=='M'))
{
extra_offs = 8;
}
lseek(fd, offs + extra_offs, SEEK_SET);
read(fd, &cur_vd_type, 1);
if (cur_vd_type == 0xff) /* voldesc set terminator */
break;
if (cur_vd_type > max_vd_type)
{
max_vd_type = cur_vd_type;
best_offs = offs + extra_offs;
}
}
return best_offs;
}
/***********************************************************************
* DRIVE_ReadSuperblock
*
* NOTE
* DRIVE_SetLabel and DRIVE_SetSerialNumber use this in order
* to check, that they are writing on a FAT filesystem !
*/
int DRIVE_ReadSuperblock (int drive, char * buff)
{
#define DRIVE_SUPER 96
int fd;
off_t offs;
int ret = 0;
struct stat stat_buf;
memset(buff, 0, DRIVE_SUPER);
/* O_NONBLOCK in case we're opening FIFO; will be reset later */
if ((fd = open(DOSDrives[drive].device, O_RDONLY|O_NOCTTY|O_NONBLOCK)) != -1) {
if (fstat(fd, &stat_buf) < 0) { /* shouldn't happen since we just opened that file */
ERR("fstat() failed for opened device '%s' (drive %c:) ! IT SHOULDN'T HAPPEN !!!\n",
DOSDrives[drive].device, 'A'+drive);
ret = -1;
} else if (!S_ISBLK(stat_buf.st_mode)) {
ERR("Device '%s' (drive %c:) is not a block device - check your config\n",
DOSDrives[drive].device, 'A'+drive);
ret = -1;
/* reset O_NONBLOCK */
} else if (fcntl(fd, F_SETFL, 0) < 0 || fcntl(fd, F_GETFL) & O_NONBLOCK) {
ERR("fcntl() failed to reset O_NONBLOCK for device '%s' (drive %c:)\n",
DOSDrives[drive].device, 'A'+drive);
ret = -1;
}
if (ret) {
close(fd);
fd = -1;
}
} else {
if (!DOSDrives[drive].device)
ERR("No device configured for drive %c: !\n", 'A'+drive);
else
ERR("Couldn't open device '%s' for drive %c: ! (%s)\n", DOSDrives[drive].device, 'A'+drive,
(stat(DOSDrives[drive].device, &stat_buf)) ?
"not available or symlink not valid ?" : "no permission");
}
if (fd == -1) {
ERR("Can't read drive volume info ! Either pre-set it or make sure the device to read it from is accessible !\n");
return -1;
}
switch(DOSDrives[drive].type)
{
case DRIVE_REMOVABLE:
case DRIVE_FIXED:
offs = 0;
break;
case DRIVE_CDROM:
offs = CDROM_Data_FindBestVoldesc(fd);
break;
default:
offs = 0;
break;
}
if ((offs) && (lseek(fd,offs,SEEK_SET)!=offs))
{
ret = -4;
goto the_end;
}
if (read(fd,buff,DRIVE_SUPER)!=DRIVE_SUPER)
{
ret = -2;
goto the_end;
}
switch(DOSDrives[drive].type)
{
case DRIVE_REMOVABLE:
case DRIVE_FIXED:
if ((buff[0x26]!=0x29) || /* Check for FAT present */
/* FIXME: do really all FAT have their name beginning with
"FAT" ? (At least FAT12, FAT16 and FAT32 have :) */
memcmp( buff+0x36,"FAT",3))
{
ERR("The filesystem is not FAT !! (device=%s)\n",
DOSDrives[drive].device);
ret = -3;
goto the_end;
}
break;
case DRIVE_CDROM:
if (strncmp(&buff[1],"CD001",5)) /* Check for iso9660 present */
{
ret = -3;
goto the_end;
}
/* FIXME: do we need to check for "CDROM", too ? (high sierra) */
break;
default:
ret = -3;
goto the_end;
}
return close(fd);
the_end:
close(fd);
return ret;
}
/***********************************************************************
* DRIVE_WriteSuperblockEntry
*
* NOTE
* We are writing as little as possible (ie. not the whole SuperBlock)
* not to interfere with kernel. The drive can be mounted !
*/
int DRIVE_WriteSuperblockEntry (int drive, off_t ofs, size_t len, char * buff)
{
int fd;
if ((fd=open(DOSDrives[drive].device,O_WRONLY))==-1)
{
ERR("Cannot open the device %s (for writing)\n",
DOSDrives[drive].device);
return -1;
}
if (lseek(fd,ofs,SEEK_SET)!=ofs)
{
ERR("lseek failed on device %s !\n",
DOSDrives[drive].device);
close(fd);
return -2;
}
if (write(fd,buff,len)!=len)
{
close(fd);
ERR("Cannot write on %s !\n",
DOSDrives[drive].device);
return -3;
}
return close (fd);
}
/******************************************************************
* static HANDLE CDROM_Open
*
*
*/
static HANDLE CDROM_Open(int drive)
{
WCHAR root[] = {'\\','\\','.','\\','A',':',0};
root[4] += drive;
return CreateFileW(root, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
}
/**************************************************************************
* CDROM_Data_GetLabel [internal]
*/
DWORD CDROM_Data_GetLabel(int drive, WCHAR *label)
{
#define LABEL_LEN 32+1
int dev = open(DOSDrives[drive].device, O_RDONLY|O_NONBLOCK);
WORD offs = CDROM_Data_FindBestVoldesc(dev);
WCHAR label_read[LABEL_LEN]; /* Unicode possible, too */
DWORD unicode_id = 0;
if (offs)
{
if ((lseek(dev, offs+0x58, SEEK_SET) == offs+0x58)
&& (read(dev, &unicode_id, 3) == 3))
{
int ver = (unicode_id & 0xff0000) >> 16;
if ((lseek(dev, offs+0x28, SEEK_SET) != offs+0x28)
|| (read(dev, &label_read, LABEL_LEN) != LABEL_LEN))
goto failure;
close(dev);
if ((LOWORD(unicode_id) == 0x2f25) /* Unicode ID */
&& ((ver == 0x40) || (ver == 0x43) || (ver == 0x45)))
{ /* yippee, unicode */
int i;
WORD ch;
for (i=0; i<LABEL_LEN;i++)
{ /* Motorola -> Intel Unicode conversion :-\ */
ch = label_read[i];
label_read[i] = (ch << 8) | (ch >> 8);
}
strncpyW(label, label_read, 11);
label[11] = 0;
}
else
{
MultiByteToWideChar(DOSDrives[drive].codepage, 0, (LPSTR)label_read, -1, label, 11);
label[11] = '\0';
}
return 1;
}
}
failure:
close(dev);
ERR("error reading label !\n");
return 0;
}
/**************************************************************************
* CDROM_GetLabel [internal]
*/
static DWORD CDROM_GetLabel(int drive, WCHAR *label)
{
HANDLE h;
CDROM_DISK_DATA cdd;
DWORD br, ret = 1;
BOOL r;
h = CDROM_Open(drive);
if( !h )
return 0;
r = DeviceIoControl(h, IOCTL_CDROM_DISK_TYPE, NULL,
0, &cdd, sizeof(cdd), &br, 0);
CloseHandle( h );
if( !r )
return 0;
switch (cdd.DiskData & 0x03)
{
case CDROM_DISK_DATA_TRACK:
if (!CDROM_Data_GetLabel(drive, label))
ret = 0;
break;
case CDROM_DISK_AUDIO_TRACK:
{
static const WCHAR audioCD[] = {'A','u','d','i','o',' ','C','D',' ',' ',' ',0};
strcpyW(label, audioCD);
break;
}
case CDROM_DISK_DATA_TRACK|CDROM_DISK_AUDIO_TRACK:
FIXME("Need to get the label of a mixed mode CD!\n");
/* This assumes that the first track is a data track! */
/* I guess the correct way would be to enumerate all data tracks
and check each for the title */
if (!CDROM_Data_GetLabel(drive, label))
ret = 0;
break;
case 0:
ret = 0;
break;
}
TRACE("CD: label is %s\n", debugstr_w(label));
return ret;
}
/***********************************************************************
* DRIVE_GetLabel
*/
LPCWSTR DRIVE_GetLabel( int drive )
{
int read = 0;
char buff[DRIVE_SUPER];
int offs = -1;
if (!DRIVE_IsValid( drive )) return NULL;
if (DOSDrives[drive].type == DRIVE_CDROM)
{
read = CDROM_GetLabel(drive, DOSDrives[drive].label_read);
}
else
if (DOSDrives[drive].flags & DRIVE_READ_VOL_INFO)
{
if (DRIVE_ReadSuperblock(drive,(char *) buff))
ERR("Invalid or unreadable superblock on %s (%c:).\n",
DOSDrives[drive].device, (char)(drive+'A'));
else {
if (DOSDrives[drive].type == DRIVE_REMOVABLE ||
DOSDrives[drive].type == DRIVE_FIXED)
offs = 0x2b;
/* FIXME: ISO9660 uses a 32 bytes long label. Should we do also? */
if (offs != -1)
MultiByteToWideChar(DOSDrives[drive].codepage, 0, buff+offs, 11,
DOSDrives[drive].label_read, 11);
DOSDrives[drive].label_read[11]='\0';
read = 1;
}
}
return (read) ?
DOSDrives[drive].label_read : DOSDrives[drive].label_conf;
}
#define CDFRAMES_PERSEC 75
#define CDFRAMES_PERMIN (CDFRAMES_PERSEC * 60)
#define FRAME_OF_ADDR(a) ((a)[0] * CDFRAMES_PERMIN + (a)[1] * CDFRAMES_PERSEC + (a)[2])
#define FRAME_OF_TOC(toc, idx) FRAME_OF_ADDR((toc).TrackData[idx - (toc).FirstTrack].Address)
/**************************************************************************
* CDROM_Audio_GetSerial [internal]
*/
static DWORD CDROM_Audio_GetSerial(HANDLE h)
{
unsigned long serial = 0;
int i;
WORD wMagic;
DWORD dwStart, dwEnd, br;
CDROM_TOC toc;
if (!DeviceIoControl(h, IOCTL_CDROM_READ_TOC, NULL, 0, &toc, sizeof(toc), &br, 0))
return 0;
/*
* wMagic collects the wFrames from track 1
* dwStart, dwEnd collect the beginning and end of the disc respectively, in
* frames.
* There it is collected for correcting the serial when there are less than
* 3 tracks.
*/
wMagic = toc.TrackData[0].Address[2];
dwStart = FRAME_OF_TOC(toc, toc.FirstTrack);
for (i = 0; i <= toc.LastTrack - toc.FirstTrack; i++) {
serial += (toc.TrackData[i].Address[0] << 16) |
(toc.TrackData[i].Address[1] << 8) | toc.TrackData[i].Address[2];
}
dwEnd = FRAME_OF_TOC(toc, toc.LastTrack + 1);
if (toc.LastTrack - toc.FirstTrack + 1 < 3)
serial += wMagic + (dwEnd - dwStart);
return serial;
}
/**************************************************************************
* CDROM_Data_GetSerial [internal]
*/
static DWORD CDROM_Data_GetSerial(int drive)
{
int dev = open(DOSDrives[drive].device, O_RDONLY|O_NONBLOCK);
WORD offs;
union {
unsigned long val;
unsigned char p[4];
} serial;
BYTE b0 = 0, b1 = 1, b2 = 2, b3 = 3;
if (dev == -1) return 0;
offs = CDROM_Data_FindBestVoldesc(dev);
serial.val = 0;
if (offs)
{
BYTE buf[2048];
RTL_OSVERSIONINFOEXW ovi;
int i;
lseek(dev, offs, SEEK_SET);
read(dev, buf, 2048);
/*
* OK, another braindead one... argh. Just believe it.
* Me$$ysoft chose to reverse the serial number in NT4/W2K.
* It's true and nobody will ever be able to change it.
*/
ovi.dwOSVersionInfoSize = sizeof(ovi);
RtlGetVersion(&ovi);
if ((ovi.dwPlatformId == VER_PLATFORM_WIN32_NT) && (ovi.dwMajorVersion >= 4))
{
b0 = 3; b1 = 2; b2 = 1; b3 = 0;
}
for (i = 0; i < 2048; i += 4)
{
/* DON'T optimize this into DWORD !! (breaks overflow) */
serial.p[b0] += buf[i+b0];
serial.p[b1] += buf[i+b1];
serial.p[b2] += buf[i+b2];
serial.p[b3] += buf[i+b3];
}
}
close(dev);
return serial.val;
}
/**************************************************************************
* CDROM_GetSerial [internal]
*/
static DWORD CDROM_GetSerial(int drive)
{
DWORD serial = 0;
HANDLE h;
CDROM_DISK_DATA cdd;
DWORD br;
BOOL r;
TRACE("%d\n", drive);
h = CDROM_Open(drive);
if( !h )
return 0;
r = DeviceIoControl(h, IOCTL_CDROM_DISK_TYPE, NULL,
0, &cdd, sizeof(cdd), &br, 0);
if (!r)
{
CloseHandle(h);
return 0;
}
switch (cdd.DiskData & 0x03)
{
case CDROM_DISK_DATA_TRACK:
/* hopefully a data CD */
serial = CDROM_Data_GetSerial(drive);
break;
case CDROM_DISK_AUDIO_TRACK:
/* fall thru */
case CDROM_DISK_DATA_TRACK|CDROM_DISK_AUDIO_TRACK:
serial = CDROM_Audio_GetSerial(h);
break;
case 0:
break;
}
if (serial)
TRACE("CD serial number is %04x-%04x.\n", HIWORD(serial), LOWORD(serial));
CloseHandle(h);
return serial;
}
/***********************************************************************
* DRIVE_GetSerialNumber
*/
DWORD DRIVE_GetSerialNumber( int drive )
{
DWORD serial = 0;
char buff[DRIVE_SUPER];
TRACE("drive %d, type = %d\n", drive, DOSDrives[drive].type);
if (!DRIVE_IsValid( drive )) return 0;
if (DOSDrives[drive].flags & DRIVE_READ_VOL_INFO)
{
switch(DOSDrives[drive].type)
{
case DRIVE_REMOVABLE:
case DRIVE_FIXED:
if (DRIVE_ReadSuperblock(drive,(char *) buff))
MESSAGE("Invalid or unreadable superblock on %s (%c:)."
" Maybe not FAT?\n" ,
DOSDrives[drive].device, 'A'+drive);
else
serial = *((DWORD*)(buff+0x27));
break;
case DRIVE_CDROM:
serial = CDROM_GetSerial(drive);
break;
default:
FIXME("Serial number reading from file system on drive %c: not supported yet.\n", drive+'A');
}
}
return (serial) ? serial : DOSDrives[drive].serial_conf;
}
/***********************************************************************
* DRIVE_SetSerialNumber
*/
int DRIVE_SetSerialNumber( int drive, DWORD serial )
{
char buff[DRIVE_SUPER];
if (!DRIVE_IsValid( drive )) return 0;
if (DOSDrives[drive].flags & DRIVE_READ_VOL_INFO)
{
if ((DOSDrives[drive].type != DRIVE_REMOVABLE) &&
(DOSDrives[drive].type != DRIVE_FIXED)) return 0;
/* check, if the drive has a FAT filesystem */
if (DRIVE_ReadSuperblock(drive, buff)) return 0;
if (DRIVE_WriteSuperblockEntry(drive, 0x27, 4, (char *) &serial)) return 0;
return 1;
}
if (DOSDrives[drive].type == DRIVE_CDROM) return 0;
DOSDrives[drive].serial_conf = serial;
return 1;
}
/***********************************************************************
* DRIVE_GetType
*/
static UINT DRIVE_GetType( int drive )
{
if (!DRIVE_IsValid( drive )) return DRIVE_NO_ROOT_DIR;
return DOSDrives[drive].type;
}
/***********************************************************************
* DRIVE_GetFlags
*/
UINT DRIVE_GetFlags( int drive )
{
if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
return DOSDrives[drive].flags;
}
/***********************************************************************
* DRIVE_GetCodepage
*/
UINT DRIVE_GetCodepage( int drive )
{
if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
return DOSDrives[drive].codepage;
}
/***********************************************************************
* DRIVE_Chdir
*/
int DRIVE_Chdir( int drive, LPCWSTR path )
{
DOS_FULL_NAME full_name;
WCHAR buffer[MAX_PATHNAME_LEN];
LPSTR unix_cwd;
BY_HANDLE_FILE_INFORMATION info;
TDB *pTask = GlobalLock16(GetCurrentTask());
buffer[0] = 'A' + drive;
buffer[1] = ':';
buffer[2] = 0;
TRACE("(%s,%s)\n", debugstr_w(buffer), debugstr_w(path) );
strncpyW( buffer + 2, path, MAX_PATHNAME_LEN - 2 );
buffer[MAX_PATHNAME_LEN - 1] = 0; /* ensure 0 termination */
if (!DOSFS_GetFullName( buffer, TRUE, &full_name )) return 0;
if (!FILE_Stat( full_name.long_name, &info, NULL )) return 0;
if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
SetLastError( ERROR_FILE_NOT_FOUND );
return 0;
}
unix_cwd = full_name.long_name + strlen( DOSDrives[drive].root );
while (*unix_cwd == '/') unix_cwd++;
TRACE("(%c:): unix_cwd=%s dos_cwd=%s\n",
'A' + drive, unix_cwd, debugstr_w(full_name.short_name + 3) );
HeapFree( GetProcessHeap(), 0, DOSDrives[drive].dos_cwd );
HeapFree( GetProcessHeap(), 0, DOSDrives[drive].unix_cwd );
DOSDrives[drive].dos_cwd = HeapAlloc(GetProcessHeap(), 0, (strlenW(full_name.short_name) - 2) * sizeof(WCHAR));
strcpyW(DOSDrives[drive].dos_cwd, full_name.short_name + 3);
DOSDrives[drive].unix_cwd = heap_strdup( unix_cwd );
if (drive == DRIVE_CurDrive)
{
UNICODE_STRING dirW;
RtlInitUnicodeString( &dirW, full_name.short_name );
RtlSetCurrentDirectory_U( &dirW );
}
if (pTask && (pTask->curdrive & 0x80) &&
((pTask->curdrive & ~0x80) == drive))
{
WideCharToMultiByte(CP_ACP, 0, full_name.short_name + 2, -1,
pTask->curdir, sizeof(pTask->curdir), NULL, NULL);
DRIVE_LastTask = GetCurrentTask();
}
return 1;
}
/***********************************************************************
* DRIVE_Disable
*/
int DRIVE_Disable( int drive )
{
if ((drive < 0) || (drive >= MAX_DOS_DRIVES) || !DOSDrives[drive].root)
{
SetLastError( ERROR_INVALID_DRIVE );
return 0;
}
DOSDrives[drive].flags |= DRIVE_DISABLED;
return 1;
}
/***********************************************************************
* DRIVE_Enable
*/
int DRIVE_Enable( int drive )
{
if ((drive < 0) || (drive >= MAX_DOS_DRIVES) || !DOSDrives[drive].root)
{
SetLastError( ERROR_INVALID_DRIVE );
return 0;
}
DOSDrives[drive].flags &= ~DRIVE_DISABLED;
return 1;
}
/***********************************************************************
* DRIVE_SetLogicalMapping
*/
int DRIVE_SetLogicalMapping ( int existing_drive, int new_drive )
{
/* If new_drive is already valid, do nothing and return 0
otherwise, copy DOSDrives[existing_drive] to DOSDrives[new_drive] */
DOSDRIVE *old, *new;
old = DOSDrives + existing_drive;
new = DOSDrives + new_drive;
if ((existing_drive < 0) || (existing_drive >= MAX_DOS_DRIVES) ||
!old->root ||
(new_drive < 0) || (new_drive >= MAX_DOS_DRIVES))
{
SetLastError( ERROR_INVALID_DRIVE );
return 0;
}
if ( new->root )
{
TRACE("Can't map drive %c: to already existing drive %c:\n",
'A' + existing_drive, 'A' + new_drive );
/* it is already mapped there, so return success */
if (!strcmp(old->root,new->root))
return 1;
return 0;
}
new->root = heap_strdup( old->root );
new->dos_cwd = HeapAlloc(GetProcessHeap(), 0, (strlenW(old->dos_cwd) + 1) * sizeof(WCHAR));
strcpyW(new->dos_cwd, old->dos_cwd);
new->unix_cwd = heap_strdup( old->unix_cwd );
new->device = heap_strdup( old->device );
memcpy ( new->label_conf, old->label_conf, 12 );
memcpy ( new->label_read, old->label_read, 12 );
new->serial_conf = old->serial_conf;
new->type = old->type;
new->flags = old->flags;
new->dev = old->dev;
new->ino = old->ino;
TRACE("Drive %c: is now equal to drive %c:\n",
'A' + new_drive, 'A' + existing_drive );
return 1;
}
/***********************************************************************
* DRIVE_OpenDevice
*
* Open the drive raw device and return a Unix fd (or -1 on error).
*/
int DRIVE_OpenDevice( int drive, int flags )
{
if (!DRIVE_IsValid( drive )) return -1;
return open( DOSDrives[drive].device, flags );
}
/***********************************************************************
* DRIVE_GetFreeSpace
*/
static int DRIVE_GetFreeSpace( int drive, PULARGE_INTEGER size,
PULARGE_INTEGER available )
{
struct statfs info;
if (!DRIVE_IsValid(drive))
{
SetLastError( ERROR_PATH_NOT_FOUND );
return 0;
}
/* FIXME: add autoconf check for this */
#if defined(__svr4__) || defined(_SCO_DS) || defined(__sun)
if (statfs( DOSDrives[drive].root, &info, 0, 0) < 0)
#else
if (statfs( DOSDrives[drive].root, &info) < 0)
#endif
{
FILE_SetDosError();
WARN("cannot do statfs(%s)\n", DOSDrives[drive].root);
return 0;
}
size->QuadPart = RtlEnlargedUnsignedMultiply( info.f_bsize, info.f_blocks );
#ifdef HAVE_STRUCT_STATFS_F_BAVAIL
available->QuadPart = RtlEnlargedUnsignedMultiply( info.f_bavail, info.f_bsize );
#else
# ifdef HAVE_STRUCT_STATFS_F_BFREE
available->QuadPart = RtlEnlargedUnsignedMultiply( info.f_bfree, info.f_bsize );
# else
# error "statfs has no bfree/bavail member!"
# endif
#endif
if (DOSDrives[drive].type == DRIVE_CDROM)
{ /* ALWAYS 0, even if no real CD-ROM mounted there !! */
available->QuadPart = 0;
}
return 1;
}
/***********************************************************************
* DRIVE_GetCurrentDirectory
* Returns "X:\\path\\etc\\".
*
* Despite the API description, return required length including the
* terminating null when buffer too small. This is the real behaviour.
*/
static UINT DRIVE_GetCurrentDirectory( UINT buflen, LPWSTR buf )
{
UINT ret;
LPCWSTR dos_cwd = DRIVE_GetDosCwd( DRIVE_GetCurrentDrive() );
static const WCHAR driveA_rootW[] = {'A',':','\\',0};
ret = strlenW(dos_cwd) + 3; /* length of WHOLE current directory */
if (ret >= buflen) return ret + 1;
strcpyW( buf, driveA_rootW );
buf[0] += DRIVE_GetCurrentDrive();
strcatW( buf, dos_cwd );
return ret;
}
/***********************************************************************
* DRIVE_BuildEnv
*
* Build the environment array containing the drives' current directories.
* Resulting pointer must be freed with HeapFree.
*/
char *DRIVE_BuildEnv(void)
{
int i, length = 0;
LPCWSTR cwd[MAX_DOS_DRIVES];
char *env, *p;
for (i = 0; i < MAX_DOS_DRIVES; i++)
{
if ((cwd[i] = DRIVE_GetDosCwd(i)) && cwd[i][0])
length += WideCharToMultiByte(DRIVE_GetCodepage(i), 0,
cwd[i], -1, NULL, 0, NULL, NULL) + 7;
}
if (!(env = HeapAlloc( GetProcessHeap(), 0, length+1 ))) return NULL;
for (i = 0, p = env; i < MAX_DOS_DRIVES; i++)
{
if (cwd[i] && cwd[i][0])
{
*p++ = '='; *p++ = 'A' + i; *p++ = ':';
*p++ = '='; *p++ = 'A' + i; *p++ = ':'; *p++ = '\\';
WideCharToMultiByte(DRIVE_GetCodepage(i), 0, cwd[i], -1, p, 0x7fffffff, NULL, NULL);
p += strlen(p) + 1;
}
}
*p = 0;
return env;
}
/***********************************************************************
* GetDiskFreeSpace (KERNEL.422)
*/
BOOL16 WINAPI GetDiskFreeSpace16( LPCSTR root, LPDWORD cluster_sectors,
LPDWORD sector_bytes, LPDWORD free_clusters,
LPDWORD total_clusters )
{
return GetDiskFreeSpaceA( root, cluster_sectors, sector_bytes,
free_clusters, total_clusters );
}
/***********************************************************************
* GetDiskFreeSpaceW (KERNEL32.@)
*
* Fails if expression resulting from current drive's dir and "root"
* is not a root dir of the target drive.
*
* UNDOC: setting some LPDWORDs to NULL is perfectly possible
* if the corresponding info is unneeded.
*
* FIXME: needs to support UNC names from Win95 OSR2 on.
*
* Behaviour under Win95a:
* CurrDir root result
* "E:\\TEST" "E:" FALSE
* "E:\\" "E:" TRUE
* "E:\\" "E" FALSE
* "E:\\" "\\" TRUE
* "E:\\TEST" "\\" TRUE
* "E:\\TEST" ":\\" FALSE
* "E:\\TEST" "E:\\" TRUE
* "E:\\TEST" "" FALSE
* "E:\\" "" FALSE (!)
* "E:\\" 0x0 TRUE
* "E:\\TEST" 0x0 TRUE (!)
* "E:\\TEST" "C:" TRUE (when CurrDir of "C:" set to "\\")
* "E:\\TEST" "C:" FALSE (when CurrDir of "C:" set to "\\TEST")
*/
BOOL WINAPI GetDiskFreeSpaceW( LPCWSTR root, LPDWORD cluster_sectors,
LPDWORD sector_bytes, LPDWORD free_clusters,
LPDWORD total_clusters )
{
int drive, sec_size;
ULARGE_INTEGER size,available;
LPCWSTR path;
DWORD cluster_sec;
TRACE("%s,%p,%p,%p,%p\n", debugstr_w(root), cluster_sectors, sector_bytes,
free_clusters, total_clusters);
if (!root || root[0] == '\\' || root[0] == '/')
drive = DRIVE_GetCurrentDrive();
else
if (root[0] && root[1] == ':') /* root contains drive tag */
{
drive = toupperW(root[0]) - 'A';
path = &root[2];
if (path[0] == '\0')
{
path = DRIVE_GetDosCwd(drive);
if (!path)
{
SetLastError(ERROR_PATH_NOT_FOUND);
return FALSE;
}
}
else
if (path[0] == '\\')
path++;
if (path[0]) /* oops, we are in a subdir */
{
SetLastError(ERROR_INVALID_NAME);
return FALSE;
}
}
else
{
if (!root[0])
SetLastError(ERROR_PATH_NOT_FOUND);
else
SetLastError(ERROR_INVALID_NAME);
return FALSE;
}
if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
/* Cap the size and available at 2GB as per specs. */
if ((size.s.HighPart) ||(size.s.LowPart > 0x7fffffff))
{
size.s.HighPart = 0;
size.s.LowPart = 0x7fffffff;
}
if ((available.s.HighPart) ||(available.s.LowPart > 0x7fffffff))
{
available.s.HighPart =0;
available.s.LowPart = 0x7fffffff;
}
sec_size = (DRIVE_GetType(drive)==DRIVE_CDROM) ? 2048 : 512;
size.s.LowPart /= sec_size;
available.s.LowPart /= sec_size;
/* FIXME: probably have to adjust those variables too for CDFS */
cluster_sec = 1;
while (cluster_sec * 65536 < size.s.LowPart) cluster_sec *= 2;
if (cluster_sectors)
*cluster_sectors = cluster_sec;
if (sector_bytes)
*sector_bytes = sec_size;
if (free_clusters)
*free_clusters = available.s.LowPart / cluster_sec;
if (total_clusters)
*total_clusters = size.s.LowPart / cluster_sec;
return TRUE;
}
/***********************************************************************
* GetDiskFreeSpaceA (KERNEL32.@)
*/
BOOL WINAPI GetDiskFreeSpaceA( LPCSTR root, LPDWORD cluster_sectors,
LPDWORD sector_bytes, LPDWORD free_clusters,
LPDWORD total_clusters )
{
UNICODE_STRING rootW;
BOOL ret = FALSE;
if (root)
{
if(!RtlCreateUnicodeStringFromAsciiz(&rootW, root))
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return FALSE;
}
}
else
rootW.Buffer = NULL;
ret = GetDiskFreeSpaceW(rootW.Buffer, cluster_sectors, sector_bytes,
free_clusters, total_clusters );
RtlFreeUnicodeString(&rootW);
return ret;
}
/***********************************************************************
* GetDiskFreeSpaceExW (KERNEL32.@)
*
* This function is used to acquire the size of the available and
* total space on a logical volume.
*
* RETURNS
*
* Zero on failure, nonzero upon success. Use GetLastError to obtain
* detailed error information.
*
*/
BOOL WINAPI GetDiskFreeSpaceExW( LPCWSTR root,
PULARGE_INTEGER avail,
PULARGE_INTEGER total,
PULARGE_INTEGER totalfree)
{
int drive;
ULARGE_INTEGER size,available;
if (!root) drive = DRIVE_GetCurrentDrive();
else
{ /* C: always works for GetDiskFreeSpaceEx */
if ((root[1]) && ((root[1] != ':') || (root[2] && root[2] != '\\')))
{
FIXME("there are valid root names which are not supported yet\n");
/* ..like UNC names, for instance. */
WARN("invalid root '%s'\n", debugstr_w(root));
return FALSE;
}
drive = toupperW(root[0]) - 'A';
}
if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
if (total)
{
total->s.HighPart = size.s.HighPart;
total->s.LowPart = size.s.LowPart;
}
if (totalfree)
{
totalfree->s.HighPart = available.s.HighPart;
totalfree->s.LowPart = available.s.LowPart;
}
if (avail)
{
if (FIXME_ON(dosfs))
{
/* On Windows2000, we need to check the disk quota
allocated for the user owning the calling process. We
don't want to be more obtrusive than necessary with the
FIXME messages, so don't print the FIXME unless Wine is
actually masquerading as Windows2000. */
RTL_OSVERSIONINFOEXW ovi;
ovi.dwOSVersionInfoSize = sizeof(ovi);
if (RtlGetVersion(&ovi))
{
if (ovi.dwPlatformId == VER_PLATFORM_WIN32_NT && ovi.dwMajorVersion > 4)
FIXME("no per-user quota support yet\n");
}
}
/* Quick hack, should eventually be fixed to work 100% with
Windows2000 (see comment above). */
avail->s.HighPart = available.s.HighPart;
avail->s.LowPart = available.s.LowPart;
}
return TRUE;
}
/***********************************************************************
* GetDiskFreeSpaceExA (KERNEL32.@)
*/
BOOL WINAPI GetDiskFreeSpaceExA( LPCSTR root, PULARGE_INTEGER avail,
PULARGE_INTEGER total,
PULARGE_INTEGER totalfree)
{
UNICODE_STRING rootW;
BOOL ret;
if (root) RtlCreateUnicodeStringFromAsciiz(&rootW, root);
else rootW.Buffer = NULL;
ret = GetDiskFreeSpaceExW( rootW.Buffer, avail, total, totalfree);
RtlFreeUnicodeString(&rootW);
return ret;
}
/***********************************************************************
* GetDriveType (KERNEL.136)
* This function returns the type of a drive in Win16.
* Note that it returns DRIVE_REMOTE for CD-ROMs, since MSCDEX uses the
* remote drive API. The return value DRIVE_REMOTE for CD-ROMs has been
* verified on Win 3.11 and Windows 95. Some programs rely on it, so don't
* do any pseudo-clever changes.
*
* RETURNS
* drivetype DRIVE_xxx
*/
UINT16 WINAPI GetDriveType16( UINT16 drive ) /* [in] number (NOT letter) of drive */
{
UINT type = DRIVE_GetType(drive);
TRACE("(%c:)\n", 'A' + drive );
if (type == DRIVE_CDROM) type = DRIVE_REMOTE;
return type;
}
/***********************************************************************
* GetDriveTypeW (KERNEL32.@)
*
* Returns the type of the disk drive specified. If root is NULL the
* root of the current directory is used.
*
* RETURNS
*
* Type of drive (from Win32 SDK):
*
* DRIVE_UNKNOWN unable to find out anything about the drive
* DRIVE_NO_ROOT_DIR nonexistent root dir
* DRIVE_REMOVABLE the disk can be removed from the machine
* DRIVE_FIXED the disk can not be removed from the machine
* DRIVE_REMOTE network disk
* DRIVE_CDROM CDROM drive
* DRIVE_RAMDISK virtual disk in RAM
*/
UINT WINAPI GetDriveTypeW(LPCWSTR root) /* [in] String describing drive */
{
int drive;
TRACE("(%s)\n", debugstr_w(root));
if (NULL == root) drive = DRIVE_GetCurrentDrive();
else
{
if ((root[1]) && (root[1] != ':'))
{
WARN("invalid root %s\n", debugstr_w(root));
return DRIVE_NO_ROOT_DIR;
}
drive = toupperW(root[0]) - 'A';
}
return DRIVE_GetType(drive);
}
/***********************************************************************
* GetDriveTypeA (KERNEL32.@)
*/
UINT WINAPI GetDriveTypeA( LPCSTR root )
{
UNICODE_STRING rootW;
UINT ret = 0;
if (root)
{
if( !RtlCreateUnicodeStringFromAsciiz(&rootW, root))
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return 0;
}
}
else
rootW.Buffer = NULL;
ret = GetDriveTypeW(rootW.Buffer);
RtlFreeUnicodeString(&rootW);
return ret;
}
/***********************************************************************
* GetCurrentDirectory (KERNEL.411)
*/
UINT16 WINAPI GetCurrentDirectory16( UINT16 buflen, LPSTR buf )
{
WCHAR cur_dirW[MAX_PATH];
DRIVE_GetCurrentDirectory(MAX_PATH, cur_dirW);
return (UINT16)WideCharToMultiByte(CP_ACP, 0, cur_dirW, -1, buf, buflen, NULL, NULL);
}
/***********************************************************************
* GetCurrentDirectoryW (KERNEL32.@)
*/
UINT WINAPI GetCurrentDirectoryW( UINT buflen, LPWSTR buf )
{
UINT ret;
WCHAR longname[MAX_PATHNAME_LEN];
WCHAR shortname[MAX_PATHNAME_LEN];
ret = DRIVE_GetCurrentDirectory(MAX_PATHNAME_LEN, shortname);
if ( ret > MAX_PATHNAME_LEN ) {
ERR_(file)("pathnamelength (%d) > MAX_PATHNAME_LEN!\n", ret );
return ret;
}
GetLongPathNameW(shortname, longname, MAX_PATHNAME_LEN);
ret = strlenW( longname ) + 1;
if (ret > buflen) return ret;
strcpyW(buf, longname);
return ret - 1;
}
/***********************************************************************
* GetCurrentDirectoryA (KERNEL32.@)
*/
UINT WINAPI GetCurrentDirectoryA( UINT buflen, LPSTR buf )
{
WCHAR bufferW[MAX_PATH];
DWORD ret, retW;
retW = GetCurrentDirectoryW(MAX_PATH, bufferW);
if (!retW)
ret = 0;
else if (retW > MAX_PATH)
{
SetLastError(ERROR_FILENAME_EXCED_RANGE);
ret = 0;
}
else
{
ret = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
if (buflen >= ret)
{
WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buf, buflen, NULL, NULL);
ret--; /* length without 0 */
}
}
return ret;
}
/***********************************************************************
* SetCurrentDirectory (KERNEL.412)
*/
BOOL16 WINAPI SetCurrentDirectory16( LPCSTR dir )
{
return SetCurrentDirectoryA( dir );
}
/***********************************************************************
* SetCurrentDirectoryW (KERNEL32.@)
*/
BOOL WINAPI SetCurrentDirectoryW( LPCWSTR dir )
{
int drive, olddrive = DRIVE_GetCurrentDrive();
if (!dir)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
if (dir[0] && (dir[1]==':'))
{
drive = toupperW( *dir ) - 'A';
dir += 2;
}
else
drive = olddrive;
/* WARNING: we need to set the drive before the dir, as DRIVE_Chdir
sets pTask->curdir only if pTask->curdrive is drive */
if (!(DRIVE_SetCurrentDrive( drive )))
return FALSE;
/* FIXME: what about empty strings? Add a \\ ? */
if (!DRIVE_Chdir( drive, dir )) {
DRIVE_SetCurrentDrive(olddrive);
return FALSE;
}
return TRUE;
}
/***********************************************************************
* SetCurrentDirectoryA (KERNEL32.@)
*/
BOOL WINAPI SetCurrentDirectoryA( LPCSTR dir )
{
UNICODE_STRING dirW;
BOOL ret = FALSE;
if (!dir)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
if (RtlCreateUnicodeStringFromAsciiz(&dirW, dir))
{
ret = SetCurrentDirectoryW(dirW.Buffer);
RtlFreeUnicodeString(&dirW);
}
else
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return ret;
}
/***********************************************************************
* GetLogicalDriveStringsA (KERNEL32.@)
*/
UINT WINAPI GetLogicalDriveStringsA( UINT len, LPSTR buffer )
{
int drive, count;
for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
if (DRIVE_IsValid(drive)) count++;
if ((count * 4) + 1 <= len)
{
LPSTR p = buffer;
for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
if (DRIVE_IsValid(drive))
{
*p++ = 'a' + drive;
*p++ = ':';
*p++ = '\\';
*p++ = '\0';
}
*p = '\0';
return count * 4;
}
else
return (count * 4) + 1; /* account for terminating null */
/* The API tells about these different return values */
}
/***********************************************************************
* GetLogicalDriveStringsW (KERNEL32.@)
*/
UINT WINAPI GetLogicalDriveStringsW( UINT len, LPWSTR buffer )
{
int drive, count;
for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
if (DRIVE_IsValid(drive)) count++;
if (count * 4 * sizeof(WCHAR) <= len)
{
LPWSTR p = buffer;
for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
if (DRIVE_IsValid(drive))
{
*p++ = (WCHAR)('a' + drive);
*p++ = (WCHAR)':';
*p++ = (WCHAR)'\\';
*p++ = (WCHAR)'\0';
}
*p = (WCHAR)'\0';
}
return count * 4 * sizeof(WCHAR);
}
/***********************************************************************
* GetLogicalDrives (KERNEL32.@)
*/
DWORD WINAPI GetLogicalDrives(void)
{
DWORD ret = 0;
int drive;
for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
{
if ( (DRIVE_IsValid(drive)) ||
(DOSDrives[drive].type == DRIVE_CDROM)) /* audio CD is also valid */
ret |= (1 << drive);
}
return ret;
}
/***********************************************************************
* GetVolumeInformationW (KERNEL32.@)
*/
BOOL WINAPI GetVolumeInformationW( LPCWSTR root, LPWSTR label,
DWORD label_len, DWORD *serial,
DWORD *filename_len, DWORD *flags,
LPWSTR fsname, DWORD fsname_len )
{
int drive;
LPWSTR cp;
/* FIXME, SetLastError()s missing */
if (!root) drive = DRIVE_GetCurrentDrive();
else
{
if (root[0] && root[1] != ':')
{
WARN("invalid root %s\n", debugstr_w(root));
return FALSE;
}
drive = toupperW(root[0]) - 'A';
}
if (!DRIVE_IsValid( drive )) return FALSE;
if (label && label_len)
{
strncpyW( label, DRIVE_GetLabel(drive), label_len );
label[label_len - 1] = 0; /* ensure 0 termination */
cp = label + strlenW(label);
while (cp != label && *(cp-1) == ' ') cp--;
*cp = '\0';
}
if (serial) *serial = DRIVE_GetSerialNumber(drive);
/* Set the filesystem information */
/* Note: we only emulate a FAT fs at present */
if (filename_len) {
if (DOSDrives[drive].flags & DRIVE_SHORT_NAMES)
*filename_len = 12;
else
*filename_len = 255;
}
if (flags)
{
*flags=0;
if (DOSDrives[drive].flags & DRIVE_CASE_SENSITIVE)
*flags|=FS_CASE_SENSITIVE;
if (DOSDrives[drive].flags & DRIVE_CASE_PRESERVING)
*flags|=FS_CASE_IS_PRESERVED;
}
if (fsname && fsname_len)
{
/* Diablo checks that return code ... */
if (DOSDrives[drive].type == DRIVE_CDROM)
{
static const WCHAR cdfsW[] = {'C','D','F','S',0};
strncpyW( fsname, cdfsW, fsname_len );
}
else
{
static const WCHAR fatW[] = {'F','A','T',0};
strncpyW( fsname, fatW, fsname_len );
}
fsname[fsname_len - 1] = 0; /* ensure 0 termination */
}
return TRUE;
}
/***********************************************************************
* GetVolumeInformationA (KERNEL32.@)
*/
BOOL WINAPI GetVolumeInformationA( LPCSTR root, LPSTR label,
DWORD label_len, DWORD *serial,
DWORD *filename_len, DWORD *flags,
LPSTR fsname, DWORD fsname_len )
{
UNICODE_STRING rootW;
LPWSTR labelW, fsnameW;
BOOL ret;
if (root) RtlCreateUnicodeStringFromAsciiz(&rootW, root);
else rootW.Buffer = NULL;
labelW = label ? HeapAlloc(GetProcessHeap(), 0, label_len * sizeof(WCHAR)) : NULL;
fsnameW = fsname ? HeapAlloc(GetProcessHeap(), 0, fsname_len * sizeof(WCHAR)) : NULL;
if ((ret = GetVolumeInformationW(rootW.Buffer, labelW, label_len, serial,
filename_len, flags, fsnameW, fsname_len)))
{
if (label) WideCharToMultiByte(CP_ACP, 0, labelW, -1, label, label_len, NULL, NULL);
if (fsname) WideCharToMultiByte(CP_ACP, 0, fsnameW, -1, fsname, fsname_len, NULL, NULL);
}
RtlFreeUnicodeString(&rootW);
if (labelW) HeapFree( GetProcessHeap(), 0, labelW );
if (fsnameW) HeapFree( GetProcessHeap(), 0, fsnameW );
return ret;
}
/***********************************************************************
* SetVolumeLabelW (KERNEL32.@)
*/
BOOL WINAPI SetVolumeLabelW( LPCWSTR root, LPCWSTR volname )
{
int drive;
/* FIXME, SetLastErrors missing */
if (!root) drive = DRIVE_GetCurrentDrive();
else
{
if ((root[1]) && (root[1] != ':'))
{
WARN("invalid root %s\n", debugstr_w(root));
return FALSE;
}
drive = toupperW(root[0]) - 'A';
}
if (!DRIVE_IsValid( drive )) return FALSE;
/* some copy protection stuff check this */
if (DOSDrives[drive].type == DRIVE_CDROM) return FALSE;
strncpyW(DOSDrives[drive].label_conf, volname, 12);
DOSDrives[drive].label_conf[12 - 1] = 0; /* ensure 0 termination */
return TRUE;
}
/***********************************************************************
* SetVolumeLabelA (KERNEL32.@)
*/
BOOL WINAPI SetVolumeLabelA(LPCSTR root, LPCSTR volname)
{
UNICODE_STRING rootW, volnameW;
BOOL ret;
if (root) RtlCreateUnicodeStringFromAsciiz(&rootW, root);
else rootW.Buffer = NULL;
if (volname) RtlCreateUnicodeStringFromAsciiz(&volnameW, volname);
else volnameW.Buffer = NULL;
ret = SetVolumeLabelW( rootW.Buffer, volnameW.Buffer );
RtlFreeUnicodeString(&rootW);
RtlFreeUnicodeString(&volnameW);
return ret;
}
/***********************************************************************
* GetVolumeNameForVolumeMountPointW (KERNEL32.@)
*/
DWORD WINAPI GetVolumeNameForVolumeMountPointW(LPWSTR str, DWORD a, DWORD b)
{
FIXME("(%s, %lx, %lx): stub\n", debugstr_w(str), a, b);
return 0;
}