win32_apply.c: Simplify and fix win32_create_file()
authorEric Biggers <ebiggers3@gmail.com>
Wed, 15 Jan 2014 18:07:06 +0000 (12:07 -0600)
committerEric Biggers <ebiggers3@gmail.com>
Wed, 15 Jan 2014 19:16:32 +0000 (13:16 -0600)
win32_create_file() can be simplified by using CREATE_NEW and attempting
to delete the existing file if it exists.

This commit also adds FILE_FLAG_OPEN_REPARSE_POINT to the CreateFile()
call.  This is the equivalent to the O_NOFOLLOW addition to the UNIX
version in the previous commit.

Note that MS has contradictory documentation for the combination of
CREATE_ALWAYS and FILE_FLAG_OPEN_REPARSE_POINT, which further supports
the CREATE_NEW method changed to in this commit.

src/win32_apply.c

index df74519..af8a4ef 100644 (file)
@@ -81,58 +81,68 @@ win32_start_extract(const wchar_t *path, struct apply_ctx *ctx)
        return 0;
 }
 
+/* Delete a non-directory file, working around Windows quirks.  */
+static BOOL
+win32_delete_file_wrapper(const wchar_t *path)
+{
+       DWORD err;
+       DWORD attrib;
+
+       if (DeleteFile(path))
+               return TRUE;
+
+       err = GetLastError();
+       attrib = GetFileAttributes(path);
+       if ((attrib != INVALID_FILE_ATTRIBUTES) &&
+           (attrib & FILE_ATTRIBUTE_READONLY))
+       {
+               /* Try again with FILE_ATTRIBUTE_READONLY cleared.  */
+               attrib &= ~FILE_ATTRIBUTE_READONLY;
+               if (SetFileAttributes(path, attrib)) {
+                       if (DeleteFile(path))
+                               return TRUE;
+                       else
+                               err = GetLastError();
+               }
+       }
+
+       SetLastError(err);
+       return FALSE;
+}
+
+
 /* Create a normal file, overwriting one already present.  */
 static int
 win32_create_file(const wchar_t *path, struct apply_ctx *ctx, u64 *cookie_ret)
 {
        HANDLE h;
-       unsigned retry_count = 0;
-       DWORD dwFlagsAndAttributes = FILE_FLAG_BACKUP_SEMANTICS;
 
+       /* Notes:
+        *
+        * WRITE_OWNER and WRITE_DAC privileges are required for some reason,
+        * even through we're creating a new file.
+        *
+        * FILE_FLAG_OPEN_REPARSE_POINT is required to prevent an existing
+        * reparse point from redirecting the creation of the new file
+        * (potentially to an arbitrary location).
+        *
+        * CREATE_ALWAYS could be used instead of CREATE_NEW.  However, there
+        * are quirks that would need to be handled (e.g. having to set
+        * FILE_ATTRIBUTE_HIDDEN and/or FILE_ATTRIBUTE_SYSTEM if the existing
+        * file had them specified, and/or having to clear
+        * FILE_ATTRIBUTE_READONLY on the existing file).  It's simpler to just
+        * call win32_delete_file_wrapper() to delete the existing file in such
+        * a way that already handles the FILE_ATTRIBUTE_READONLY quirk.
+        */
 retry:
-       /* WRITE_OWNER and WRITE_DAC privileges are required for some reason,
-        * even through we're creating a new file.  */
-       h = CreateFile(path, WRITE_OWNER | WRITE_DAC, 0, NULL,
-                      CREATE_ALWAYS, dwFlagsAndAttributes, NULL);
+       h = CreateFile(path, WRITE_OWNER | WRITE_DAC, 0, NULL, CREATE_NEW,
+                      FILE_FLAG_BACKUP_SEMANTICS |
+                               FILE_FLAG_OPEN_REPARSE_POINT, NULL);
        if (h == INVALID_HANDLE_VALUE) {
-               /* File couldn't be created.  */
                DWORD err = GetLastError();
-               if (err == ERROR_ACCESS_DENIED && retry_count == 0) {
 
-                       /* Access denied error for the first time.  Try
-                        * adjusting file attributes.  */
-
-                       /* Get attributes of the existing file.  */
-                       DWORD attribs = GetFileAttributes(path);
-                       if (attribs != INVALID_FILE_ATTRIBUTES &&
-                           (attribs & (FILE_ATTRIBUTE_HIDDEN |
-                                       FILE_ATTRIBUTE_SYSTEM |
-                                       FILE_ATTRIBUTE_READONLY)))
-                       {
-                               /* If the existing file has
-                                * FILE_ATTRIBUTE_HIDDEN and/or
-                                * FILE_ATTRIBUTE_SYSTEM, they must be set in
-                                * the call to CreateFile().  This is true even
-                                * when FILE_ATTRIBUTE_NORMAL was not specified,
-                                * contrary to the MS "documentation".  */
-                               dwFlagsAndAttributes |= (attribs &
-                                                        (FILE_ATTRIBUTE_HIDDEN |
-                                                         FILE_ATTRIBUTE_SYSTEM));
-                               /* If the existing file has
-                                * FILE_ATTRIBUTE_READONLY, it must be cleared
-                                * before attempting to create a new file over
-                                * it.  This is true even when the process has
-                                * the SE_RESTORE_NAME privilege and requested
-                                * the FILE_FLAG_BACKUP_SEMANTICS flag to
-                                * CreateFile().  */
-                               if (attribs & FILE_ATTRIBUTE_READONLY) {
-                                       SetFileAttributes(path,
-                                                         attribs & ~FILE_ATTRIBUTE_READONLY);
-                               }
-                               retry_count++;
-                               goto retry;
-                       }
-               }
+               if (err == ERROR_FILE_EXISTS && win32_delete_file_wrapper(path))
+                       goto retry;
                set_errno_from_win32_error(err);
                return WIMLIB_ERR_OPEN;
        }
@@ -154,35 +164,6 @@ error:
        return WIMLIB_ERR_MKDIR;
 }
 
-/* Delete a non-directory file, working around Windows quirks.  */
-static BOOL
-win32_delete_file_wrapper(const wchar_t *path)
-{
-       DWORD err;
-       DWORD attrib;
-
-       if (DeleteFile(path))
-               return TRUE;
-
-       err = GetLastError();
-       attrib = GetFileAttributes(path);
-       if ((attrib != INVALID_FILE_ATTRIBUTES) &&
-           (attrib & FILE_ATTRIBUTE_READONLY))
-       {
-               /* Try again with FILE_ATTRIBUTE_READONLY cleared.  */
-               attrib &= ~FILE_ATTRIBUTE_READONLY;
-               if (SetFileAttributes(path, attrib)) {
-                       if (DeleteFile(path))
-                               return TRUE;
-                       else
-                               err = GetLastError();
-               }
-       }
-
-       SetLastError(err);
-       return FALSE;
-}
-
 static int
 win32_create_hardlink(const wchar_t *oldpath, const wchar_t *newpath,
                      struct apply_ctx *ctx)