blob: 94a56cf38e2c017a8bd1a77994f9af3addeab00f [file] [log] [blame]
/*
* 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
#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 "file.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 / */
char *device; /* raw device path */
dev_t dev; /* unix device number */
ino_t ino; /* unix inode number */
} DOSDRIVE;
#define MAX_DOS_DRIVES 26
static DOSDRIVE DOSDrives[MAX_DOS_DRIVES];
/* 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;
}
/***********************************************************************
* DRIVE_Init
*/
int DRIVE_Init(void)
{
int i, len, symlink_count = 0, 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 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;
char *root;
const char *config_dir = wine_get_config_dir();
static const WCHAR PathW[] = {'P','a','t','h',0};
static const WCHAR DeviceW[] = {'D','e','v','i','c','e',0};
attr.Length = sizeof(attr);
attr.RootDirectory = 0;
attr.ObjectName = &nameW;
attr.Attributes = 0;
attr.SecurityDescriptor = NULL;
attr.SecurityQualityOfService = NULL;
/* get the root of the drives from symlinks */
root = NULL;
for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
{
if (!root)
{
root = HeapAlloc( GetProcessHeap(), 0, strlen(config_dir) + sizeof("/dosdevices/a:") );
strcpy( root, config_dir );
strcat( root, "/dosdevices/a:" );
}
root[strlen(root)-2] = 'a' + i;
if (stat( root, &drive_stat_buffer ))
{
if (!lstat( root, &drive_stat_buffer))
MESSAGE("Could not stat %s (%s), ignoring drive %c:\n",
root, strerror(errno), 'a' + i);
continue;
}
if (!S_ISDIR(drive_stat_buffer.st_mode))
{
MESSAGE("%s is not a directory, ignoring drive %c:\n", root, 'a' + i );
continue;
}
drive->root = root;
drive->device = NULL;
drive->dev = drive_stat_buffer.st_dev;
drive->ino = drive_stat_buffer.st_ino;
root = NULL;
symlink_count++;
}
if (root) HeapFree( GetProcessHeap(), 0, root );
/* now get the parameters from the config file */
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 root path */
if (!symlink_count)
{
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(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL);
drive->root = HeapAlloc(GetProcessHeap(), 0, len);
WideCharToMultiByte(CP_UNIXCP, 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(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL) + 2;
drive->root = HeapAlloc( GetProcessHeap(), 0, len );
len -= sprintf( drive->root, "%s/", config );
WideCharToMultiByte(CP_UNIXCP, 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->device = NULL;
drive->dev = drive_stat_buffer.st_dev;
drive->ino = drive_stat_buffer.st_ino;
}
}
if (drive->root)
{
/* 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_UNIXCP, 0, data, -1, NULL, 0, NULL, NULL);
drive->device = HeapAlloc(GetProcessHeap(), 0, len);
WideCharToMultiByte(CP_UNIXCP, 0, data, -1, drive->device, len, NULL, NULL);
}
count++;
TRACE("Drive %c: path=%s dev=%x ino=%x\n",
'A' + i, drive->root, (int)drive->dev, (int)drive->ino );
}
next:
NtClose( hkey );
}
return 1;
}
/***********************************************************************
* DRIVE_IsValid
*/
int DRIVE_IsValid( int drive )
{
if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
return (DOSDrives[drive].root != NULL);
}
/***********************************************************************
* 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 );
for (p = buffer; *p; p++) if (*p == '\\') *p = '/';
len = p - 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) 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 );
for (p = buffer; *p; p++) if (*p == '\\') *p = '/';
len = p - buffer;
/* strip off trailing slashes */
while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
for (;;)
{
char buffA[MAX_PATHNAME_LEN];
WideCharToMultiByte( CP_UNIXCP, 0, buffer, -1, buffA, sizeof(buffA), NULL, NULL );
if (stat( buffA, &st ) == 0 && S_ISDIR( st.st_mode ))
{
/* Find the drive */
for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
{
if (!DOSDrives[drive].root) continue;
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_GetDevice
*/
const char * DRIVE_GetDevice( int drive )
{
return (DRIVE_IsValid( drive )) ? DOSDrives[drive].device : NULL;
}