]> wimlib.net Git - wimlib/blobdiff - src/win32_capture.c
Win32 capture: Query stream information with native API
[wimlib] / src / win32_capture.c
index 7f6df42e49393e65614ab512cfecdb1af17d0b25..0957d54cb3c8e1ec1d6910b0be4a6c81e94cc393 100644 (file)
 #include "wimlib/paths.h"
 #include "wimlib/reparse.h"
 
+#ifdef WITH_NTDLL
+#  include <winternl.h>
+#  include <ntstatus.h>
+#endif
+
 #define MAX_GET_SD_ACCESS_DENIED_WARNINGS 1
 #define MAX_GET_SACL_PRIV_NOTHELD_WARNINGS 1
+#define MAX_CAPTURE_LONG_PATH_WARNINGS 5
+
 struct win32_capture_state {
        unsigned long num_get_sd_access_denied;
        unsigned long num_get_sacl_priv_notheld;
+       unsigned long num_long_path_warnings;
 };
 
 
@@ -154,9 +162,10 @@ win32_encrypted_export_cb(unsigned char *_data, void *_ctx, unsigned long len)
                }
        } else {
                size_t len_to_copy = min(len, ctx->bytes_remaining);
-               memcpy(ctx->read_prefix_ctx_or_buf, data, len_to_copy);
+               ctx->read_prefix_ctx_or_buf = mempcpy(ctx->read_prefix_ctx_or_buf,
+                                                     data,
+                                                     len_to_copy);
                ctx->bytes_remaining -= len_to_copy;
-               ctx->read_prefix_ctx_or_buf += len_to_copy;
        }
        return ERROR_SUCCESS;
 }
@@ -367,7 +376,7 @@ win32_recurse_directory(struct wim_dentry *root,
         * opendir(), FindFirstFileW has file globbing built into it.  But this
         * isn't what we actually want, so just add a dummy glob to get all
         * entries. */
-       dir_path[dir_path_num_chars] = L'/';
+       dir_path[dir_path_num_chars] = OS_PREFERRED_PATH_SEPARATOR;
        dir_path[dir_path_num_chars + 1] = L'*';
        dir_path[dir_path_num_chars + 2] = L'\0';
        hFind = FindFirstFileW(dir_path, &dat);
@@ -393,7 +402,7 @@ win32_recurse_directory(struct wim_dentry *root,
                        continue;
                size_t filename_len = wcslen(dat.cFileName);
 
-               dir_path[dir_path_num_chars] = L'/';
+               dir_path[dir_path_num_chars] = OS_PREFERRED_PATH_SEPARATOR;
                wmemcpy(dir_path + dir_path_num_chars + 1,
                        dat.cFileName,
                        filename_len + 1);
@@ -506,12 +515,10 @@ win32_capture_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p,
                        const wchar_t *path)
 {
        struct reparse_data rpdata;
-       DWORD rpbuflen;
        int ret;
        enum rp_status rp_status;
 
-       rpbuflen = *rpbuflen_p;
-       ret = parse_reparse_data(rpbuf, rpbuflen, &rpdata);
+       ret = parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata);
        if (ret)
                return -ret;
 
@@ -519,7 +526,7 @@ win32_capture_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p,
                                                     &rpdata.substitute_name_nbytes,
                                                     capture_root_ino,
                                                     capture_root_dev,
-                                                    le32_to_cpu(*(u32*)rpbuf));
+                                                    le32_to_cpu(*(le32*)rpbuf));
        if (rp_status & RP_FIXED) {
                wimlib_assert(rpdata.substitute_name_nbytes % 2 == 0);
                utf16lechar substitute_name_copy[rpdata.substitute_name_nbytes / 2];
@@ -535,7 +542,7 @@ win32_capture_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p,
                        rpdata.print_name += 4;
                        rpdata.print_name_nbytes -= 8;
                }
