win32_common: add winnt_fsctl() helper method
authorEric Biggers <ebiggers3@gmail.com>
Thu, 18 Feb 2016 02:21:38 +0000 (20:21 -0600)
committerEric Biggers <ebiggers3@gmail.com>
Thu, 18 Feb 2016 02:21:38 +0000 (20:21 -0600)
This is needed to handle STATUS_PENDING.

include/wimlib/win32_common.h
src/win32_apply.c
src/win32_capture.c
src/win32_common.c

index d3649ac..e669d52 100644 (file)
@@ -101,17 +101,6 @@ extern NTSTATUS (WINAPI *func_NtSetSecurityObject)(HANDLE Handle,
                                                   SECURITY_INFORMATION SecurityInformation,
                                                   PSECURITY_DESCRIPTOR SecurityDescriptor);
 
-extern NTSTATUS (WINAPI *func_NtFsControlFile) (HANDLE FileHandle,
-                                               HANDLE Event,
-                                               PIO_APC_ROUTINE ApcRoutine,
-                                               PVOID ApcContext,
-                                               PIO_STATUS_BLOCK IoStatusBlock,
-                                               ULONG FsControlCode,
-                                               PVOID InputBuffer,
-                                               ULONG InputBufferLength,
-                                               PVOID OutputBuffer,
-                                               ULONG OutputBufferLength);
-
 extern NTSTATUS (WINAPI *func_NtClose) (HANDLE Handle);
 
 extern DWORD (WINAPI *func_RtlNtStatusToDosError)(NTSTATUS status);
@@ -174,4 +163,8 @@ winnt_warning(NTSTATUS status, const wchar_t *format, ...) _cold_attribute;
 extern void
 winnt_error(NTSTATUS status, const wchar_t *format, ...) _cold_attribute;
 
+extern NTSTATUS
+winnt_fsctl(HANDLE h, u32 code, const void *in, u32 in_size,
+           void *out, u32 out_size_avail, u32 *actual_out_size_ret);
+
 #endif /* _WIMLIB_WIN32_COMMON_H */
index 395e698..afa11ac 100644 (file)
@@ -1092,6 +1092,9 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
 {
        const bool compressed = (dentry->d_inode->i_attributes &
                                 FILE_ATTRIBUTE_COMPRESSED);
+       FILE_BASIC_INFORMATION info;
+       USHORT compression_state;
+       NTSTATUS status;
 
        if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)
                return 0;
@@ -1099,10 +1102,6 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
        if (!ctx->common.supported_features.compressed_files)
                return 0;
 
