Changed MoveFileEx to use ntdll functions, and moved it to

dlls/kernel/path.c.
This commit is contained in:
Alexandre Julliard 2004-04-21 22:27:34 +00:00
parent 4bb759e8ad
commit 31a7a7847e
2 changed files with 347 additions and 355 deletions

View File

@ -24,6 +24,8 @@
#include "config.h"
#include "wine/port.h"
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#define NONAMELESSUNION
@ -36,6 +38,7 @@
#include "winternl.h"
#include "kernel_private.h"
#include "file.h"
#include "wine/unicode.h"
#include "wine/debug.h"
@ -43,6 +46,157 @@ WINE_DEFAULT_DEBUG_CHANNEL(file);
#define MAX_PATHNAME_LEN 1024
/* check if a file name is for an executable file (.exe or .com) */
inline static BOOL is_executable( const WCHAR *name )
{
static const WCHAR exeW[] = {'.','e','x','e',0};
static const WCHAR comW[] = {'.','c','o','m',0};
int len = strlenW(name);
if (len < 4) return FALSE;
return (!strcmpiW( name + len - 4, exeW ) || !strcmpiW( name + len - 4, comW ));
}
/***********************************************************************
* add_boot_rename_entry
*
* Adds an entry to the registry that is loaded when windows boots and
* checks if there are some files to be removed or renamed/moved.
* <fn1> has to be valid and <fn2> may be NULL. If both pointers are
* non-NULL then the file is moved, otherwise it is deleted. The
* entry of the registrykey is always appended with two zero
* terminated strings. If <fn2> is NULL then the second entry is
* simply a single 0-byte. Otherwise the second filename goes
* there. The entries are prepended with \??\ before the path and the
* second filename gets also a '!' as the first character if
* MOVEFILE_REPLACE_EXISTING is set. After the final string another
* 0-byte follows to indicate the end of the strings.
* i.e.:
* \??\D:\test\file1[0]
* !\??\D:\test\file1_renamed[0]
* \??\D:\Test|delete[0]
* [0] <- file is to be deleted, second string empty
* \??\D:\test\file2[0]
* !\??\D:\test\file2_renamed[0]
* [0] <- indicates end of strings
*
* or:
* \??\D:\test\file1[0]
* !\??\D:\test\file1_renamed[0]
* \??\D:\Test|delete[0]
* [0] <- file is to be deleted, second string empty
* [0] <- indicates end of strings
*
*/
static BOOL add_boot_rename_entry( LPCWSTR fn1, LPCWSTR fn2, DWORD flags )
{
static const WCHAR PreString[] = {'\\','?','?','\\',0};
static const WCHAR ValueName[] = {'P','e','n','d','i','n','g',
'F','i','l','e','R','e','n','a','m','e',
'O','p','e','r','a','t','i','o','n','s',0};
static const WCHAR SessionW[] = {'M','a','c','h','i','n','e','\\',
'S','y','s','t','e','m','\\',
'C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\',
'C','o','n','t','r','o','l','\\',
'S','e','s','s','i','o','n',' ','M','a','n','a','g','e','r',0};
static const int info_size = FIELD_OFFSET( KEY_VALUE_PARTIAL_INFORMATION, Data );
OBJECT_ATTRIBUTES attr;
UNICODE_STRING nameW;
KEY_VALUE_PARTIAL_INFORMATION *info;
BOOL rc = FALSE;
HKEY Reboot = 0;
DWORD len0, len1, len2;
DWORD DataSize = 0;
BYTE *Buffer = NULL;
WCHAR *p;
attr.Length = sizeof(attr);
attr.RootDirectory = 0;
attr.ObjectName = &nameW;
attr.Attributes = 0;
attr.SecurityDescriptor = NULL;
attr.SecurityQualityOfService = NULL;
RtlInitUnicodeString( &nameW, SessionW );
if (NtCreateKey( &Reboot, KEY_ALL_ACCESS, &attr, 0, NULL, 0, NULL ) != STATUS_SUCCESS)
{
WARN("Error creating key for reboot managment [%s]\n",
"SYSTEM\\CurrentControlSet\\Control\\Session Manager");
return FALSE;
}
len0 = strlenW(PreString);
len1 = strlenW(fn1) + len0 + 1;
if (fn2)
{
len2 = strlenW(fn2) + len0 + 1;
if (flags & MOVEFILE_REPLACE_EXISTING) len2++; /* Plus 1 because of the leading '!' */
}
else len2 = 1; /* minimum is the 0 characters for the empty second string */
/* convert characters to bytes */
len0 *= sizeof(WCHAR);
len1 *= sizeof(WCHAR);
len2 *= sizeof(WCHAR);
RtlInitUnicodeString( &nameW, ValueName );
/* First we check if the key exists and if so how many bytes it already contains. */
if (NtQueryValueKey( Reboot, &nameW, KeyValuePartialInformation,
NULL, 0, &DataSize ) == STATUS_BUFFER_OVERFLOW)
{
if (!(Buffer = HeapAlloc( GetProcessHeap(), 0, DataSize + len1 + len2 + sizeof(WCHAR) )))
goto Quit;
if (NtQueryValueKey( Reboot, &nameW, KeyValuePartialInformation,
Buffer, DataSize, &DataSize )) goto Quit;
info = (KEY_VALUE_PARTIAL_INFORMATION *)Buffer;
if (info->Type != REG_MULTI_SZ) goto Quit;
if (DataSize > sizeof(info)) DataSize -= sizeof(WCHAR); /* remove terminating null (will be added back later) */
}
else
{
DataSize = info_size;
if (!(Buffer = HeapAlloc( GetProcessHeap(), 0, DataSize + len1 + len2 + sizeof(WCHAR) )))
goto Quit;
}
p = (WCHAR *)(Buffer + DataSize);
strcpyW( p, PreString );
strcatW( p, fn1 );
DataSize += len1;
if (fn2)
{
p = (WCHAR *)(Buffer + DataSize);
if (flags & MOVEFILE_REPLACE_EXISTING)
*p++ = '!';
strcpyW( p, PreString );
strcatW( p, fn2 );
DataSize += len2;
}
else
{
p = (WCHAR *)(Buffer + DataSize);
*p = 0;
DataSize += sizeof(WCHAR);
}
/* add final null */
p = (WCHAR *)(Buffer + DataSize);
*p = 0;
DataSize += sizeof(WCHAR);
rc = !NtSetValueKey(Reboot, &nameW, 0, REG_MULTI_SZ, Buffer + info_size, DataSize - info_size);
Quit:
if (Reboot) NtClose(Reboot);
if (Buffer) HeapFree( GetProcessHeap(), 0, Buffer );
return(rc);
}
/***********************************************************************
* GetFullPathNameW (KERNEL32.@)
* NOTES
@ -659,3 +813,189 @@ DWORD WINAPI SearchPathA( LPCSTR path, LPCSTR name, LPCSTR ext,
RtlFreeUnicodeString(&extW);
return ret;
}
/**************************************************************************
* MoveFileExW (KERNEL32.@)
*/
BOOL WINAPI MoveFileExW( LPCWSTR source, LPCWSTR dest, DWORD flag )
{
FILE_BASIC_INFORMATION info;
UNICODE_STRING nt_name;
OBJECT_ATTRIBUTES attr;
IO_STATUS_BLOCK io;
NTSTATUS status;
HANDLE source_handle = 0, dest_handle;
char *source_unix = NULL, *dest_unix = NULL;
TRACE("(%s,%s,%04lx)\n", debugstr_w(source), debugstr_w(dest), flag);
if (flag & MOVEFILE_DELAY_UNTIL_REBOOT)
return add_boot_rename_entry( source, dest, flag );
if (!dest)
return DeleteFileW( source );
/* check if we are allowed to rename the source */
if (!RtlDosPathNameToNtPathName_U( source, &nt_name, NULL, NULL ))
{
SetLastError( ERROR_PATH_NOT_FOUND );
return FALSE;
}
attr.Length = sizeof(attr);
attr.RootDirectory = 0;
attr.Attributes = OBJ_CASE_INSENSITIVE;
attr.ObjectName = &nt_name;
attr.SecurityDescriptor = NULL;
attr.SecurityQualityOfService = NULL;
status = NtOpenFile( &source_handle, 0, &attr, &io, 0, FILE_SYNCHRONOUS_IO_NONALERT );
RtlFreeUnicodeString( &nt_name );
if (status != STATUS_SUCCESS)
{
SetLastError( RtlNtStatusToDosError(status) );
goto error;
}
status = NtQueryInformationFile( source_handle, &io, &info, sizeof(info), FileBasicInformation );
if (status != STATUS_SUCCESS)
{
SetLastError( RtlNtStatusToDosError(status) );
goto error;
}
if (!(source_unix = wine_get_unix_file_name( source ))) /* should not happen */
{
SetLastError( ERROR_FILE_NOT_FOUND );
goto error;
}
if (info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (flag & MOVEFILE_REPLACE_EXISTING) /* cannot replace directory */
{
SetLastError( ERROR_INVALID_PARAMETER );
goto error;
}
}
/* we must have write access to the destination, and it must */
/* not exist except if MOVEFILE_REPLACE_EXISTING is set */
if (!RtlDosPathNameToNtPathName_U( dest, &nt_name, NULL, NULL ))
{
SetLastError( ERROR_PATH_NOT_FOUND );
goto error;
}
status = NtOpenFile( &dest_handle, GENERIC_READ | GENERIC_WRITE, &attr, &io, 0,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT );
RtlFreeUnicodeString( &nt_name );
if (status == STATUS_SUCCESS)
{
NtClose( dest_handle );
if (!(flag & MOVEFILE_REPLACE_EXISTING))
{
SetLastError( ERROR_ALREADY_EXISTS );
goto error;
}
}
else if (status != STATUS_OBJECT_NAME_NOT_FOUND)
{
SetLastError( RtlNtStatusToDosError(status) );
goto error;
}
if (!(dest_unix = wine_get_unix_file_name( dest ))) /* should not happen */
{
SetLastError( ERROR_FILE_NOT_FOUND );
goto error;
}
/* now perform the rename */
if (rename( source_unix, dest_unix ) == -1)
{
if (errno == EXDEV && (flag & MOVEFILE_COPY_ALLOWED))
{
NtClose( source_handle );
HeapFree( GetProcessHeap(), 0, source_unix );
HeapFree( GetProcessHeap(), 0, dest_unix );
return (CopyFileW( source, dest, TRUE ) && DeleteFileW( source ));
}
FILE_SetDosError();
/* if we created the destination, remove it */
if (io.Information == FILE_CREATED) unlink( dest_unix );
goto error;
}
/* fixup executable permissions */
if (is_executable( source ) != is_executable( dest ))
{
struct stat fstat;
if (stat( dest_unix, &fstat ) != -1)
{
if (is_executable( dest ))
/* set executable bit where read bit is set */
fstat.st_mode |= (fstat.st_mode & 0444) >> 2;
else
fstat.st_mode &= ~0111;
chmod( dest_unix, fstat.st_mode );
}
}
NtClose( source_handle );
HeapFree( GetProcessHeap(), 0, source_unix );
HeapFree( GetProcessHeap(), 0, dest_unix );
return TRUE;
error:
if (source_handle) NtClose( source_handle );
if (source_unix) HeapFree( GetProcessHeap(), 0, source_unix );
if (dest_unix) HeapFree( GetProcessHeap(), 0, dest_unix );
return FALSE;
}
/**************************************************************************
* MoveFileExA (KERNEL32.@)
*/
BOOL WINAPI MoveFileExA( LPCSTR source, LPCSTR dest, DWORD flag )
{
UNICODE_STRING sourceW, destW;
BOOL ret;
if (!source)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
RtlCreateUnicodeStringFromAsciiz(&sourceW, source);
if (dest) RtlCreateUnicodeStringFromAsciiz(&destW, dest);
else destW.Buffer = NULL;
ret = MoveFileExW( sourceW.Buffer, destW.Buffer, flag );
RtlFreeUnicodeString(&sourceW);
RtlFreeUnicodeString(&destW);
return ret;
}
/**************************************************************************
* MoveFileW (KERNEL32.@)
*
* Move file or directory
*/
BOOL WINAPI MoveFileW( LPCWSTR source, LPCWSTR dest )
{
return MoveFileExW( source, dest, MOVEFILE_COPY_ALLOWED );
}
/**************************************************************************
* MoveFileA (KERNEL32.@)
*/
BOOL WINAPI MoveFileA( LPCSTR source, LPCSTR dest )
{
return MoveFileExA( source, dest, MOVEFILE_COPY_ALLOWED );
}

