]> wimlib.net Git - wimlib/blobdiff - src/win32_capture.c
Win32 capture: Query stream information with native API
[wimlib] / src / win32_capture.c
index e2d98078702f840b68ff8a14848999ad8309f43e..0957d54cb3c8e1ec1d6910b0be4a6c81e94cc393 100644 (file)
 
 #ifdef __WIN32__
 
-#include "win32_common.h"
-#include "wimlib_internal.h"
-#include "lookup_table.h"
-#include "security.h"
-#include "endianness.h"
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include "wimlib/win32_common.h"
+
+#include "wimlib/capture.h"
+#include "wimlib/endianness.h"
+#include "wimlib/error.h"
+#include "wimlib/lookup_table.h"
+#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;
 };
 
 
@@ -147,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;
 }
@@ -254,7 +270,7 @@ win32_get_short_name(struct wim_dentry *dentry, const wchar_t *path)
 
 static int
 win32_get_security_descriptor(struct wim_dentry *dentry,
-                             struct sd_set *sd_set,
+                             struct wim_sd_set *sd_set,
                              const wchar_t *path,
                              struct win32_capture_state *state,
                              int add_flags)
@@ -360,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);
@@ -386,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);
@@ -417,40 +433,6 @@ out_find_close:
        return ret;
 }
 
-int
-win32_get_file_and_vol_ids(const wchar_t *path, u64 *ino_ret, u64 *dev_ret)
-{
-       HANDLE hFile;
-       DWORD err;
-       BY_HANDLE_FILE_INFORMATION file_info;
-       int ret;
-
-       hFile = win32_open_existing_file(path, FILE_READ_ATTRIBUTES);
-       if (hFile == INVALID_HANDLE_VALUE) {
-               err = GetLastError();
-               if (err != ERROR_FILE_NOT_FOUND) {
-                       WARNING("Failed to open \"%ls\" to get file "
-                               "and volume IDs", path);
-                       win32_error(err);
-               }
-               return WIMLIB_ERR_OPEN;
-       }
-
-       if (!GetFileInformationByHandle(hFile, &file_info)) {
-               err = GetLastError();
-               ERROR("Failed to get file information for \"%ls\"", path);
-               win32_error(err);
-               ret = WIMLIB_ERR_STAT;
-       } else {
-               *ino_ret = ((u64)file_info.nFileIndexHigh << 32) |
-                           (u64)file_info.nFileIndexLow;
-               *dev_ret = file_info.dwVolumeSerialNumber;
-               ret = 0;
-       }
-       CloseHandle(hFile);
-       return ret;
-}
-
 /* Reparse point fixup status code */
 enum rp_status {
        /* Reparse point corresponded to an absolute symbolic link or junction
@@ -533,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;
 
@@ -546,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];
@@ -562,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
@@ -639,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))
@@ -782,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;
                }
        }
 
@@ -813,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;
        }
 
@@ -837,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).
- *
- * @path_num_chars:     Number of 2-byte characters in @path.
+/* Load information about the streams of an open file into a WIM inode.
  *
- * @inode:              WIM inode to save the stream into.
- *
- * @lookup_table:       Stream lookup table for the WIM.
+ * By default, we use the NtQueryInformationFile() system call instead of
+ * FindFirstStream() and FindNextStream().  This is done for two reasons:
  *
- * @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,
@@ -866,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();
@@ -920,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;
 }
@@ -959,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);
@@ -1030,7 +1083,7 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
         * only 1 link and refuse to hard link them.  This is because Windows
         * has a bug where it can return duplicate File IDs for files and
         * directories on the FAT filesystem. */
-       ret = inode_table_new_dentry(params->inode_table,
+       ret = inode_table_new_dentry(&params->inode_table,
                                     path_basename_with_len(path, path_num_chars),
                                     ((u64)file_info.nFileIndexHigh << 32) |
                                         (u64)file_info.nFileIndexLow,
@@ -1061,7 +1114,7 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
        if (!(params->add_flags & WIMLIB_ADD_FLAG_NO_ACLS)
            && (vol_flags & FILE_PERSISTENT_ACLS))
        {
-               ret = win32_get_security_descriptor(root, params->sd_set,
+               ret = win32_get_security_descriptor(root, &params->sd_set,
                                                    path, state,
                                                    params->add_flags);
                if (ret)
@@ -1071,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) {
@@ -1142,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,
@@ -1153,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 &&
@@ -1177,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);