-       FILE_BASIC_INFORMATION info;
-       NTSTATUS status;
-       USHORT compression_state;
-       DWORD bytes_returned;
 
        /* Get current attributes  */
        status = (*func_NtQueryInformationFile)(h, &ctx->iosb,
@@ -1122,14 +1121,12 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
        else
                compression_state = COMPRESSION_FORMAT_NONE;
 
-       /* Note: don't use NtFsControlFile() here unless prepared to handle
-        * STATUS_PENDING.  */
-       if (DeviceIoControl(h, FSCTL_SET_COMPRESSION,
-                           &compression_state, sizeof(USHORT), NULL, 0,
-                           &bytes_returned, NULL))
-               return 0;
+       status = winnt_fsctl(h, FSCTL_SET_COMPRESSION,
+                            &compression_state, sizeof(USHORT), NULL, 0, NULL);
+       if (NT_SUCCESS(status))
+               return status;
 
-       win32_error(GetLastError(), L"Can't %s compression attribute on \"%ls\"",
+       winnt_error(status, L"Can't %s compression attribute on \"%ls\"",
                    (compressed ? "set" : "clear"), current_path(ctx));
        return WIMLIB_ERR_SET_ATTRIBUTES;
 }
@@ -1347,7 +1344,7 @@ retry:
  * A wrapper around NtCreateFile() to make it slightly more usable...
  * This uses the path currently constructed in ctx->pathbuf.
  *
- * Also, we always specify FILE_OPEN_FOR_BACKUP_INTENT and
+ * Also, we always specify SYNCHRONIZE access, FILE_OPEN_FOR_BACKUP_INTENT, and
  * FILE_OPEN_REPARSE_POINT.
  */
 static NTSTATUS
@@ -1360,7 +1357,7 @@ do_create_file(PHANDLE FileHandle,
               struct win32_apply_ctx *ctx)
 {
        return (*func_NtCreateFile)(FileHandle,
-                                   DesiredAccess,
+                                   DesiredAccess | SYNCHRONIZE,
                                    &ctx->attr,
                                    &ctx->iosb,
                                    AllocationSize,
@@ -1525,10 +1522,8 @@ do_set_reparse_point(const struct wim_dentry *dentry,
        if (!NT_SUCCESS(status))
                goto fail;
 
-       status = (*func_NtFsControlFile)(h, NULL, NULL, NULL,
-                                        &ctx->iosb, FSCTL_SET_REPARSE_POINT,
-                                        (void *)rpbuf, rpbuflen,
-                                        NULL, 0);
+       status = winnt_fsctl(h, FSCTL_SET_REPARSE_POINT,
+                            rpbuf, rpbuflen, NULL, 0, NULL);
        (*func_NtClose)(h);
 
        if (NT_SUCCESS(status))
@@ -2378,7 +2373,6 @@ static NTSTATUS
 set_system_compression(HANDLE h, int format)
 {
        NTSTATUS status;
-       IO_STATUS_BLOCK iosb;
        struct {
                struct wof_external_info wof_info;
                struct file_provider_external_info file_info;
@@ -2399,9 +2393,8 @@ set_system_compression(HANDLE h, int format)
         * versions of Windows (before Windows 10?).  This can be a problem if
         * the WOFADK driver is being used rather than the regular WOF, since
         * WOFADK can be used on older versions of Windows.  */
-       status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, &iosb,
-                                        FSCTL_SET_EXTERNAL_BACKING,
-                                        &in, sizeof(in), NULL, 0);
+       status = winnt_fsctl(h, FSCTL_SET_EXTERNAL_BACKING,
+                            &in, sizeof(in), NULL, 0, NULL);
 
        if (status == 0xC000046F) /* "Compressing this object would not save space."  */
                return STATUS_SUCCESS;
index c9e8ba1..951f61b 100644 (file)
@@ -243,8 +243,7 @@ get_windows_file_path(const struct windows_file *file)
  * Open the file named by the NT namespace path @path of length @path_nchars
  * characters.  If @cur_dir is not NULL then the path is given relative to
  * @cur_dir; otherwise the path is absolute.  @perms is the access mask of
- * permissions to request on the handle.  If permission to read the data is
- * requested, then SYNCHRONIZE is automatically added.
+ * permissions to request on the handle.  SYNCHRONIZE permision is always added.
  */
 static NTSTATUS
 winnt_openat(HANDLE cur_dir, const wchar_t *path, size_t path_nchars,
@@ -264,8 +263,8 @@ winnt_openat(HANDLE cur_dir, const wchar_t *path, size_t path_nchars,
        NTSTATUS status;
        ULONG options = FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT;
 
+       perms |= SYNCHRONIZE;
        if (perms & (FILE_READ_DATA | FILE_LIST_DIRECTORY)) {
-               perms |= SYNCHRONIZE;
                options |= FILE_SYNCHRONOUS_IO_NONALERT;
                options |= FILE_SEQUENTIAL_ONLY;
        }
@@ -978,7 +977,8 @@ winnt_load_reparse_data(HANDLE h, struct wim_inode *inode,
                        const wchar_t *full_path, struct capture_params *params)
 {
        struct reparse_buffer_disk rpbuf;
-       DWORD bytes_returned;
+       NTSTATUS status;
+       u32 len;
        u16 rpbuflen;
        int ret;
 
@@ -989,16 +989,15 @@ winnt_load_reparse_data(HANDLE h, struct wim_inode *inode,
                return 0;
        }
 
-       if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT,
-                            NULL, 0, &rpbuf, REPARSE_POINT_MAX_SIZE,
-                            &bytes_returned, NULL))
-       {
-               win32_error(GetLastError(), L"\"%ls\": Can't get reparse point",
+       status = winnt_fsctl(h, FSCTL_GET_REPARSE_POINT,
+                            NULL, 0, &rpbuf, sizeof(rpbuf), &len);
+       if (!NT_SUCCESS(status)) {
+               winnt_error(status, L"\"%ls\": Can't get reparse point",
                            printable_path(full_path));
                return WIMLIB_ERR_READLINK;
        }
 
-       rpbuflen = bytes_returned;
+       rpbuflen = len;
 
        if (unlikely(rpbuflen < REPARSE_DATA_OFFSET)) {
                ERROR("\"%ls\": reparse point buffer is too short",
@@ -1295,12 +1294,9 @@ get_sort_key(HANDLE h)
 {
        STARTING_VCN_INPUT_BUFFER in = { .StartingVcn.QuadPart = 0 };
        RETRIEVAL_POINTERS_BUFFER out;
-       DWORD bytesReturned;
 
-       if (!DeviceIoControl(h, FSCTL_GET_RETRIEVAL_POINTERS,
-                            &in, sizeof(in),
-                            &out, sizeof(out),
-                            &bytesReturned, NULL))
+       if (!NT_SUCCESS(winnt_fsctl(h, FSCTL_GET_RETRIEVAL_POINTERS,
+                                   &in, sizeof(in), &out, sizeof(out), NULL)))
                return 0;
 
        return extract_starting_lcn(&out);
@@ -1396,14 +1392,12 @@ try_to_use_wimboot_hash(HANDLE h, struct wim_inode *inode,
                        struct wof_external_info wof_info;
                        struct wim_provider_external_info wim_info;
                } out;
-               IO_STATUS_BLOCK iosb;
                NTSTATUS status;
 
                /* WOF may be attached.  Try reading this file's external
                 * backing info.  */
-               status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, &iosb,
-                                                FSCTL_GET_EXTERNAL_BACKING,
-                                                NULL, 0, &out, sizeof(out));
+               status = winnt_fsctl(h, FSCTL_GET_EXTERNAL_BACKING,
+                                    NULL, 0, &out, sizeof(out), NULL);
 
                /* Is WOF not attached?  */
                if (status == STATUS_INVALID_DEVICE_REQUEST ||
@@ -2349,9 +2343,7 @@ load_files_from_mft(const wchar_t *path, struct ntfs_inode_map *inode_map)
        };
        const size_t outsize = 32768;
        QUERY_FILE_LAYOUT_OUTPUT *out = NULL;
-       DWORD bytes_returned;
        int ret;
-       DWORD err;
        NTSTATUS status;
 
        status = winnt_open(path, wcslen(path),
@@ -2367,8 +2359,9 @@ load_files_from_mft(const wchar_t *path, struct ntfs_inode_map *inode_map)
                goto out;
        }
 
-       while (DeviceIoControl(h, FSCTL_QUERY_FILE_LAYOUT, &in, sizeof(in),
-                              out, outsize, &bytes_returned, NULL))
+       while (NT_SUCCESS(status = winnt_fsctl(h, FSCTL_QUERY_FILE_LAYOUT,
+                                              &in, sizeof(in),
+                                              out, outsize, NULL)))
        {
                const FILE_LAYOUT_ENTRY *file =
                        (const void *)out + out->FirstFileOffset;
@@ -2383,16 +2376,15 @@ load_files_from_mft(const wchar_t *path, struct ntfs_inode_map *inode_map)
                in.Flags &= ~QUERY_FILE_LAYOUT_RESTART;
        }
 
-       /* Normally, FSCTL_QUERY_FILE_LAYOUT fails with error code 38 after all
-        * files have been enumerated.  */
-       err = GetLastError();
-       if (err != 38) {
-               if (err == ERROR_INVALID_FUNCTION ||
-                   err == ERROR_INVALID_PARAMETER) {
+       /* Normally, FSCTL_QUERY_FILE_LAYOUT fails with STATUS_END_OF_FILE after
+        * all files have been enumerated.  */
+       if (status != STATUS_END_OF_FILE) {
+               if (status == STATUS_INVALID_DEVICE_REQUEST /* old OS */ ||
+                   status == STATUS_INVALID_PARAMETER /* not root directory */ ) {
                        /* Silently try standard recursive scan instead  */
                        ret = -1;
                } else {
-                       win32_error(err,
+                       winnt_error(status,
                                    L"Error enumerating files on volume \"%ls\"",
                                    path);
                        /* Try standard recursive scan instead  */
index b8ac219..628f1f0 100644 (file)
@@ -3,7 +3,7 @@
  */
 
 /*
- * Copyright (C) 2013, 2014, 2015 Eric Biggers
+ * Copyright (C) 2013-2016 Eric Biggers
  *
  * This file 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
@@ -27,6 +27,7 @@
 
 #include "wimlib/win32_common.h"
 
+#include "wimlib/assert.h"
 #include "wimlib/error.h"
 #include "wimlib/util.h"
 #include "wimlib/win32_vss.h"
@@ -170,16 +171,20 @@ NTSTATUS (WINAPI *func_NtSetSecurityObject)(HANDLE Handle,
                                            SECURITY_INFORMATION SecurityInformation,
                                            PSECURITY_DESCRIPTOR SecurityDescriptor);
 
-NTSTATUS (WINAPI *func_NtFsControlFile) (HANDLE FileHandle,
-                                        HANDLE Event,
-                                        PIO_APC_ROUTINE ApcRoutine,
-                                        PVOID ApcContext,
-                                        PIO_STATUS_BLOCK IoStatusBlock,
-                                        ULONG FsControlCode,
-                                        PVOID InputBuffer,
-                                        ULONG InputBufferLength,
-                                        PVOID OutputBuffer,
-                                        ULONG OutputBufferLength);
+static NTSTATUS (WINAPI *func_NtFsControlFile) (HANDLE FileHandle,
+                                               HANDLE Event,
+                                               PIO_APC_ROUTINE ApcRoutine,
+                                               PVOID ApcContext,
+                                               PIO_STATUS_BLOCK IoStatusBlock,
+                                               ULONG FsControlCode,
+                                               PVOID InputBuffer,
+                                               ULONG InputBufferLength,
+                                               PVOID OutputBuffer,
+                                               ULONG OutputBufferLength);
+
+static NTSTATUS (WINAPI *func_NtWaitForSingleObject) (HANDLE Handle,
+                                                     BOOLEAN Alertable,
+                                                     PLARGE_INTEGER Timeout);
 
 NTSTATUS (WINAPI *func_NtClose) (HANDLE Handle);
 
@@ -233,6 +238,7 @@ struct dll_spec ntdll_spec = {
                DLL_SYM(NtSetInformationFile, true),
                DLL_SYM(NtSetSecurityObject, true),
                DLL_SYM(NtFsControlFile, true),
+               DLL_SYM(NtWaitForSingleObject, true),
                DLL_SYM(NtClose, true),
                DLL_SYM(RtlNtStatusToDosError, true),
                DLL_SYM(RtlCreateSystemVolumeInformationFolder, false),
@@ -531,4 +537,40 @@ winnt_error(NTSTATUS status, const wchar_t *format, ...)
        va_end(va);
 }
 
+/*
+ * Synchronously execute a filesystem control method.  This is a wrapper around
+ * NtFsControlFile() that handles STATUS_PENDING.  Note that SYNCHRONIZE
+ * permission is, in general, required on the handle.
+ */
+NTSTATUS
+winnt_fsctl(HANDLE h, u32 code, const void *in, u32 in_size,
+           void *out, u32 out_size_avail, u32 *actual_out_size_ret)
+{
+       IO_STATUS_BLOCK iosb;
+       NTSTATUS status;
+
+       status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, &iosb, code,
+                                        (void *)in, in_size,
+                                        out, out_size_avail);
+       if (status == STATUS_PENDING) {
+               /* Beware: this case is often encountered with remote
+                * filesystems, but rarely with local filesystems.  */
+
+               status = (*func_NtWaitForSingleObject)(h, FALSE, NULL);
+               if (NT_SUCCESS(status)) {
+                       status = iosb.Status;
+               } else {
+                       /* We shouldn't be issuing ioctls on a handle to which
+                        * we don't have SYNCHRONIZE access.  Otherwise we have
+                        * no way to wait for them to complete.  */
+                       wimlib_assert(status != STATUS_ACCESS_DENIED);
+               }
+       }
+
+       if (NT_SUCCESS(status) && actual_out_size_ret != NULL)
+               *actual_out_size_ret = (u32)iosb.Information;
+
+       return status;
+}
+
 #endif /* __WIN32__ */