From e172faae0eb9f31a0e1acbf55b7a392b460f0bdb Mon Sep 17 00:00:00 2001 From: Erich Hoover Date: Sun, 24 Feb 2002 11:22:29 -0700 Subject: Implement ReplaceFileA/ReplaceFileW --- dlls/kernel32/file.c | 201 ++++++++++++++++++++++++++++++-- dlls/kernel32/kernel_private.h | 3 + dlls/kernel32/path.c | 26 +---- dlls/kernel32/tests/file.c | 252 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 449 insertions(+), 33 deletions(-) diff --git a/dlls/kernel32/file.c b/dlls/kernel32/file.c index 90280ad..00569a4 100644 --- a/dlls/kernel32/file.c +++ b/dlls/kernel32/file.c @@ -163,6 +163,46 @@ static BOOL check_dir_symlink( FIND_FIRST_INFO *info, const FILE_BOTH_DIR_INFORM } +/****************************************************************** + * FILE_copy_contents (internal) + * + * Copy the contents of a file handle into another file handle. + */ +BOOL FILE_copy_contents(HANDLE source, HANDLE destination) +{ + static const int buffer_size = 65536; + BOOL ret = TRUE; + char *buffer; + DWORD count; + + /* Create a copying buffer */ + if (!(buffer = HeapAlloc( GetProcessHeap(), 0, buffer_size ))) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + } + /* Perform actual copy operation */ + while (ReadFile( source, buffer, buffer_size, &count, NULL ) && count) + { + char *p = buffer; + while (count != 0) + { + DWORD res; + if (!WriteFile( destination, p, count, &res, NULL ) || !res) + { + ret = FALSE; + break; + } + p += res; + count -= res; + } + } + /* Free the copying buffer */ + HeapFree( GetProcessHeap(), 0, buffer ); + return ret; +} + + /*********************************************************************** * FILE_SetDosError * @@ -1514,10 +1554,125 @@ BOOL WINAPI ReplaceFileW(LPCWSTR lpReplacedFileName,LPCWSTR lpReplacementFileNam LPCWSTR lpBackupFileName, DWORD dwReplaceFlags, LPVOID lpExclude, LPVOID lpReserved) { - FIXME("(%s,%s,%s,%08x,%p,%p) stub\n",debugstr_w(lpReplacedFileName),debugstr_w(lpReplacementFileName), - debugstr_w(lpBackupFileName),dwReplaceFlags,lpExclude,lpReserved); - SetLastError(ERROR_UNABLE_TO_MOVE_REPLACEMENT); - return FALSE; + BY_HANDLE_FILE_INFORMATION ifoReplaced, ifoReplacement; + HANDLE hReplaced, hReplacement, hBackup; + BOOL skipBackup = FALSE, ret = FALSE; + + if (dwReplaceFlags) + FIXME("Ignoring flags %x\n", dwReplaceFlags); + /* First two arguments are mandatory */ + if (!lpReplacedFileName || !lpReplacementFileName) + { + SetLastError( ERROR_INVALID_PARAMETER ); + return FALSE; + } + /* + * Open the replacement file for reading, writing, and deleting + * (writing and deleting are needed when finished) + */ + if ((hReplacement = CreateFileW(lpReplacementFileName, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, 0, 0)) == INVALID_HANDLE_VALUE) + { + return FALSE; + } + /* Obtain the file attributes from the replacement file */ + if (!GetFileInformationByHandle( hReplacement, &ifoReplacement )) + { + WARN("GetFileInformationByHandle returned error for %s\n", debugstr_w(lpReplacementFileName)); + goto replace_fail_1; + } + /* Open the "replaced" file for reading and writing */ + if ((hReplaced = CreateFileW(lpReplacedFileName, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + ifoReplacement.dwFileAttributes, hReplacement)) == INVALID_HANDLE_VALUE) + { + if ( GetLastError() == ERROR_FILE_NOT_FOUND ) + { + /* If "replaced" does not exist then create it for the write, but skip backup */ + if ((hReplaced = CreateFileW(lpReplacedFileName, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, ifoReplacement.dwFileAttributes, + hReplacement)) == INVALID_HANDLE_VALUE) + { + goto replace_fail_1; + } + skipBackup = TRUE; + } + else + { + /* Inappropriate permissions to remove "replaced" */ + SetLastError( ERROR_UNABLE_TO_REMOVE_REPLACED ); + goto replace_fail_1; + } + } + /* If the user wants a backup then that needs to be performed first */ + if ( lpBackupFileName && !skipBackup ) + { + /* Obtain the file attributes from the "replaced" file */ + if (!GetFileInformationByHandle( hReplaced, &ifoReplaced )) + { + WARN("GetFileInformationByHandle returned error for %s\n", debugstr_w(lpReplacedFileName)); + goto replace_fail_2; + } + /* If an existing backup exists then copy over it */ + if ((hBackup = CreateFileW(lpBackupFileName, GENERIC_WRITE, + FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, ifoReplaced.dwFileAttributes, + hReplaced)) == INVALID_HANDLE_VALUE) + { + goto replace_fail_2; + } + /* Actually copy the "replaced" file into the backup file */ + if (!FILE_copy_contents( hReplaced, hBackup )) + { + /* on failure we need to cleanup all our resources */ + CloseHandle( hBackup ); + goto replace_fail_2; + } + /* Set the filetime of the backup to that of the "replaced" file */ + SetFileTime( hBackup, NULL, NULL, &ifoReplaced.ftLastWriteTime ); + CloseHandle( hBackup ); + /* Seek back to the beginning of the file */ + SetFilePointer( hReplaced, 0, NULL, FILE_BEGIN ); + } + /* + * Now that the backup has been performed (if requested), copy the replacement + * into place + */ + if (!FILE_copy_contents( hReplacement, hReplaced )) + { + /* on failure we need to cleanup all our resources */ + SetLastError(ERROR_UNABLE_TO_MOVE_REPLACEMENT); + goto replace_fail_2; + } + /* + * If the file was bigger before, then end after the last new write + * (we did not clear the contents of this file on handle creation) + */ + SetEndOfFile( hReplaced ); + /* Set the filetime of the "replaced" file to that of the replacement */ + SetFileTime( hReplaced, NULL, NULL, &ifoReplacement.ftLastWriteTime ); + /* + * Delete the replacement file, note that this delete won't really occur + * until the original handle is released. + */ + if (!DeleteFileW( lpReplacementFileName )) + { + /* + * This case should never occur, we've already checked permissions earlier + * and we are holding the file handle open. If this case does occur then + * DeleteFileW calls SetLastError() appropriately. + */ + WARN("Replacement file may not be deleted!\n"); + } + ret = TRUE; + + /* Clean up all allocated resources */ +replace_fail_2: + CloseHandle( hReplaced ); +replace_fail_1: + CloseHandle( hReplacement ); + return ret; } @@ -1528,10 +1683,40 @@ BOOL WINAPI ReplaceFileA(LPCSTR lpReplacedFileName,LPCSTR lpReplacementFileName, LPCSTR lpBackupFileName, DWORD dwReplaceFlags, LPVOID lpExclude, LPVOID lpReserved) { - FIXME("(%s,%s,%s,%08x,%p,%p) stub\n",lpReplacedFileName,lpReplacementFileName, - lpBackupFileName,dwReplaceFlags,lpExclude,lpReserved); - SetLastError(ERROR_UNABLE_TO_MOVE_REPLACEMENT); - return FALSE; + WCHAR *replacedW, *replacementW, *backupW = NULL; + BOOL ret; + + /* This function only makes sense when the first two parameters are defined */ + if (!lpReplacedFileName || !(replacedW = FILE_name_AtoW( lpReplacedFileName, TRUE ))) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + if (!lpReplacementFileName || !(replacementW = FILE_name_AtoW( lpReplacementFileName, TRUE ))) + { + HeapFree( GetProcessHeap(), 0, replacedW ); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + /* The backup parameter, however, is optional */ + if(lpBackupFileName) + { + if (!(backupW = FILE_name_AtoW( lpBackupFileName, TRUE ))) + { + HeapFree( GetProcessHeap(), 0, replacedW ); + HeapFree( GetProcessHeap(), 0, replacementW ); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + } + /* Make the actual replacement call */ + ret = ReplaceFileW( replacedW, replacementW, backupW, dwReplaceFlags, lpExclude, lpReserved ); + /* Free all the resources allocated by FILE_name_AtoW */ + HeapFree( GetProcessHeap(), 0, replacedW ); + HeapFree( GetProcessHeap(), 0, replacementW ); + if(lpBackupFileName) + HeapFree( GetProcessHeap(), 0, backupW ); + return ret; } diff --git a/dlls/kernel32/kernel_private.h b/dlls/kernel32/kernel_private.h index dd7c916..e0bd48b 100644 --- a/dlls/kernel32/kernel_private.h +++ b/dlls/kernel32/kernel_private.h @@ -142,6 +142,9 @@ extern struct winedos_exports void (* BiosTick)(WORD timer); } winedos; +/* file.c */ +extern BOOL FILE_copy_contents(HANDLE source, HANDLE destination); + /* returns directory handle for named objects */ extern HANDLE get_BaseNamedObjects_handle(void); diff --git a/dlls/kernel32/path.c b/dlls/kernel32/path.c index 7681880..eeba0bd 100644 --- a/dlls/kernel32/path.c +++ b/dlls/kernel32/path.c @@ -857,23 +857,15 @@ DWORD WINAPI SearchPathA( LPCSTR path, LPCSTR name, LPCSTR ext, */ BOOL WINAPI CopyFileW( LPCWSTR source, LPCWSTR dest, BOOL fail_if_exists ) { - static const int buffer_size = 65536; HANDLE h1, h2; BY_HANDLE_FILE_INFORMATION info; - DWORD count; BOOL ret = FALSE; - char *buffer; if (!source || !dest) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } - if (!(buffer = HeapAlloc( GetProcessHeap(), 0, buffer_size ))) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return FALSE; - } TRACE("%s -> %s\n", debugstr_w(source), debugstr_w(dest)); @@ -881,14 +873,12 @@ BOOL WINAPI CopyFileW( LPCWSTR source, LPCWSTR dest, BOOL fail_if_exists ) NULL, OPEN_EXISTING, 0, 0)) == INVALID_HANDLE_VALUE) { WARN("Unable to open source %s\n", debugstr_w(source)); - HeapFree( GetProcessHeap(), 0, buffer ); return FALSE; } if (!GetFileInformationByHandle( h1, &info )) { WARN("GetFileInformationByHandle returned error for %s\n", debugstr_w(source)); - HeapFree( GetProcessHeap(), 0, buffer ); CloseHandle( h1 ); return FALSE; } @@ -898,27 +888,13 @@ BOOL WINAPI CopyFileW( LPCWSTR source, LPCWSTR dest, BOOL fail_if_exists ) info.dwFileAttributes, h1 )) == INVALID_HANDLE_VALUE) { WARN("Unable to open dest %s\n", debugstr_w(dest)); - HeapFree( GetProcessHeap(), 0, buffer ); CloseHandle( h1 ); return FALSE; } - while (ReadFile( h1, buffer, buffer_size, &count, NULL ) && count) - { - char *p = buffer; - while (count != 0) - { - DWORD res; - if (!WriteFile( h2, p, count, &res, NULL ) || !res) goto done; - p += res; - count -= res; - } - } - ret = TRUE; -done: + ret = FILE_copy_contents( h1, h2 ); /* Maintain the timestamp of source file to destination file */ SetFileTime(h2, NULL, NULL, &info.ftLastWriteTime); - HeapFree( GetProcessHeap(), 0, buffer ); CloseHandle( h1 ); CloseHandle( h2 ); return ret; diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index e9f97f5..a3d93da 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -1818,6 +1818,256 @@ static void test_RemoveDirectory(void) } } +static void test_ReplaceFileA(void) +{ + char replaced[MAX_PATH], replacement[MAX_PATH], backup[MAX_PATH]; + HANDLE hReplacedFile, hReplacementFile, hBackupFile; + static const char replacedData[] = "file-to-replace"; + static const char replacementData[] = "new-file"; + static const char backupData[] = "backup-file"; + FILETIME ftReplaced, ftReplacement, ftBackup; + static const char prefix[] = "pfx"; + char temp_path[MAX_PATH]; + DWORD ret; + BOOL retok; + + ret = GetTempPathA(MAX_PATH, temp_path); + ok(ret != 0, "GetTempPathA error %d\n", GetLastError()); + ok(ret < MAX_PATH, "temp path should fit into MAX_PATH\n"); + + ret = GetTempFileNameA(temp_path, prefix, 0, replaced); + ok(ret != 0, "GetTempFileNameA error (replaced) %d\n", GetLastError()); + + ret = GetTempFileNameA(temp_path, prefix, 0, replacement); + ok(ret != 0, "GetTempFileNameA error (replacement) %d\n", GetLastError()); + + ret = GetTempFileNameA(temp_path, prefix, 0, backup); + ok(ret != 0, "GetTempFileNameA error (backup) %d\n", GetLastError()); + + /* place predictable data in the file to be replaced */ + hReplacedFile = CreateFileA(replaced, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0 ); + ok(hReplacedFile != INVALID_HANDLE_VALUE, + "failed to open replaced file\n"); + retok = WriteFile(hReplacedFile, replacedData, sizeof(replacedData), &ret, NULL ); + ok( retok && ret == sizeof(replacedData), + "WriteFile error (replaced) %d\n", GetLastError()); + ok(GetFileSize(hReplacedFile, NULL) == sizeof(replacedData), + "replaced file has wrong size\n"); + /* place predictable data in the file to be the replacement */ + hReplacementFile = CreateFileA(replacement, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0 ); + ok(hReplacementFile != INVALID_HANDLE_VALUE, + "failed to open replacement file\n"); + retok = WriteFile(hReplacementFile, replacementData, sizeof(replacementData), &ret, NULL ); + ok( retok && ret == sizeof(replacementData), + "WriteFile error (replacement) %d\n", GetLastError()); + ok(GetFileSize(hReplacementFile, NULL) == sizeof(replacementData), + "replacement file has wrong size\n"); + /* place predictable data in the backup file (to be over-written) */ + hBackupFile = CreateFileA(backup, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0 ); + ok(hBackupFile != INVALID_HANDLE_VALUE, + "failed to open backup file\n"); + retok = WriteFile(hBackupFile, backupData, sizeof(backupData), &ret, NULL ); + ok( retok && ret == sizeof(backupData), + "WriteFile error (replacement) %d\n", GetLastError()); + ok(GetFileSize(hBackupFile, NULL) == sizeof(backupData), + "backup file has wrong size\n"); + /* change the filetime on the "replaced" file to ensure that it changes */ + ret = GetFileTime(hReplacedFile, NULL, NULL, &ftReplaced); + ok( ret, "GetFileTime error (replaced) %d\n", GetLastError()); + ftReplaced.dwLowDateTime -= 600000000; /* 60 second */ + ret = SetFileTime(hReplacedFile, NULL, NULL, &ftReplaced); + ok( ret, "SetFileTime error (replaced) %d\n", GetLastError()); + GetFileTime(hReplacedFile, NULL, NULL, &ftReplaced); /* get the actual time back */ + CloseHandle(hReplacedFile); + /* change the filetime on the backup to ensure that it changes */ + ret = GetFileTime(hBackupFile, NULL, NULL, &ftBackup); + ok( ret, "GetFileTime error (backup) %d\n", GetLastError()); + ftBackup.dwLowDateTime -= 1200000000; /* 120 second */ + ret = SetFileTime(hBackupFile, NULL, NULL, &ftBackup); + ok( ret, "SetFileTime error (backup) %d\n", GetLastError()); + GetFileTime(hBackupFile, NULL, NULL, &ftBackup); /* get the actual time back */ + CloseHandle(hBackupFile); + /* get the filetime on the replacement file to perform checks */ + ret = GetFileTime(hReplacementFile, NULL, NULL, &ftReplacement); + ok( ret, "GetFileTime error (replacement) %d\n", GetLastError()); + CloseHandle(hReplacementFile); + + /* perform replacement w/ backup + * TODO: flags are not implemented + */ + SetLastError(0xdeadbeef); + ret = ReplaceFileA(replaced, replacement, backup, 0, 0, 0); + ok(ret, "ReplaceFileA: unexpected error %d\n", GetLastError()); + /* make sure that the backup has the size of the old "replaced" file */ + hBackupFile = CreateFileA(backup, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); + ok(hBackupFile != INVALID_HANDLE_VALUE, + "failed to open backup file\n"); + ret = GetFileSize(hBackupFile, NULL); + ok(ret == sizeof(replacedData), + "backup file has wrong size %d\n", ret); + /* make sure that the "replaced" file has the size of the replacement file */ + hReplacedFile = CreateFileA(replaced, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); + ok(hReplacedFile != INVALID_HANDLE_VALUE, + "failed to open replaced file\n"); + ret = GetFileSize(hReplacedFile, NULL); + ok(ret == sizeof(replacementData), + "replaced file has wrong size %d\n", ret); + /* make sure that the replacement file no-longer exists */ + hReplacementFile = CreateFileA(replacement, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); + ok(hReplacementFile == INVALID_HANDLE_VALUE, + "unexpected error, replacement file should not exist %d\n", GetLastError()); + /* make sure that the backup has the old "replaced" filetime */ + ret = GetFileTime(hBackupFile, NULL, NULL, &ftBackup); + ok( ret, "GetFileTime error (backup %d\n", GetLastError()); + ok(CompareFileTime(&ftBackup, &ftReplaced) == 0, + "backup file has wrong filetime\n"); + CloseHandle(hBackupFile); + /* make sure that the "replaced" has the old replacement filetime */ + ret = GetFileTime(hReplacedFile, NULL, NULL, &ftReplaced); + ok( ret, "GetFileTime error (backup %d\n", GetLastError()); + ok(CompareFileTime(&ftReplaced, &ftReplacement) == 0, + "replaced file has wrong filetime\n"); + CloseHandle(hReplacedFile); + + /* re-create replacement file for pass w/o backup (blank) */ + ret = GetTempFileNameA(temp_path, prefix, 0, replacement); + ok(ret != 0, "GetTempFileNameA error (replacement) %d\n", GetLastError()); + /* perform replacement w/o backup + * TODO: flags are not implemented + */ + SetLastError(0xdeadbeef); + ret = ReplaceFileA(replaced, replacement, NULL, 0, 0, 0); + ok(ret, "ReplaceFileA: unexpected error %d\n", GetLastError()); + + /* re-create replacement file for pass w/ backup (backup-file not existing) */ + ret = GetTempFileNameA(temp_path, prefix, 0, replacement); + ok(ret != 0, "GetTempFileNameA error (replacement) %d\n", GetLastError()); + ret = DeleteFileA(backup); + ok(ret, "DeleteFileA: error (backup) %d\n", GetLastError()); + /* perform replacement w/ backup (no pre-existing backup) + * TODO: flags are not implemented + */ + SetLastError(0xdeadbeef); + ret = ReplaceFileA(replaced, replacement, backup, 0, 0, 0); + ok(ret, "ReplaceFileA: unexpected error %d\n", GetLastError()); + + /* re-create replacement file for pass w/ no permissions to "replaced" */ + ret = GetTempFileNameA(temp_path, prefix, 0, replacement); + ok(ret != 0, "GetTempFileNameA error (replacement) %d\n", GetLastError()); + ret = SetFileAttributesA(replaced, FILE_ATTRIBUTE_READONLY); + ok(ret, "SetFileAttributesA: error setting to read only %d\n", GetLastError()); + /* perform replacement w/ backup (no permission to "replaced") + * TODO: flags are not implemented + */ + SetLastError(0xdeadbeef); + ret = ReplaceFileA(replaced, replacement, backup, 0, 0, 0); + ok(ret != ERROR_UNABLE_TO_REMOVE_REPLACED, "ReplaceFileA: unexpected error %d\n", GetLastError()); + /* make sure that the replacement file still exists */ + hReplacementFile = CreateFileA(replacement, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); + ok(hReplacementFile != INVALID_HANDLE_VALUE, + "unexpected error, replacement file should still exist %d\n", GetLastError()); + CloseHandle(hReplacementFile); + ret = SetFileAttributesA(replaced, FILE_ATTRIBUTE_NORMAL); + ok(ret, "SetFileAttributesA: error setting to normal %d\n", GetLastError()); + + /* replacement file still exists, make pass w/o "replaced" */ + ret = DeleteFileA(replaced); + ok(ret, "DeleteFileA: error (replaced) %d\n", GetLastError()); + /* perform replacement w/ backup (no pre-existing backup or "replaced") + * TODO: flags are not implemented + */ + SetLastError(0xdeadbeef); + ret = ReplaceFileA(replaced, replacement, backup, 0, 0, 0); + ok(ret, "ReplaceFileA: unexpected error %d\n", GetLastError()); + + /* perform replacement w/o existing "replacement" file + * TODO: flags are not implemented + */ + SetLastError(0xdeadbeef); + ret = ReplaceFileA(replaced, replacement, NULL, 0, 0, 0); + ok(!ret && GetLastError() == ERROR_FILE_NOT_FOUND, + "ReplaceFileA: unexpected error %d\n", GetLastError()); + + /* + * if the first round (w/ backup) worked then as long as there is no + * failure then there is no need to check this round (w/ backup is the + * more complete case) + */ + + /* delete temporary files, replacement is already deleted */ + ret = DeleteFileA(backup); + ok(ret, "DeleteFileA: error (backup) %d\n", GetLastError()); + ret = DeleteFileA(replaced); + ok(ret, "DeleteFileA: error (replaced) %d\n", GetLastError()); +} + +/* + * ReplaceFileW is a simpler case of ReplaceFileA, there is no + * need to be as thorough. + */ +static void test_ReplaceFileW(void) +{ + WCHAR replaced[MAX_PATH], replacement[MAX_PATH], backup[MAX_PATH]; + static const WCHAR prefix[] = {'p','f','x',0}; + WCHAR temp_path[MAX_PATH]; + DWORD ret; + + ret = GetTempPathW(MAX_PATH, temp_path); + if (ret==0 && GetLastError()==ERROR_CALL_NOT_IMPLEMENTED) + return; + ok(ret != 0, "GetTempPathW error %d\n", GetLastError()); + ok(ret < MAX_PATH, "temp path should fit into MAX_PATH\n"); + + ret = GetTempFileNameW(temp_path, prefix, 0, replaced); + ok(ret != 0, "GetTempFileNameW error (replaced) %d\n", GetLastError()); + + ret = GetTempFileNameW(temp_path, prefix, 0, replacement); + ok(ret != 0, "GetTempFileNameW error (replacement) %d\n", GetLastError()); + + ret = GetTempFileNameW(temp_path, prefix, 0, backup); + ok(ret != 0, "GetTempFileNameW error (backup) %d\n", GetLastError()); + + ret = ReplaceFileW(replaced, replacement, backup, 0, 0, 0); + ok(ret, "ReplaceFileW: error %d\n", GetLastError()); + + ret = GetTempFileNameW(temp_path, prefix, 0, replacement); + ok(ret != 0, "GetTempFileNameW error (replacement) %d\n", GetLastError()); + ret = ReplaceFileW(replaced, replacement, NULL, 0, 0, 0); + ok(ret, "ReplaceFileW: error %d\n", GetLastError()); + + ret = GetTempFileNameW(temp_path, prefix, 0, replacement); + ok(ret != 0, "GetTempFileNameW error (replacement) %d\n", GetLastError()); + ret = DeleteFileW(backup); + ok(ret, "DeleteFileW: error (backup) %d\n", GetLastError()); + ret = ReplaceFileW(replaced, replacement, backup, 0, 0, 0); + ok(ret, "ReplaceFileW: error %d\n", GetLastError()); + + ret = GetTempFileNameW(temp_path, prefix, 0, replacement); + ok(ret != 0, "GetTempFileNameW error (replacement) %d\n", GetLastError()); + ret = SetFileAttributesW(replaced, FILE_ATTRIBUTE_READONLY); + ok(ret, "SetFileAttributesW: error setting to read only %d\n", GetLastError()); + + ret = ReplaceFileW(replaced, replacement, backup, 0, 0, 0); + ok(ret != ERROR_UNABLE_TO_REMOVE_REPLACED, + "ReplaceFileW: unexpected error %d\n", GetLastError()); + ret = SetFileAttributesW(replaced, FILE_ATTRIBUTE_NORMAL); + ok(ret, "SetFileAttributesW: error setting to normal %d\n", GetLastError()); + + ret = DeleteFileW(replaced); + ok(ret, "DeleteFileW: error (replaced) %d\n", GetLastError()); + ret = ReplaceFileW(replaced, replacement, backup, 0, 0, 0); + ok(ret, "ReplaceFileW: error %d\n", GetLastError()); + + ret = ReplaceFileW(replaced, replacement, NULL, 0, 0, 0); + ok(!ret && GetLastError() == ERROR_FILE_NOT_FOUND, + "ReplaceFileW: unexpected error %d\n", GetLastError()); + + ret = DeleteFileW(backup); + ok(ret, "DeleteFileW: error %d\n", GetLastError()); + ret = DeleteFileW(replaced); + ok(ret, "DeleteFileW: error %d\n", GetLastError()); +} + START_TEST(file) { test__hread( ); @@ -1849,4 +2099,6 @@ START_TEST(file) test_OpenFile(); test_overlapped(); test_RemoveDirectory(); + test_ReplaceFileA(); + test_ReplaceFileW(); } -- 1.4.4.2