]> wimlib.net Git - wimlib/blobdiff - src/win32_capture.c
win32_capture_streams(): Handle not implemented/supported
[wimlib] / src / win32_capture.c
index 7f6df42e49393e65614ab512cfecdb1af17d0b25..e0fe2608e4452444fcc69ee17bef9a209ab3acc4 100644 (file)
 
 #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;
 };
 
 
@@ -60,14 +63,13 @@ read_win32_file_prefix(const struct wim_lookup_table_entry *lte,
 {
        int ret = 0;
        void *out_buf;
-       DWORD err;
        u64 bytes_remaining;
 
-       HANDLE hFile = win32_open_file_data_only(lte->file_on_disk);
+       HANDLE hFile = win32_open_existing_file(lte->file_on_disk,
+                                               FILE_READ_DATA);
        if (hFile == INVALID_HANDLE_VALUE) {
-               err = GetLastError();
-               ERROR("Failed to open \"%ls\"", lte->file_on_disk);
-               win32_error(err);
+               set_errno_from_GetLastError();
+               ERROR_WITH_ERRNO("Failed to open \"%ls\"", lte->file_on_disk);
                return WIMLIB_ERR_OPEN;
        }
 
@@ -84,9 +86,9 @@ read_win32_file_prefix(const struct wim_lookup_table_entry *lte,
                if (!ReadFile(hFile, out_buf, bytesToRead, &bytesRead, NULL) ||
                    bytesRead != bytesToRead)
                {
-                       err = GetLastError();
-                       ERROR("Failed to read data from \"%ls\"", lte->file_on_disk);
-                       win32_error(err);
+                       set_errno_from_GetLastError();
+                       ERROR_WITH_ERRNO("Failed to read data from \"%ls\"",
+                                        lte->file_on_disk);
                        ret = WIMLIB_ERR_READ;
                        break;
                }
@@ -154,9 +156,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;
 }
@@ -189,20 +192,20 @@ read_win32_encrypted_file_prefix(const struct wim_lookup_table_entry *lte,
        export_ctx.buf_filled = 0;
        export_ctx.bytes_remaining = size;
 
-       err = OpenEncryptedFileRawW(lte->file_on_disk, 0, &file_ctx);
+       err = OpenEncryptedFileRaw(lte->file_on_disk, 0, &file_ctx);
        if (err != ERROR_SUCCESS) {
-               ERROR("Failed to open encrypted file \"%ls\" for raw read",
-                     lte->file_on_disk);
-               win32_error(err);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Failed to open encrypted file \"%ls\" "
+                                "for raw read", lte->file_on_disk);
                ret = WIMLIB_ERR_OPEN;
                goto out_free_buf;
        }
        err = ReadEncryptedFileRaw(win32_encrypted_export_cb,
                                   &export_ctx, file_ctx);
        if (err != ERROR_SUCCESS) {
-               ERROR("Failed to read encrypted file \"%ls\"",
-                     lte->file_on_disk);
-               win32_error(err);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Failed to read encrypted file \"%ls\"",
+                                lte->file_on_disk);
                ret = export_ctx.wimlib_err_code;
                if (ret == 0)
                        ret = WIMLIB_ERR_READ;
@@ -228,18 +231,47 @@ FILETIME_to_u64(const FILETIME *ft)
        return ((u64)ft->dwHighDateTime << 32) | (u64)ft->dwLowDateTime;
 }
 
+/* Load the short name of a file into a WIM dentry.
+ *
+ * If we can't read the short filename for some reason, we just ignore the error
+ * and assume the file has no short name.  This shouldn't be an issue, since the
+ * short names are essentially obsolete anyway.
+ */
 static int
-win32_get_short_name(struct wim_dentry *dentry, const wchar_t *path)
+win32_get_short_name(HANDLE hFile, const wchar_t *path, struct wim_dentry *dentry)
 {
+
+       /* It's not any harder to just make the NtQueryInformationFile() system
+        * call ourselves, and it saves a dumb call to FindFirstFile() which of
+        * course has to create its own handle.  */
+#ifdef WITH_NTDLL
+       if (func_NtQueryInformationFile) {
+               NTSTATUS status;
+               IO_STATUS_BLOCK io_status;
+               u8 buf[128] _aligned_attribute(8);
+               const FILE_NAME_INFORMATION *info;
+
+               status = (*func_NtQueryInformationFile)(hFile, &io_status, buf, sizeof(buf),
+                                                       FileAlternateNameInformation);
+               info = (const FILE_NAME_INFORMATION*)buf;
+               if (status == STATUS_SUCCESS && info->FileNameLength != 0) {
+                       dentry->short_name = MALLOC(info->FileNameLength + 2);
+                       if (!dentry->short_name)
+                               return WIMLIB_ERR_NOMEM;
+                       memcpy(dentry->short_name, info->FileName,
+                              info->FileNameLength);
+                       dentry->short_name[info->FileNameLength / 2] = L'\0';
+                       dentry->short_name_nbytes = info->FileNameLength;
+               }
+               return 0;
+       }
+#endif
+
        WIN32_FIND_DATAW dat;
        HANDLE hFind;
        int ret = 0;
 
-       /* If we can't read the short filename for some reason, we just ignore
-        * the error and assume the file has no short name.  I don't think this
-        * should be an issue, since the short names are essentially obsolete
-        * anyway. */
-       hFind = FindFirstFileW(path, &dat);
+       hFind = FindFirstFile(path, &dat);
        if (hFind != INVALID_HANDLE_VALUE) {
                if (dat.cAlternateFileName[0] != L'\0') {
                        DEBUG("\"%ls\": short name \"%ls\"", path, dat.cAlternateFileName);
@@ -259,83 +291,133 @@ win32_get_short_name(struct wim_dentry *dentry, const wchar_t *path)
        return ret;
 }
 
+/*
+ * win32_query_security_descriptor() - Query a file's security descriptor
+ *
+ * We need the file's security descriptor in SECURITY_DESCRIPTOR_RELATIVE
+ * format, and we currently have a handle opened with as many relevant
+ * permissions as possible.  At this point, on Windows there are a number of
+ * options for reading a file's security descriptor:
+ *
+ * GetFileSecurity():  This takes in a path and returns the
+ * SECURITY_DESCRIPTOR_RELATIVE.  Problem: this uses an internal handle, not
+ * ours, and the handle created internally doesn't specify
+ * FILE_FLAG_BACKUP_SEMANTICS.  Therefore there can be access denied errors on
+ * some files and directories, even when running as the Administrator.
+ *
+ * GetSecurityInfo():  This takes in a handle and returns the security
+ * descriptor split into a bunch of different parts.  This should work, but it's
+ * dumb because we have to put the security descriptor back together again.
+ *
+ * BackupRead():  This can read the security descriptor, but this is a
+ * difficult-to-use API, probably only works as the Administrator, and the
+ * format of the returned data is not well documented.
+ *
+ * NtQuerySecurityObject():  This is exactly what we need, as it takes in a
+ * handle and returns the security descriptor in SECURITY_DESCRIPTOR_RELATIVE
+ * format.  Only problem is that it's a ntdll function and therefore not
+ * officially part of the Win32 API.  Oh well.
+ */
+static DWORD
+win32_query_security_descriptor(HANDLE hFile, const wchar_t *path,
+                               SECURITY_INFORMATION requestedInformation,
+                               SECURITY_DESCRIPTOR *buf,
+                               DWORD bufsize, DWORD *lengthNeeded)
+{
+#ifdef WITH_NTDLL
+       if (func_NtQuerySecurityObject) {
+               NTSTATUS status;
+
+               status = (*func_NtQuerySecurityObject)(hFile,
+                                                      requestedInformation, buf,
+                                                      bufsize, lengthNeeded);
+               /* Since it queries an already-open handle, NtQuerySecurityObject()
+                * apparently returns STATUS_ACCESS_DENIED rather than
+                * STATUS_PRIVILEGE_NOT_HELD.  */
+               if (status == STATUS_ACCESS_DENIED)
+                       return ERROR_PRIVILEGE_NOT_HELD;
+               else
+                       return (*func_RtlNtStatusToDosError)(status);
+       }
+#endif
+       if (GetFileSecurity(path, requestedInformation, buf,
+                           bufsize, lengthNeeded))
+               return ERROR_SUCCESS;
+       else
+               return GetLastError();
+}
+
 static int
-win32_get_security_descriptor(struct wim_dentry *dentry,
-                             struct wim_sd_set *sd_set,
+win32_get_security_descriptor(HANDLE hFile,
                              const wchar_t *path,
+                             struct wim_inode *inode,
+                             struct wim_sd_set *sd_set,
                              struct win32_capture_state *state,
                              int add_flags)
 {
        SECURITY_INFORMATION requestedInformation;
-       DWORD lenNeeded = 0;
-       BOOL status;
+       u8 _buf[4096];
+       u8 *buf;
+       size_t bufsize;
+       DWORD lenNeeded;
        DWORD err;
-       unsigned long n;
+       int ret;
 
        requestedInformation = DACL_SECURITY_INFORMATION |
                               SACL_SECURITY_INFORMATION |
                               OWNER_SECURITY_INFORMATION |
                               GROUP_SECURITY_INFORMATION;
-again:
-       /* Request length of security descriptor */
-       status = GetFileSecurityW(path, requestedInformation,
-                                 NULL, 0, &lenNeeded);
-       err = GetLastError();
-       if (!status && err == ERROR_INSUFFICIENT_BUFFER) {
-               DWORD len = lenNeeded;
-               char buf[len];
-               if (GetFileSecurityW(path, requestedInformation,
-                                    (PSECURITY_DESCRIPTOR)buf, len, &lenNeeded))
-               {
-                       int security_id = sd_set_add_sd(sd_set, buf, len);
-                       if (security_id < 0)
+       buf = _buf;
+       bufsize = sizeof(_buf);
+       for (;;) {
+               err = win32_query_security_descriptor(hFile, path,
+                                                     requestedInformation,
+                                                     (SECURITY_DESCRIPTOR*)buf,
+                                                     bufsize, &lenNeeded);
+               switch (err) {
+               case ERROR_SUCCESS:
+                       goto have_descriptor;
+               case ERROR_INSUFFICIENT_BUFFER:
+                       wimlib_assert(buf == _buf);
+                       buf = MALLOC(lenNeeded);
+                       if (!buf)
                                return WIMLIB_ERR_NOMEM;
-                       else {
-                               dentry->d_inode->i_security_id = security_id;
-                               return 0;
+                       bufsize = lenNeeded;
+                       break;
+               case ERROR_PRIVILEGE_NOT_HELD:
+                       if (add_flags & WIMLIB_ADD_FLAG_STRICT_ACLS)
+                               goto fail;
+                       if (requestedInformation & SACL_SECURITY_INFORMATION) {
+                               state->num_get_sacl_priv_notheld++;
+                               requestedInformation &= ~SACL_SECURITY_INFORMATION;
+                               break;
                        }
-               } else {
-                       err = GetLastError();
+                       /* Fall through */
+               case ERROR_ACCESS_DENIED:
+                       if (add_flags & WIMLIB_ADD_FLAG_STRICT_ACLS)
+                               goto fail;
+                       state->num_get_sd_access_denied++;
+                       ret = 0;
+                       goto out_free_buf;
+               default:
+               fail:
+                       set_errno_from_win32_error(err);
+                       ERROR("Failed to read security descriptor of \"%ls\"", path);
+                       ret = WIMLIB_ERR_READ;
+                       goto out_free_buf;
                }
        }
 
-       if (add_flags & WIMLIB_ADD_FLAG_STRICT_ACLS)
-               goto fail;
-
-       switch (err) {
-       case ERROR_PRIVILEGE_NOT_HELD:
-               if (requestedInformation & SACL_SECURITY_INFORMATION) {
-                       n = state->num_get_sacl_priv_notheld++;
-                       requestedInformation &= ~SACL_SECURITY_INFORMATION;
-                       if (n < MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) {
-                               WARNING(
-"We don't have enough privileges to read the full security\n"
-"          descriptor of \"%ls\"!\n"
-"          Re-trying with SACL omitted.\n", path);
-                       } else if (n == MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) {
-                               WARNING(
-"Suppressing further privileges not held error messages when reading\n"
-"          security descriptors.");
-                       }
-                       goto again;
-               }
-               /* Fall through */
-       case ERROR_ACCESS_DENIED:
-               n = state->num_get_sd_access_denied++;
-               if (n < MAX_GET_SD_ACCESS_DENIED_WARNINGS) {
-                       WARNING("Failed to read security descriptor of \"%ls\": "
-                               "Access denied!\n%ls", path, capture_access_denied_msg);
-               } else if (n == MAX_GET_SD_ACCESS_DENIED_WARNINGS) {
-                       WARNING("Suppressing further access denied errors messages i"
-                               "when reading security descriptors");
-               }
-               return 0;
-       default:
-fail:
-               ERROR("Failed to read security descriptor of \"%ls\"", path);
-               win32_error(err);
-               return WIMLIB_ERR_READ;
-       }
+have_descriptor:
+       inode->i_security_id = sd_set_add_sd(sd_set, buf, lenNeeded);
+       if (inode->i_security_id < 0)
+               ret = WIMLIB_ERR_NOMEM;
+       else
+               ret = 0;
+out_free_buf:
+       if (buf != _buf)
+               FREE(buf);
+       return ret;
 }
 
 static int
@@ -346,31 +428,109 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
                                  struct win32_capture_state *state,
                                  unsigned vol_flags);
 
-/* Reads the directory entries of directory using a Win32 API and recursively
- * calls win32_build_dentry_tree() on them. */
+/* Reads the directory entries of directory and recursively calls
+ * win32_build_dentry_tree() on them.  */
 static int
-win32_recurse_directory(struct wim_dentry *root,
+win32_recurse_directory(HANDLE hDir,
                        wchar_t *dir_path,
                        size_t dir_path_num_chars,
+                       struct wim_dentry *root,
                        struct add_image_params *params,
                        struct win32_capture_state *state,
                        unsigned vol_flags)
 {
-       WIN32_FIND_DATAW dat;
-       HANDLE hFind;
-       DWORD err;
        int ret;
 
        DEBUG("Recurse to directory \"%ls\"", dir_path);
 
+       /* Using NtQueryDirectoryFile() we can re-use the same open handle,
+        * which we opened with FILE_FLAG_BACKUP_SEMANTICS (probably not the
+        * case for the FindFirstFile() API; it's not documented).  */
+#ifdef WITH_NTDLL
+       if (func_NtQueryDirectoryFile) {
+               NTSTATUS status;
+               IO_STATUS_BLOCK io_status;
+               const size_t bufsize = 8192;
+               u8 *buf;
+               BOOL restartScan = TRUE;
+               const FILE_NAMES_INFORMATION *info;
+
+               buf = MALLOC(bufsize);
+               if (!buf)
+                       return WIMLIB_ERR_NOMEM;
+               for (;;) {
+                       status = (*func_NtQueryDirectoryFile)(hDir, NULL, NULL, NULL,
+                                                             &io_status, buf, bufsize,
+                                                             FileNamesInformation,
+                                                             FALSE, NULL, restartScan);
+                       restartScan = FALSE;
+                       if (status != STATUS_SUCCESS) {
+                               if (status == STATUS_NO_MORE_FILES ||
+                                   status == STATUS_NO_MORE_ENTRIES ||
+                                   status == STATUS_NO_MORE_MATCHES) {
+                                       ret = 0;
+                               } else {
+                                       set_errno_from_nt_status(status);
+                                       ERROR_WITH_ERRNO("Failed to read directory "
+                                                        "\"%ls\"", dir_path);
+                                       ret = WIMLIB_ERR_READ;
+                               }
+                               goto out_free_buf;
+                       }
+                       wimlib_assert(io_status.Information != 0);
+                       info = (const FILE_NAMES_INFORMATION*)buf;
+                       for (;;) {
+                               if (!(info->FileNameLength == 2 && info->FileName[0] == L'.') &&
+                                   !(info->FileNameLength == 4 && info->FileName[0] == L'.' &&
+                                                                  info->FileName[1] == L'.'))
+                               {
+                                       wchar_t *p;
+                                       struct wim_dentry *child;
+
+                                       p = dir_path + dir_path_num_chars;
+                                       *p++ = L'\\';
+                                       p = wmempcpy(p, info->FileName,
+                                                    info->FileNameLength / 2);
+                                       *p = '\0';
+
+                                       ret = win32_build_dentry_tree_recursive(
+                                                                       &child,
+                                                                       dir_path,
+                                                                       p - dir_path,
+                                                                       params,
+                                                                       state,
+                                                                       vol_flags);
+
+                                       dir_path[dir_path_num_chars] = L'\0';
+
+                                       if (ret)
+                                               goto out_free_buf;
+                                       if (child)
+                                               dentry_add_child(root, child);
+                               }
+                               if (info->NextEntryOffset == 0)
+                                       break;
+                               info = (const FILE_NAMES_INFORMATION*)
+                                               ((const u8*)info + info->NextEntryOffset);
+                       }
+               }
+       out_free_buf:
+               FREE(buf);
+               return ret;
+       }
+#endif
+       WIN32_FIND_DATAW dat;
+       HANDLE hFind;
+       DWORD err;
+
        /* Begin reading the directory by calling FindFirstFileW.  Unlike UNIX
         * 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);
+       hFind = FindFirstFile(dir_path, &dat);
        dir_path[dir_path_num_chars] = L'\0';
 
        if (hFind == INVALID_HANDLE_VALUE) {
@@ -378,8 +538,9 @@ win32_recurse_directory(struct wim_dentry *root,
                if (err == ERROR_FILE_NOT_FOUND) {
                        return 0;
                } else {
-                       ERROR("Failed to read directory \"%ls\"", dir_path);
-                       win32_error(err);
+                       set_errno_from_win32_error(err);
+                       ERROR_WITH_ERRNO("Failed to read directory \"%ls\"",
+                                        dir_path);
                        return WIMLIB_ERR_READ;
                }
        }
@@ -393,7 +554,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);
@@ -411,11 +572,11 @@ win32_recurse_directory(struct wim_dentry *root,
                        goto out_find_close;
                if (child)
                        dentry_add_child(root, child);
-       } while (FindNextFileW(hFind, &dat));
+       } while (FindNextFile(hFind, &dat));
        err = GetLastError();
        if (err != ERROR_NO_MORE_FILES) {
-               ERROR("Failed to read directory \"%ls\"", dir_path);
-               win32_error(err);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Failed to read directory \"%ls\"", dir_path);
                if (ret == 0)
                        ret = WIMLIB_ERR_READ;
        }
@@ -506,12 +667,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 +678,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 +694,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
@@ -601,9 +760,8 @@ win32_get_reparse_data(HANDLE hFile, const wchar_t *path,
                             &bytesReturned,
                             NULL))
        {
-               DWORD err = GetLastError();
-               ERROR("Failed to get reparse data of \"%ls\"", path);
-               win32_error(err);
+               set_errno_from_GetLastError();
+               ERROR_WITH_ERRNO("Failed to get reparse data of \"%ls\"", path);
                return -WIMLIB_ERR_READ;
        }
        if (bytesReturned < 8 || bytesReturned > REPARSE_POINT_MAX_SIZE) {
@@ -612,7 +770,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))
@@ -631,10 +789,10 @@ win32_get_reparse_data(HANDLE hFile, const wchar_t *path,
 }
 
 static DWORD WINAPI
-win32_tally_encrypted_size_cb(unsigned char *_data, void *_ctx,
+win32_tally_encrypted_size_cb(unsigned char *_data, void *_size_ret,
                              unsigned long len)
 {
-       *(u64*)_ctx += len;
+       *(u64*)_size_ret += len;
        return ERROR_SUCCESS;
 }
 
@@ -645,18 +803,20 @@ win32_get_encrypted_file_size(const wchar_t *path, u64 *size_ret)
        void *file_ctx;
        int ret;
 
-       *size_ret = 0;
-       err = OpenEncryptedFileRawW(path, 0, &file_ctx);
+       err = OpenEncryptedFileRaw(path, 0, &file_ctx);
        if (err != ERROR_SUCCESS) {
-               ERROR("Failed to open encrypted file \"%ls\" for raw read", path);
-               win32_error(err);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Failed to open encrypted file \"%ls\" "
+                                "for raw read", path);
                return WIMLIB_ERR_OPEN;
        }
+       *size_ret = 0;
        err = ReadEncryptedFileRaw(win32_tally_encrypted_size_cb,
                                   size_ret, file_ctx);
        if (err != ERROR_SUCCESS) {
-               ERROR("Failed to read raw encrypted data from \"%ls\"", path);
-               win32_error(err);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Failed to read raw encrypted data from "
+                                "\"%ls\"", path);
                ret = WIMLIB_ERR_READ;
        } else {
                ret = 0;
@@ -755,19 +915,18 @@ 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;
                }
        }
 
        spath_buf_nbytes = (spath_nchars + 1) * sizeof(wchar_t);
        spath = MALLOC(spath_buf_nbytes);
 
-       swprintf(spath, L"%ls%ls%ls%ls",
+       tsprintf(spath, L"%ls%ls%ls%ls",
                 relpath_prefix, path, colonchar, stream_name);
 
        /* Make a new wim_lookup_table_entry */
@@ -786,7 +945,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 +969,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.
- *
- * @inode:              WIM inode to save the stream into.
- *
- * @lookup_table:       Stream lookup table for the WIM.
+/* Load information about the streams of an open file into a WIM inode.
  *
- * @file_size:         Size of unnamed data stream.  (Used only if alternate
- *                      data streams API appears to be unavailable.)
+ * By default, we use the NtQueryInformationFile() system call instead of
+ * FindFirstStream() and FindNextStream().  This is done for two reasons:
  *
- * @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_p,
+                     const wchar_t *path,
                      size_t path_num_chars,
                      struct wim_inode *inode,
                      struct wim_lookup_table *lookup_table,
@@ -839,19 +993,116 @@ 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;
+#endif
        HANDLE hFind;
        DWORD err;
 
        DEBUG("Capturing streams from \"%ls\"", path);
 
-       if (win32func_FindFirstStreamW == NULL ||
-           !(vol_flags & FILE_NAMED_STREAMS))
+       if (!(vol_flags & FILE_NAMED_STREAMS))
                goto unnamed_only;
 
+#ifdef WITH_NTDLL
+       if (!func_NtQueryInformationFile)
+               goto use_FindFirstStream;
+
+       buf = _buf;
+       bufsize = sizeof(_buf);
+
+       /* Get a buffer containing the stream information.  */
+       for (;;) {
+               status = (*func_NtQueryInformationFile)(*hFile_p, &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 if (status == STATUS_NOT_IMPLEMENTED ||
+                          status == STATUS_NOT_SUPPORTED ||
+                          status == STATUS_INVALID_INFO_CLASS) {
+                       goto use_FindFirstStream;
+               } else {
+                       set_errno_from_nt_status(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;
+       }
+
+       if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) {
+               /* OpenEncryptedFileRaw() seems to fail with
+                * ERROR_SHARING_VIOLATION if there are any handles opened to
+                * the file.  */
+               CloseHandle(*hFile_p);
+               *hFile_p = INVALID_HANDLE_VALUE;
+       }
+
+       /* 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;
+#endif /* WITH_NTDLL */
+
+use_FindFirstStream:
+       if (win32func_FindFirstStreamW == NULL)
+               goto unnamed_only;
        hFind = win32func_FindFirstStreamW(path, FindStreamInfoStandard, &dat, 0);
        if (hFind == INVALID_HANDLE_VALUE) {
                err = GetLastError();
-               if (err == ERROR_CALL_NOT_IMPLEMENTED)
+               if (err == ERROR_CALL_NOT_IMPLEMENTED ||
+                   err == ERROR_NOT_SUPPORTED ||
+                   err == ERROR_INVALID_FUNCTION ||
+                   err == ERROR_INVALID_PARAMETER)
                        goto unnamed_only;
 
                /* Seems legal for this to return ERROR_HANDLE_EOF on reparse
@@ -869,9 +1120,9 @@ win32_capture_streams(const wchar_t *path,
                                        path, capture_access_denied_msg);
                                return 0;
                        } else {
-                               ERROR("Failed to look up data streams "
-                                     "of \"%ls\"", path);
-                               win32_error(err);
+                               set_errno_from_win32_error(err);
+                               ERROR_WITH_ERRNO("Failed to look up data streams "
+                                                "of \"%ls\"", path);
                                return WIMLIB_ERR_READ;
                        }
                }
@@ -886,33 +1137,27 @@ win32_capture_streams(const wchar_t *path,
        } while (win32func_FindNextStreamW(hFind, &dat));
        err = GetLastError();
        if (err != ERROR_HANDLE_EOF) {
-               ERROR("Win32 API: Error reading data streams from \"%ls\"", path);
-               win32_error(err);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Error reading data streams from "
+                                "\"%ls\"", path);
                ret = WIMLIB_ERR_READ;
        }
 out_find_close:
        FindClose(hFind);
        return ret;
+
 unnamed_only:
-       /* FindFirstStreamW() API is not available, or the volume does not
+       /* FindFirstStream() 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))
-       {
-               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);
-       }
-       return ret;
+       if (inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
+                                  FILE_ATTRIBUTE_REPARSE_POINT))
+               return 0;
+
+       wcscpy(dat.cStreamName, L"::$DATA");
+       dat.StreamSize.QuadPart = file_size;
+       return win32_capture_stream(path, path_num_chars,
+                                   inode, lookup_table, &dat);
 }
 
 static int
@@ -931,6 +1176,10 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
        u8 *rpbuf;
        u16 rpbuflen;
        u16 not_rpfixed;
+       HANDLE hFile;
+       DWORD desiredAccess;
+
+       params->progress.scan.cur_path = path;
 
        if (exclude_path(path, path_num_chars, params->config, true)) {
                if (params->add_flags & WIMLIB_ADD_FLAG_ROOT) {
@@ -938,43 +1187,52 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
                        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
 
-       HANDLE hFile = win32_open_existing_file(path,
-                                               FILE_READ_DATA | FILE_READ_ATTRIBUTES);
+       do_capture_progress(params, WIMLIB_SCAN_DENTRY_OK);
+
+       desiredAccess = FILE_READ_DATA | FILE_READ_ATTRIBUTES |
+                       READ_CONTROL | ACCESS_SYSTEM_SECURITY;
+again:
+       hFile = win32_open_existing_file(path, desiredAccess);
        if (hFile == INVALID_HANDLE_VALUE) {
                err = GetLastError();
-               ERROR("Win32 API: Failed to open \"%ls\"", path);
-               win32_error(err);
+               if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) {
+                       if (desiredAccess & ACCESS_SYSTEM_SECURITY) {
+                               desiredAccess &= ~ACCESS_SYSTEM_SECURITY;
+                               goto again;
+                       }
+                       if (desiredAccess & READ_CONTROL) {
+                               desiredAccess &= ~READ_CONTROL;
+                               goto again;
+                       }
+               }
+               set_errno_from_GetLastError();
+               ERROR_WITH_ERRNO("Failed to open \"%ls\" for reading", path);
                ret = WIMLIB_ERR_OPEN;
                goto out;
        }
 
        BY_HANDLE_FILE_INFORMATION file_info;
        if (!GetFileInformationByHandle(hFile, &file_info)) {
-               err = GetLastError();
-               ERROR("Win32 API: Failed to get file information for \"%ls\"",
-                     path);
-               win32_error(err);
+               set_errno_from_GetLastError();
+               ERROR_WITH_ERRNO("Failed to get file information for \"%ls\"",
+                                path);
                ret = WIMLIB_ERR_STAT;
                goto out_close_handle;
        }
@@ -1014,7 +1272,7 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
        if (ret)
                goto out_close_handle;
 
-       ret = win32_get_short_name(root, path);
+       ret = win32_get_short_name(hFile, path, root);
        if (ret)
                goto out_close_handle;
 
@@ -1034,8 +1292,8 @@ 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,
-                                                   path, state,
+               ret = win32_get_security_descriptor(hFile, path, inode,
+                                                   &params->sd_set, state,
                                                    params->add_flags);
                if (ret)
                        goto out_close_handle;
@@ -1044,36 +1302,49 @@ 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;
 
        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) {
                /* Directory (not a reparse point) --- recurse to children */
-               ret = win32_recurse_directory(root,
+
+               if (hFile == INVALID_HANDLE_VALUE) {
+                       /* Re-open handle that was closed to read raw encrypted
+                        * data.  */
+                       hFile = win32_open_existing_file(path, FILE_READ_DATA);
+                       if (hFile == INVALID_HANDLE_VALUE) {
+                               set_errno_from_GetLastError();
+                               ERROR_WITH_ERRNO("Failed to reopen \"%ls\"",
+                                                path);
+                               ret = WIMLIB_ERR_OPEN;
+                               goto out_close_handle;
+                       }
+               }
+               ret = win32_recurse_directory(hFile,
                                              path,
                                              path_num_chars,
+                                             root,
                                              params,
                                              state,
                                              vol_flags);
        }
-       goto out;
 out_close_handle:
        CloseHandle(hFile);
 out:
@@ -1085,36 +1356,31 @@ out:
 }
 
 static void
-win32_do_capture_warnings(const struct win32_capture_state *state,
+win32_do_capture_warnings(const wchar_t *path,
+                         const struct win32_capture_state *state,
                          int add_flags)
 {
        if (state->num_get_sacl_priv_notheld == 0 &&
            state->num_get_sd_access_denied == 0)
                return;
 
-       WARNING("");
-       WARNING("Built dentry tree successfully, but with the following problem(s):");
+       WARNING("Scan of \"%ls\" complete, but with one or more warnings:", path);
        if (state->num_get_sacl_priv_notheld != 0) {
-               WARNING("Could not capture SACL (System Access Control List)\n"
-                       "          on %lu files or directories.",
+               WARNING("Could not capture SACL (System Access Control List)\n"
+                       "            on %lu files or directories.",
                        state->num_get_sacl_priv_notheld);
        }
        if (state->num_get_sd_access_denied != 0) {
-               WARNING("Could not capture security descriptor at all\n"
-                       "          on %lu files or directories.",
+               WARNING("Could not capture security descriptor at all\n"
+                       "            on %lu files or directories.",
                        state->num_get_sd_access_denied);
        }
-       WARNING(
-          "Try running the program as the Administrator to make sure all the\n"
-"          desired metadata has been captured exactly.  However, if you\n"
-"          do not care about capturing security descriptors correctly, then\n"
-"          nothing more needs to be done%ls\n",
-       (add_flags & WIMLIB_ADD_FLAG_NO_ACLS) ? L"." :
-         L", although you might consider\n"
-"          using the --no-acls option to explicitly capture no security\n"
-"          descriptors.\n");
+       WARNING("To fully capture all security descriptors, run the program\n"
+               "          with Administrator rights.");
 }
 
+#define WINDOWS_NT_MAX_PATH 32768
+
 /* Win32 version of capturing a directory tree */
 int
 win32_build_dentry_tree(struct wim_dentry **root_ret,
@@ -1126,49 +1392,81 @@ 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;
 
-       if (!win32func_FindFirstStreamW) {
+       if (!win32func_FindFirstStreamW
+#ifdef WITH_NTDLL
+           && !func_NtQueryInformationFile
+#endif
+          )
+       {
                WARNING("Running on Windows XP or earlier; "
                        "alternate data streams will not be captured.");
        }
 
        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 &&
-           GetLastError() == ERROR_FILE_NOT_FOUND)
-       {
-               ERROR("Capture directory \"%ls\" does not exist!",
-                     root_disk_path);
-               return WIMLIB_ERR_OPENDIR;
-       }
-
        ret = win32_get_file_and_vol_ids(root_disk_path,
                                         &params->capture_root_ino,
                                         &params->capture_root_dev);
-       if (ret)
+       if (ret) {
+               ERROR_WITH_ERRNO("Can't open %ls", root_disk_path);
                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);
+               win32_do_capture_warnings(root_disk_path, &state, params->add_flags);
        return ret;
 }