kernel32: Implement ReplaceFileA/ReplaceFileW (revised)

Erich Hoover ehoover at mines.edu
Mon Mar 19 12:27:02 CDT 2007


Skipped content of type multipart/alternative-------------- next part --------------
From 2162cd5dcbec02dc7ed4dc91da796bb85db95442 Mon Sep 17 00:00:00 2001
From: Erich Hoover <ehoover at mediaserver.(none)>
Date: Mon, 19 Mar 2007 11:19:01 -0600
Subject: kernel32: Implement ReplaceFileA/ReplaceFileW
---
 dlls/kernel32/file.c           |  200 ++++++++++++++++++++++++++++++--
 dlls/kernel32/kernel_private.h |    3 
 dlls/kernel32/path.c           |   23 ----
 dlls/kernel32/tests/file.c     |  252 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 448 insertions(+), 30 deletions(-)

diff --git a/dlls/kernel32/file.c b/dlls/kernel32/file.c
index 71d5030..4005218 100644
--- a/dlls/kernel32/file.c
+++ b/dlls/kernel32/file.c
@@ -163,6 +163,46 @@ static BOOL check_dir_symlink( FIND_FIRS
 }
 
 
+/******************************************************************
+ *		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
  *
@@ -1531,10 +1571,124 @@ BOOL WINAPI ReplaceFileW(LPCWSTR lpRepla
                          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.
+         */
+        ERR("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;
 }
 
 
@@ -1545,10 +1699,40 @@ BOOL WINAPI ReplaceFileA(LPCSTR lpReplac
                          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 4796e62..014967b 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 b195435..1d66a44 100644
--- a/dlls/kernel32/path.c
+++ b/dlls/kernel32/path.c
@@ -855,23 +855,15 @@ DWORD WINAPI SearchPathA( LPCSTR path, L
  */
 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));
 
@@ -898,22 +890,9 @@ BOOL WINAPI CopyFileW( LPCWSTR source, L
         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 89cf0a2..1db17f0 100644
--- a/dlls/kernel32/tests/file.c
+++ b/dlls/kernel32/tests/file.c
@@ -1820,6 +1820,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(  );
@@ -1851,4 +2101,6 @@ START_TEST(file)
     test_OpenFile();
     test_overlapped();
     test_RemoveDirectory();
+    test_ReplaceFileA();
+    test_ReplaceFileW();
 }
-- 
1.4.1


More information about the wine-devel mailing list