From: Eric Biggers Date: Sun, 18 Aug 2013 02:48:50 +0000 (-0500) Subject: Win32 capture: Query stream information with native API X-Git-Tag: v1.5.0~46 X-Git-Url: https://wimlib.net/git/?p=wimlib;a=commitdiff_plain;h=8fc9b6f370468126f775fe5d0845dd4417c15a42 Win32 capture: Query stream information with native API --- diff --git a/configure.ac b/configure.ac index 84c397a3..3e076173 100644 --- a/configure.ac +++ b/configure.ac @@ -231,6 +231,24 @@ case "$host" in ;; esac +AC_ARG_WITH([ntdll], + AS_HELP_STRING([--without-ntdll], [Windows only: do not link with + ntdll. By default, in some parts of its code, + wimlib uses Windows NT system calls to work around + flaws in the Win32 API. This support can be omitted + if needed, but some annoying bugs will surface (e.g. + permission denied errors even when running as the + Administrator).]), + [WITH_NTDLL=$withval], + [WITH_NTDLL=yes]) + +if test "x$WINDOWS_NATIVE_BUILD" = "xyes"; then + if test "x$WITH_NTDLL" = "xyes"; then + WINDOWS_LDADD="$WINDOWS_LDADD -lntdll" + AC_DEFINE([WITH_NTDLL], [1], [Define to 1 if linking with ntdll]) + fi +fi + AC_SUBST([VISIBILITY_CFLAGS], [$VISIBILITY_CFLAGS]) AC_SUBST([WINDOWS_LDFLAGS], [$WINDOWS_LDFLAGS]) AC_SUBST([WINDOWS_LDADD], [$WINDOWS_LDADD]) diff --git a/src/win32_capture.c b/src/win32_capture.c index bbcfbe3d..0957d54c 100644 --- a/src/win32_capture.c +++ b/src/win32_capture.c @@ -36,6 +36,11 @@ #include "wimlib/paths.h" #include "wimlib/reparse.h" +#ifdef WITH_NTDLL +# include +# include +#endif + #define MAX_GET_SD_ACCESS_DENIED_WARNINGS 1 #define MAX_GET_SACL_PRIV_NOTHELD_WARNINGS 1 #define MAX_CAPTURE_LONG_PATH_WARNINGS 5 @@ -811,27 +816,22 @@ out_invalid_stream_name: goto out; } -/* Scans a Win32 file for unnamed and named data streams (not reparse point - * streams). - * - * @path: Path to the file (UTF-16LE). +/* Load information about the streams of an open file into a WIM inode. * - * @path_num_chars: Number of 2-byte characters in @path. - * - * @inode: WIM inode to save the stream into. + * By default, we use the NtQueryInformationFile() system call instead of + * FindFirstStream() and FindNextStream(). This is done for two reasons: * - * @lookup_table: Stream lookup table for the WIM. - * - * @file_size: Size of unnamed data stream. (Used only if alternate - * data streams API appears to be unavailable.) - * - * @vol_flags: Flags that specify features of the volume being - * captured. - * - * Returns 0 on success; nonzero on failure. + * - FindFirstStream() opens its own handle to the file or directory and + * apparently does so without specifying FILE_FLAG_BACKUP_SEMANTICS, thereby + * causing access denied errors on certain files (even when running as the + * Administrator). + * - FindFirstStream() and FindNextStream() is only available on Windows Vista + * and later, whereas the stream support in NtQueryInformationFile() was + * already present in Windows XP. */ static int -win32_capture_streams(const wchar_t *path, +win32_capture_streams(HANDLE hFile, + const wchar_t *path, size_t path_num_chars, struct wim_inode *inode, struct wim_lookup_table *lookup_table, @@ -840,15 +840,95 @@ win32_capture_streams(const wchar_t *path, { WIN32_FIND_STREAM_DATA dat; int ret; +#ifdef WITH_NTDLL + u8 _buf[8192] _aligned_attribute(8); + u8 *buf; + size_t bufsize; + IO_STATUS_BLOCK io_status; + NTSTATUS status; + const FILE_STREAM_INFORMATION *info; +#else HANDLE hFind; DWORD err; +#endif DEBUG("Capturing streams from \"%ls\"", path); - if (win32func_FindFirstStreamW == NULL || - !(vol_flags & FILE_NAMED_STREAMS)) + if (!(vol_flags & FILE_NAMED_STREAMS)) goto unnamed_only; +#ifndef WITH_NTDLL + if (win32func_FindFirstStreamW == NULL) + goto unnamed_only; +#endif + +#ifdef WITH_NTDLL + buf = _buf; + bufsize = sizeof(_buf); + + /* Get a buffer containing the stream information. */ + for (;;) { + status = NtQueryInformationFile(hFile, &io_status, buf, bufsize, + FileStreamInformation); + if (status == STATUS_SUCCESS) { + break; + } else if (status == STATUS_BUFFER_OVERFLOW) { + u8 *newbuf; + + bufsize *= 2; + if (buf == _buf) + newbuf = MALLOC(bufsize); + else + newbuf = REALLOC(buf, bufsize); + if (!newbuf) { + ret = WIMLIB_ERR_NOMEM; + goto out_free_buf; + } + buf = newbuf; + } else { + errno = win32_error_to_errno(RtlNtStatusToDosError(status)); + ERROR_WITH_ERRNO("Failed to read streams of %ls", path); + ret = WIMLIB_ERR_READ; + goto out_free_buf; + } + } + + if (io_status.Information == 0) { + /* No stream information. */ + ret = 0; + goto out_free_buf; + } + + /* Parse one or more stream information structures. */ + info = (const FILE_STREAM_INFORMATION*)buf; + for (;;) { + if (info->StreamNameLength <= sizeof(dat.cStreamName) - 2) { + dat.StreamSize = info->StreamSize; + memcpy(dat.cStreamName, info->StreamName, info->StreamNameLength); + dat.cStreamName[info->StreamNameLength / 2] = L'\0'; + + /* Capture the stream. */ + ret = win32_capture_stream(path, path_num_chars, inode, + lookup_table, &dat); + if (ret) + goto out_free_buf; + } + if (info->NextEntryOffset == 0) { + /* No more stream information. */ + ret = 0; + break; + } + /* Advance to next stream information. */ + info = (const FILE_STREAM_INFORMATION*) + ((const u8*)info + info->NextEntryOffset); + } +out_free_buf: + /* Free buffer if allocated on heap. */ + if (buf != _buf) + FREE(buf); + return ret; + +#else /* WITH_NTDLL */ hFind = win32func_FindFirstStreamW(path, FindStreamInfoStandard, &dat, 0); if (hFind == INVALID_HANDLE_VALUE) { err = GetLastError(); @@ -894,24 +974,23 @@ win32_capture_streams(const wchar_t *path, out_find_close: FindClose(hFind); return ret; +#endif /* !WITH_NTDLL */ + unnamed_only: /* FindFirstStreamW() API is not available, or the volume does not * support named streams. Only capture the unnamed data stream. */ DEBUG("Only capturing unnamed data stream"); - if (inode->i_attributes & - (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) + if (!(inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_REPARSE_POINT))) { - ret = 0; - } else { - /* Just create our own WIN32_FIND_STREAM_DATA for an unnamed - * stream to reduce the code to a call to the - * already-implemented win32_capture_stream() */ wcscpy(dat.cStreamName, L"::$DATA"); dat.StreamSize.QuadPart = file_size; ret = win32_capture_stream(path, path_num_chars, inode, lookup_table, &dat); + if (ret) + return ret; } return ret; } @@ -1045,18 +1124,20 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret, file_size = ((u64)file_info.nFileSizeHigh << 32) | (u64)file_info.nFileSizeLow; - CloseHandle(hFile); /* Capture the unnamed data stream (only should be present for regular * files) and any alternate data streams. */ - ret = win32_capture_streams(path, + ret = win32_capture_streams(hFile, + path, path_num_chars, inode, params->lookup_table, file_size, vol_flags); if (ret) - goto out; + goto out_close_handle; + + CloseHandle(hFile); if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { /* Reparse point: set the reparse data (which we read already) @@ -1132,10 +1213,12 @@ win32_build_dentry_tree(struct wim_dentry **root_ret, DWORD dret; bool need_prefix_free = false; +#ifndef WITH_NTDLL if (!win32func_FindFirstStreamW) { WARNING("Running on Windows XP or earlier; " "alternate data streams will not be captured."); } +#endif path_nchars = wcslen(root_disk_path); if (path_nchars > WINDOWS_NT_MAX_PATH) diff --git a/src/win32_common.c b/src/win32_common.c index c347a965..e3dace38 100644 --- a/src/win32_common.c +++ b/src/win32_common.c @@ -520,6 +520,7 @@ win32_open_file_data_only(const wchar_t *path) return win32_open_existing_file(path, FILE_READ_DATA); } +#ifndef WITH_NTDLL /* Pointers to functions that are not available on all targetted versions of * Windows (XP and later). NOTE: The WINAPI annotations seem to be important; I * assume it specifies a certain calling convention. */ @@ -533,6 +534,7 @@ HANDLE (WINAPI *win32func_FindFirstStreamW)(LPCWSTR lpFileName, /* Vista and later */ BOOL (WINAPI *win32func_FindNextStreamW)(HANDLE hFindStream, LPVOID lpFindStreamData) = NULL; +#endif /* !WITH_NTDLL */ /* Vista and later */ BOOL (WINAPI *win32func_CreateSymbolicLinkW)(const wchar_t *lpSymlinkFileName, @@ -578,6 +580,7 @@ win32_global_init(int init_flags) hKernel32 = LoadLibrary(L"Kernel32.dll"); if (hKernel32) { + #ifndef WITH_NTDLL win32func_FindFirstStreamW = (void*)GetProcAddress(hKernel32, "FindFirstStreamW"); if (win32func_FindFirstStreamW) { @@ -586,6 +589,7 @@ win32_global_init(int init_flags) if (!win32func_FindNextStreamW) win32func_FindFirstStreamW = NULL; } + #endif /* !WITH_NTDLL */ win32func_CreateSymbolicLinkW = (void*)GetProcAddress(hKernel32, "CreateSymbolicLinkW"); }