-               ret = make_reparse_buffer(&rpdata, rpbuf);
+               ret = make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p);
                if (ret == 0)
                        ret = rp_status;
                else
@@ -612,7 +619,7 @@ win32_get_reparse_data(HANDLE hFile, const wchar_t *path,
        }
 
        rpbuflen = bytesReturned;
-       reparse_tag = le32_to_cpu(*(u32*)rpbuf);
+       reparse_tag = le32_to_cpu(*(le32*)rpbuf);
        if (params->add_flags & WIMLIB_ADD_FLAG_RPFIX &&
            (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
             reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT))
@@ -755,12 +762,11 @@ win32_capture_stream(const wchar_t *path,
        if (is_named_stream) {
                spath_nchars += 1 + stream_name_nchars;
                colonchar = L":";
-               if (path_num_chars == 1 &&
-                   path[0] != L'/' &&
-                   path[0] != L'\\')
-               {
+               if (path_num_chars == 1 && !is_any_path_separator(path[0])) {
                        spath_nchars += 2;
-                       relpath_prefix = L"./";
+                       static const wchar_t _relpath_prefix[] =
+                               {L'.', OS_PREFERRED_PATH_SEPARATOR, L'\0'};
+                       relpath_prefix = _relpath_prefix;
                }
        }
 
@@ -786,7 +792,7 @@ win32_capture_stream(const wchar_t *path,
                        goto out_free_spath;
                lte->resource_entry.original_size = encrypted_size;
        } else {
-               lte->resource_location = RESOURCE_WIN32;
+               lte->resource_location = RESOURCE_IN_FILE_ON_DISK;
                lte->resource_entry.original_size = (u64)dat->StreamSize.QuadPart;
        }
 
@@ -810,27 +816,22 @@ out_invalid_stream_name:
        goto out;
 }
 
-/* Scans a Win32 file for unnamed and named data streams (not reparse point
- * streams).
+/* Load information about the streams of an open file into a WIM inode.
  *
- * @path:               Path to the file (UTF-16LE).
- *
- * @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,
@@ -839,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();
@@ -893,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;
 }
@@ -932,32 +1012,32 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
        u16 rpbuflen;
        u16 not_rpfixed;
 
+       params->progress.scan.cur_path = path;
+
        if (exclude_path(path, path_num_chars, params->config, true)) {
                if (params->add_flags & WIMLIB_ADD_FLAG_ROOT) {
                        ERROR("Cannot exclude the root directory from capture");
                        ret = WIMLIB_ERR_INVALID_CAPTURE_CONFIG;
                        goto out;
                }
-               if ((params->add_flags & WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE)
-                   && params->progress_func)
-               {
-                       union wimlib_progress_info info;
-                       info.scan.cur_path = path;
-                       info.scan.excluded = true;
-                       params->progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info);
-               }
+               do_capture_progress(params, WIMLIB_SCAN_DENTRY_EXCLUDED);
                ret = 0;
                goto out;
        }
 
-       if ((params->add_flags & WIMLIB_ADD_FLAG_VERBOSE)
-           && params->progress_func)
+#if 0
+       if (path_num_chars >= 4 &&
+           !wmemcmp(path, L"\\\\?\\", 4) &&
+           path_num_chars + 1 - 4 > MAX_PATH &&
+           state->num_long_path_warnings < MAX_CAPTURE_LONG_PATH_WARNINGS)
        {
-               union wimlib_progress_info info;
-               info.scan.cur_path = path;
-               info.scan.excluded = false;
-               params->progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info);
+               WARNING("Path \"%ls\" exceeds MAX_PATH", path);
+               if (++state->num_long_path_warnings == MAX_CAPTURE_LONG_PATH_WARNINGS)
+                       WARNING("Suppressing further warnings about long paths.");
        }
+#endif
+
+       do_capture_progress(params, WIMLIB_SCAN_DENTRY_OK);
 
        HANDLE hFile = win32_open_existing_file(path,
                                                FILE_READ_DATA | FILE_READ_ATTRIBUTES);
@@ -1044,24 +1124,26 @@ 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)
                 * */
                inode->i_not_rpfixed = not_rpfixed;
-               inode->i_reparse_tag = le32_to_cpu(*(u32*)rpbuf);
+               inode->i_reparse_tag = le32_to_cpu(*(le32*)rpbuf);
                ret = inode_set_unnamed_stream(inode, rpbuf + 8, rpbuflen - 8,
                                               params->lookup_table);
        } else if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