View File

@ -149,7 +149,7 @@ void FILE_SetDosError(void)
break;
case ENFILE:
case EMFILE:
SetLastError( ERROR_NO_MORE_FILES );
SetLastError( ERROR_TOO_MANY_OPEN_FILES );
break;
case EEXIST:
SetLastError( ERROR_FILE_EXISTS );
@ -164,6 +164,12 @@ void FILE_SetDosError(void)
case ENOEXEC:
SetLastError( ERROR_BAD_FORMAT );
break;
case ENOTDIR:
SetLastError( ERROR_PATH_NOT_FOUND );
break;
case EXDEV:
SetLastError( ERROR_NOT_SAME_DEVICE );
break;
default:
WARN("unknown file error: %s\n", strerror(save_errno) );
SetLastError( ERROR_GEN_FAILURE );
@ -1033,360 +1039,6 @@ DWORD WINAPI GetFileType( HANDLE hFile )
}
/* check if a file name is for an executable file (.exe or .com) */
inline static BOOL is_executable( const char *name )
{
int len = strlen(name);
if (len < 4) return FALSE;
return (!strcasecmp( name + len - 4, ".exe" ) ||
!strcasecmp( name + len - 4, ".com" ));
}
/***********************************************************************
* FILE_AddBootRenameEntry
*
* Adds an entry to the registry that is loaded when windows boots and
* checks if there are some files to be removed or renamed/moved.
* <fn1> has to be valid and <fn2> may be NULL. If both pointers are
* non-NULL then the file is moved, otherwise it is deleted. The
* entry of the registrykey is always appended with two zero
* terminated strings. If <fn2> is NULL then the second entry is
* simply a single 0-byte. Otherwise the second filename goes
* there. The entries are prepended with \??\ before the path and the
* second filename gets also a '!' as the first character if
* MOVEFILE_REPLACE_EXISTING is set. After the final string another
* 0-byte follows to indicate the end of the strings.
* i.e.:
* \??\D:\test\file1[0]
* !\??\D:\test\file1_renamed[0]
* \??\D:\Test|delete[0]
* [0] <- file is to be deleted, second string empty
* \??\D:\test\file2[0]
* !\??\D:\test\file2_renamed[0]
* [0] <- indicates end of strings
*
* or:
* \??\D:\test\file1[0]
* !\??\D:\test\file1_renamed[0]
* \??\D:\Test|delete[0]
* [0] <- file is to be deleted, second string empty
* [0] <- indicates end of strings
*
*/
static BOOL FILE_AddBootRenameEntry( LPCWSTR fn1, LPCWSTR fn2, DWORD flags )
{
static const WCHAR PreString[] = {'\\','?','?','\\',0};
static const WCHAR ValueName[] = {'P','e','n','d','i','n','g',
'F','i','l','e','R','e','n','a','m','e',
'O','p','e','r','a','t','i','o','n','s',0};
static const WCHAR SessionW[] = {'M','a','c','h','i','n','e','\\',
'S','y','s','t','e','m','\\',
'C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\',
'C','o','n','t','r','o','l','\\',
'S','e','s','s','i','o','n',' ','M','a','n','a','g','e','r',0};
static const int info_size = FIELD_OFFSET( KEY_VALUE_PARTIAL_INFORMATION, Data );
OBJECT_ATTRIBUTES attr;
UNICODE_STRING nameW;
KEY_VALUE_PARTIAL_INFORMATION *info;
BOOL rc = FALSE;
HKEY Reboot = 0;
DWORD len0, len1, len2;
DWORD DataSize = 0;
BYTE *Buffer = NULL;
WCHAR *p;
attr.Length = sizeof(attr);
attr.RootDirectory = 0;
attr.ObjectName = &nameW;
attr.Attributes = 0;
attr.SecurityDescriptor = NULL;
attr.SecurityQualityOfService = NULL;
RtlInitUnicodeString( &nameW, SessionW );
if (NtCreateKey( &Reboot, KEY_ALL_ACCESS, &attr, 0, NULL, 0, NULL ) != STATUS_SUCCESS)
{
WARN("Error creating key for reboot managment [%s]\n",
"SYSTEM\\CurrentControlSet\\Control\\Session Manager");
return FALSE;
}
len0 = strlenW(PreString);
len1 = strlenW(fn1) + len0 + 1;
if (fn2)
{
len2 = strlenW(fn2) + len0 + 1;
if (flags & MOVEFILE_REPLACE_EXISTING) len2++; /* Plus 1 because of the leading '!' */
}
else len2 = 1; /* minimum is the 0 characters for the empty second string */
/* convert characters to bytes */
len0 *= sizeof(WCHAR);
len1 *= sizeof(WCHAR);
len2 *= sizeof(WCHAR);
RtlInitUnicodeString( &nameW, ValueName );
/* First we check if the key exists and if so how many bytes it already contains. */
if (NtQueryValueKey( Reboot, &nameW, KeyValuePartialInformation,
NULL, 0, &DataSize ) == STATUS_BUFFER_OVERFLOW)
{
if (!(Buffer = HeapAlloc( GetProcessHeap(), 0, DataSize + len1 + len2 + sizeof(WCHAR) )))
goto Quit;
if (NtQueryValueKey( Reboot, &nameW, KeyValuePartialInformation,
Buffer, DataSize, &DataSize )) goto Quit;
info = (KEY_VALUE_PARTIAL_INFORMATION *)Buffer;
if (info->Type != REG_MULTI_SZ) goto Quit;
if (DataSize > sizeof(info)) DataSize -= sizeof(WCHAR); /* remove terminating null (will be added back later) */
}
else
{
DataSize = info_size;
if (!(Buffer = HeapAlloc( GetProcessHeap(), 0, DataSize + len1 + len2 + sizeof(WCHAR) )))
goto Quit;
}
p = (WCHAR *)(Buffer + DataSize);
strcpyW( p, PreString );
strcatW( p, fn1 );
DataSize += len1;
if (fn2)
{
p = (WCHAR *)(Buffer + DataSize);
if (flags & MOVEFILE_REPLACE_EXISTING)
*p++ = '!';
strcpyW( p, PreString );
strcatW( p, fn2 );
DataSize += len2;
}
else
{
p = (WCHAR *)(Buffer + DataSize);
*p = 0;
DataSize += sizeof(WCHAR);
}
/* add final null */
p = (WCHAR *)(Buffer + DataSize);
*p = 0;
DataSize += sizeof(WCHAR);
rc = !NtSetValueKey(Reboot, &nameW, 0, REG_MULTI_SZ, Buffer + info_size, DataSize - info_size);
Quit:
if (Reboot) NtClose(Reboot);
if (Buffer) HeapFree( GetProcessHeap(), 0, Buffer );
return(rc);
}
/**************************************************************************
* MoveFileExW (KERNEL32.@)
*/
BOOL WINAPI MoveFileExW( LPCWSTR fn1, LPCWSTR fn2, DWORD flag )
{
DOS_FULL_NAME full_name1, full_name2;
HANDLE hFile;
DWORD attr = INVALID_FILE_ATTRIBUTES;
TRACE("(%s,%s,%04lx)\n", debugstr_w(fn1), debugstr_w(fn2), flag);
/* FIXME: <Gerhard W. Gruber>sparhawk@gmx.at
In case of W9x and lesser this function should return 120 (ERROR_CALL_NOT_IMPLEMENTED)
to be really compatible. Most programs won't have any problems though. In case
you encounter one, this is what you should return here. I don't know what's up
with NT 3.5. Is this function available there or not?
Does anybody really care about 3.5? :)
*/
/* Filename1 has to be always set to a valid path. Filename2 may be NULL
if the source file has to be deleted.
*/
if (!fn1) {
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
/* This function has to be run through in order to process the name properly.
If the BOOTDELAY flag is set, the file doesn't need to exist though. At least
that is the behaviour on NT 4.0. The operation accepts the filenames as
they are given but it can't reply with a reasonable returncode. Success
means in that case success for entering the values into the registry.
*/
if(!DOSFS_GetFullName( fn1, TRUE, &full_name1 ))
{
if(!(flag & MOVEFILE_DELAY_UNTIL_REBOOT))
return FALSE;
}
if (fn2) /* !fn2 means delete fn1 */
{
if (DOSFS_GetFullName( fn2, TRUE, &full_name2 ))
{
if(!(flag & MOVEFILE_DELAY_UNTIL_REBOOT))
{
/* target exists, check if we may overwrite */
if (!(flag & MOVEFILE_REPLACE_EXISTING))
{
SetLastError( ERROR_ALREADY_EXISTS );
return FALSE;
}
}
}
else
{
if (!DOSFS_GetFullName( fn2, FALSE, &full_name2 ))
{
if(!(flag & MOVEFILE_DELAY_UNTIL_REBOOT))
return FALSE;
}
}
/* Source name and target path are valid */
if (flag & MOVEFILE_DELAY_UNTIL_REBOOT)
{
return FILE_AddBootRenameEntry( fn1, fn2, flag );
}
attr = GetFileAttributesW( fn1 );
if ( attr == INVALID_FILE_ATTRIBUTES ) return FALSE;
/* check if we are allowed to rename the source */
hFile = FILE_CreateFile( full_name1.long_name, 0, 0,
NULL, OPEN_EXISTING, 0, 0 );
if (!hFile)
{
if (GetLastError() != ERROR_ACCESS_DENIED) return FALSE;
if ( !(attr & FILE_ATTRIBUTE_DIRECTORY) ) return FALSE;
/* if it's a directory we can continue */
}
else CloseHandle(hFile);
/* check, if we are allowed to delete the destination,
** (but the file not being there is fine) */
hFile = FILE_CreateFile( full_name2.long_name, GENERIC_READ|GENERIC_WRITE, 0,
NULL, OPEN_EXISTING, 0, 0 );
if(!hFile && GetLastError() != ERROR_FILE_NOT_FOUND) return FALSE;
CloseHandle(hFile);
if (full_name1.drive != full_name2.drive)
{
if (!(flag & MOVEFILE_COPY_ALLOWED))
{
SetLastError( ERROR_NOT_SAME_DEVICE );
return FALSE;
}
else if ( attr & FILE_ATTRIBUTE_DIRECTORY )
{
/* Strange, but that's what Windows returns */
SetLastError ( ERROR_ACCESS_DENIED );
return FALSE;
}
}
if (rename( full_name1.long_name, full_name2.long_name ) == -1)
/* Try copy/delete unless it's a directory. */
/* FIXME: This does not handle the (unlikely) case that the two locations
are on the same Wine drive, but on different Unix file systems. */
{
if ( attr & FILE_ATTRIBUTE_DIRECTORY )
{
FILE_SetDosError();
return FALSE;
}
else
{
if ( ! CopyFileW( fn1, fn2, !(flag & MOVEFILE_REPLACE_EXISTING) ))
return FALSE;
if ( ! DeleteFileW ( fn1 ) )
return FALSE;
}
}
if (is_executable( full_name1.long_name ) != is_executable( full_name2.long_name ))
{
struct stat fstat;
if (stat( full_name2.long_name, &fstat ) != -1)
{
if (is_executable( full_name2.long_name ))
/* set executable bit where read bit is set */
fstat.st_mode |= (fstat.st_mode & 0444) >> 2;
else
fstat.st_mode &= ~0111;
chmod( full_name2.long_name, fstat.st_mode );
}
}
return TRUE;
}
else /* fn2 == NULL means delete source */
{
if (flag & MOVEFILE_DELAY_UNTIL_REBOOT)
{
if (flag & MOVEFILE_COPY_ALLOWED) {
WARN("Illegal flag\n");
SetLastError( ERROR_GEN_FAILURE );
return FALSE;
}
return FILE_AddBootRenameEntry( fn1, NULL, flag );
}
if (unlink( full_name1.long_name ) == -1)
{
FILE_SetDosError();
return FALSE;
}
return TRUE; /* successfully deleted */
}
}
/**************************************************************************
* MoveFileExA (KERNEL32.@)
*/
BOOL WINAPI MoveFileExA( LPCSTR fn1, LPCSTR fn2, DWORD flag )
{
UNICODE_STRING fn1W, fn2W;
BOOL ret;
if (!fn1)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
RtlCreateUnicodeStringFromAsciiz(&fn1W, fn1);
if (fn2) RtlCreateUnicodeStringFromAsciiz(&fn2W, fn2);
else fn2W.Buffer = NULL;
ret = MoveFileExW( fn1W.Buffer, fn2W.Buffer, flag );
RtlFreeUnicodeString(&fn1W);
RtlFreeUnicodeString(&fn2W);
return ret;
}
/**************************************************************************
* MoveFileW (KERNEL32.@)
*
* Move file or directory
*/
BOOL WINAPI MoveFileW( LPCWSTR fn1, LPCWSTR fn2 )
{
return MoveFileExW( fn1, fn2, MOVEFILE_COPY_ALLOWED );
}
/**************************************************************************
* MoveFileA (KERNEL32.@)
*/
BOOL WINAPI MoveFileA( LPCSTR fn1, LPCSTR fn2 )
{
return MoveFileExA( fn1, fn2, MOVEFILE_COPY_ALLOWED );
}
/**************************************************************************
* CopyFileW (KERNEL32.@)
*/