@@ -1115,6 +1197,8 @@ win32_do_capture_warnings(const struct win32_capture_state *state,
 "          descriptors.\n");
 }
 
+#define WINDOWS_NT_MAX_PATH 32768
+
 /* Win32 version of capturing a directory tree */
 int
 win32_build_dentry_tree(struct wim_dentry **root_ret,
@@ -1126,14 +1210,18 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
        int ret;
        struct win32_capture_state state;
        unsigned vol_flags;
+       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 > 32767)
+       if (path_nchars > WINDOWS_NT_MAX_PATH)
                return WIMLIB_ERR_INVALID_PARAM;
 
        if (GetFileAttributesW(root_disk_path) == INVALID_FILE_ATTRIBUTES &&
@@ -1150,22 +1238,53 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
        if (ret)
                return ret;
 
-       win32_get_vol_flags(root_disk_path, &vol_flags);
+       win32_get_vol_flags(root_disk_path, &vol_flags, NULL);
 
-       /* There is no check for overflow later when this buffer is being used!
-        * But the max path length on NTFS is 32767 characters, and paths need
-        * to be written specially to even go past 260 characters, so we should
-        * be okay with 32770 characters. */
-       path = MALLOC(32770 * sizeof(wchar_t));
+       /* WARNING: There is no check for overflow later when this buffer is
+        * being used!  But it's as long as the maximum path length understood
+        * by Windows NT (which is NOT the same as MAX_PATH). */
+       path = MALLOC(WINDOWS_NT_MAX_PATH * sizeof(wchar_t));
        if (!path)
                return WIMLIB_ERR_NOMEM;
 
-       wmemcpy(path, root_disk_path, path_nchars + 1);
+       /* Work around defective behavior in Windows where paths longer than 260
+        * characters are not supported by default; instead they need to be
+        * turned into absolute paths and prefixed with "\\?\".  */
+
+       if (wcsncmp(root_disk_path, L"\\\\?\\", 4)) {
+               dret = GetFullPathName(root_disk_path, WINDOWS_NT_MAX_PATH - 4,
+                                      &path[4], NULL);
+
+               if (dret == 0 || dret >= WINDOWS_NT_MAX_PATH - 4) {
+                       WARNING("Can't get full path name for \"%ls\"", root_disk_path);
+                       wmemcpy(path, root_disk_path, path_nchars + 1);
+               } else {
+                       wmemcpy(path, L"\\\\?\\", 4);
+                       path_nchars = 4 + dret;
+                       /* Update pattern prefix */
+                       if (params->config != NULL)
+                       {
+                               params->config->_prefix = TSTRDUP(path);
+                               params->config->_prefix_num_tchars = path_nchars;
+                               if (params->config->_prefix == NULL)
+                               {
+                                       ret = WIMLIB_ERR_NOMEM;
+                                       goto out_free_path;
+                               }
+                               need_prefix_free = true;
+                       }
+               }
+       } else {
+               wmemcpy(path, root_disk_path, path_nchars + 1);
+       }
 
        memset(&state, 0, sizeof(state));
        ret = win32_build_dentry_tree_recursive(root_ret, path,
                                                path_nchars, params,
                                                &state, vol_flags);
+       if (need_prefix_free)
+               FREE(params->config->_prefix);
+out_free_path:
        FREE(path);
        if (ret == 0)
                win32_do_capture_warnings(&state, params->add_flags);