]> wimlib.net Git - wimlib/blobdiff - src/win32_capture.c
unmount_timed_out_cb(): Do not check errno if kill() succeeds
[wimlib] / src / win32_capture.c
index 14a6c134833e0b47860c60e75321560ea60258ba..6f29828c842a95d70e7aea9a0df66cd7a06a3375 100644 (file)
 #include "wimlib/paths.h"
 #include "wimlib/reparse.h"
 
-#ifdef WITH_NTDLL
-#  include <winternl.h>
-#  include <ntstatus.h>
-
-NTSTATUS WINAPI
-NtQuerySecurityObject(HANDLE handle,
-                     SECURITY_INFORMATION SecurityInformation,
-                     PSECURITY_DESCRIPTOR SecurityDescriptor,
-                     ULONG Length,
-                     PULONG LengthNeeded);
-NTSTATUS WINAPI
-NtQueryDirectoryFile(HANDLE FileHandle,
-                    HANDLE Event,
-                    PIO_APC_ROUTINE ApcRoutine,
-                    PVOID ApcContext,
-                    PIO_STATUS_BLOCK IoStatusBlock,
-                    PVOID FileInformation,
-                    ULONG Length,
-                    FILE_INFORMATION_CLASS FileInformationClass,
-                    BOOLEAN ReturnSingleEntry,
-                    PUNICODE_STRING FileName,
-                    BOOLEAN RestartScan);
-#endif
-
 #define MAX_GET_SD_ACCESS_DENIED_WARNINGS 1
 #define MAX_GET_SACL_PRIV_NOTHELD_WARNINGS 1
 #define MAX_CAPTURE_LONG_PATH_WARNINGS 5
@@ -82,12 +58,11 @@ int
 read_win32_file_prefix(const struct wim_lookup_table_entry *lte,
                       u64 size,
                       consume_data_callback_t cb,
-                      void *ctx_or_buf,
-                      int _ignored_flags)
+                      void *cb_ctx)
 {
        int ret = 0;
-       void *out_buf;
        u64 bytes_remaining;
+       u8 buf[BUFFER_SIZE];
 
        HANDLE hFile = win32_open_existing_file(lte->file_on_disk,
                                                FILE_READ_DATA);
@@ -97,17 +72,12 @@ read_win32_file_prefix(const struct wim_lookup_table_entry *lte,
                return WIMLIB_ERR_OPEN;
        }
 
-       if (cb)
-               out_buf = alloca(WIM_CHUNK_SIZE);
-       else
-               out_buf = ctx_or_buf;
-
        bytes_remaining = size;
        while (bytes_remaining) {
                DWORD bytesToRead, bytesRead;
 
-               bytesToRead = min(WIM_CHUNK_SIZE, bytes_remaining);
-               if (!ReadFile(hFile, out_buf, bytesToRead, &bytesRead, NULL) ||
+               bytesToRead = min(sizeof(buf), bytes_remaining);
+               if (!ReadFile(hFile, buf, bytesToRead, &bytesRead, NULL) ||
                    bytesRead != bytesToRead)
                {
                        set_errno_from_GetLastError();
@@ -117,74 +87,40 @@ read_win32_file_prefix(const struct wim_lookup_table_entry *lte,
                        break;
                }
                bytes_remaining -= bytesRead;
-               if (cb) {
-                       ret = (*cb)(out_buf, bytesRead, ctx_or_buf);
-                       if (ret)
-                               break;
-               } else {
-                       out_buf += bytesRead;
-               }
+               ret = (*cb)(buf, bytesRead, cb_ctx);
+               if (ret)
+                       break;
        }
+out_close_handle:
        CloseHandle(hFile);
        return ret;
 }
 
 struct win32_encrypted_read_ctx {
        consume_data_callback_t read_prefix_cb;
-       void *read_prefix_ctx_or_buf;
+       void *read_prefix_ctx;
        int wimlib_err_code;
-       void *buf;
-       size_t buf_filled;
        u64 bytes_remaining;
 };
 
 static DWORD WINAPI
-win32_encrypted_export_cb(unsigned char *_data, void *_ctx, unsigned long len)
+win32_encrypted_export_cb(unsigned char *data, void *_ctx, unsigned long len)
 {
-       const void *data = _data;
        struct win32_encrypted_read_ctx *ctx = _ctx;
        int ret;
+       size_t bytes_to_consume = min(len, ctx->bytes_remaining);
 
-       DEBUG("len = %lu", len);
-       if (ctx->read_prefix_cb) {
-               /* The length of the buffer passed to the ReadEncryptedFileRaw()
-                * export callback is undocumented, so we assume it may be of
-                * arbitrary size. */
-               size_t bytes_to_buffer = min(ctx->bytes_remaining - ctx->buf_filled,
-                                            len);
-               while (bytes_to_buffer) {
-                       size_t bytes_to_copy_to_buf =
-                               min(bytes_to_buffer, WIM_CHUNK_SIZE - ctx->buf_filled);
-
-                       memcpy(ctx->buf + ctx->buf_filled, data,
-                              bytes_to_copy_to_buf);
-                       ctx->buf_filled += bytes_to_copy_to_buf;
-                       data += bytes_to_copy_to_buf;
-                       bytes_to_buffer -= bytes_to_copy_to_buf;
-
-                       if (ctx->buf_filled == WIM_CHUNK_SIZE ||
-                           ctx->buf_filled == ctx->bytes_remaining)
-                       {
-                               ret = (*ctx->read_prefix_cb)(ctx->buf,
-                                                            ctx->buf_filled,
-                                                            ctx->read_prefix_ctx_or_buf);
-                               if (ret) {
-                                       ctx->wimlib_err_code = ret;
-                                       /* Shouldn't matter what error code is returned
-                                        * here, as long as it isn't ERROR_SUCCESS. */
-                                       return ERROR_READ_FAULT;
-                               }
-                               ctx->bytes_remaining -= ctx->buf_filled;
-                               ctx->buf_filled = 0;
-                       }
-               }
-       } else {
-               size_t len_to_copy = min(len, ctx->bytes_remaining);
-               ctx->read_prefix_ctx_or_buf = mempcpy(ctx->read_prefix_ctx_or_buf,
-                                                     data,
-                                                     len_to_copy);
-               ctx->bytes_remaining -= len_to_copy;
+       if (bytes_to_consume == 0)
+               return ERROR_SUCCESS;
+
+       ret = (*ctx->read_prefix_cb)(data, bytes_to_consume, ctx->read_prefix_ctx);
+       if (ret) {
+               ctx->wimlib_err_code = ret;
+               /* Shouldn't matter what error code is returned here, as long as
+                * it isn't ERROR_SUCCESS.  */
+               return ERROR_READ_FAULT;
        }
+       ctx->bytes_remaining -= bytes_to_consume;
        return ERROR_SUCCESS;
 }
 
@@ -192,8 +128,7 @@ int
 read_win32_encrypted_file_prefix(const struct wim_lookup_table_entry *lte,
                                 u64 size,
                                 consume_data_callback_t cb,
-                                void *ctx_or_buf,
-                                int _ignored_flags)
+                                void *cb_ctx)
 {
        struct win32_encrypted_read_ctx export_ctx;
        DWORD err;
@@ -204,16 +139,8 @@ read_win32_encrypted_file_prefix(const struct wim_lookup_table_entry *lte,
              size, lte->file_on_disk);
 
        export_ctx.read_prefix_cb = cb;
-       export_ctx.read_prefix_ctx_or_buf = ctx_or_buf;
+       export_ctx.read_prefix_ctx = cb_ctx;
        export_ctx.wimlib_err_code = 0;
-       if (cb) {
-               export_ctx.buf = MALLOC(WIM_CHUNK_SIZE);
-               if (!export_ctx.buf)
-                       return WIMLIB_ERR_NOMEM;
-       } else {
-               export_ctx.buf = NULL;
-       }
-       export_ctx.buf_filled = 0;
        export_ctx.bytes_remaining = size;
 
        err = OpenEncryptedFileRaw(lte->file_on_disk, 0, &file_ctx);
@@ -221,8 +148,7 @@ read_win32_encrypted_file_prefix(const struct wim_lookup_table_entry *lte,
                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;
+               return WIMLIB_ERR_OPEN;
        }
        err = ReadEncryptedFileRaw(win32_encrypted_export_cb,
                                   &export_ctx, file_ctx);
@@ -243,8 +169,6 @@ read_win32_encrypted_file_prefix(const struct wim_lookup_table_entry *lte,
                ret = 0;
        }
        CloseEncryptedFileRaw(file_ctx);
-out_free_buf:
-       FREE(export_ctx.buf);
        return ret;
 }
 
@@ -269,25 +193,28 @@ win32_get_short_name(HANDLE hFile, const wchar_t *path, struct wim_dentry *dentr
         * call ourselves, and it saves a dumb call to FindFirstFile() which of
         * course has to create its own handle.  */
 #ifdef WITH_NTDLL
-       NTSTATUS status;
-       IO_STATUS_BLOCK io_status;
-       u8 buf[128] _aligned_attribute(8);
-       const FILE_NAME_INFORMATION *info;
-
-       status = 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;
+       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;
        }
-       return 0;
-#else
+#endif
+
        WIN32_FIND_DATAW dat;
        HANDLE hFind;
        int ret = 0;
@@ -310,7 +237,6 @@ win32_get_short_name(HANDLE hFile, const wchar_t *path, struct wim_dentry *dentr
                FindClose(hFind);
        }
        return ret;
-#endif
 }
 
 /*
@@ -347,24 +273,26 @@ win32_query_security_descriptor(HANDLE hFile, const wchar_t *path,
                                DWORD bufsize, DWORD *lengthNeeded)
 {
 #ifdef WITH_NTDLL
-       NTSTATUS status;
-
-       status = 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 RtlNtStatusToDosError(status);
-#else
+       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();
-#endif
 }
 
 static int
@@ -422,7 +350,7 @@ win32_get_security_descriptor(HANDLE hFile,
                default:
                fail:
                        set_errno_from_win32_error(err);
-                       ERROR("Failed to read security descriptor of \"%ls\"", path);
+                       ERROR_WITH_ERRNO("Failed to read security descriptor of \"%ls\"", path);
                        ret = WIMLIB_ERR_READ;
                        goto out_free_buf;
                }
@@ -467,6 +395,9 @@ win32_recurse_directory(HANDLE hDir,
         * 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)
+               goto use_FindFirstFile;
+
        NTSTATUS status;
        IO_STATUS_BLOCK io_status;
        const size_t bufsize = 8192;
@@ -478,16 +409,21 @@ win32_recurse_directory(HANDLE hDir,
        if (!buf)
                return WIMLIB_ERR_NOMEM;
        for (;;) {
-               status = NtQueryDirectoryFile(hDir, NULL, NULL, NULL,
-                                             &io_status, buf, bufsize,
-                                             FileNamesInformation,
-                                             FALSE, NULL, restartScan);
+               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 if (status == STATUS_NOT_IMPLEMENTED ||
+                                  status == STATUS_NOT_SUPPORTED ||
+                                  status == STATUS_INVALID_INFO_CLASS) {
+                               FREE(buf);
+                               goto use_FindFirstFile;
                        } else {
                                set_errno_from_nt_status(status);
                                ERROR_WITH_ERRNO("Failed to read directory "
@@ -536,7 +472,10 @@ win32_recurse_directory(HANDLE hDir,
 out_free_buf:
        FREE(buf);
        return ret;
-#else
+#endif
+
+use_FindFirstFile:
+       ;
        WIN32_FIND_DATAW dat;
        HANDLE hFind;
        DWORD err;
@@ -601,7 +540,6 @@ out_free_buf:
 out_find_close:
        FindClose(hFind);
        return ret;
-#endif
 }
 
 /* Reparse point fixup status code */
@@ -683,7 +621,7 @@ win32_capture_maybe_rpfix_target(wchar_t *target, u16 *target_nbytes_p,
 static int
 win32_capture_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p,
                        u64 capture_root_ino, u64 capture_root_dev,
-                       const wchar_t *path)
+                       const wchar_t *path, struct add_image_params *params)
 {
        struct reparse_data rpdata;
        int ret;
@@ -720,17 +658,18 @@ win32_capture_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p,
                        ret = -ret;
        } else {
                if (rp_status == RP_EXCLUDED) {
+                       /* Ignoring absolute symbolic link or junction point
+                        * that points out of the tree to be captured.  */
                        size_t print_name_nchars = rpdata.print_name_nbytes / 2;
                        wchar_t print_name0[print_name_nchars + 1];
                        print_name0[print_name_nchars] = L'\0';
                        wmemcpy(print_name0, rpdata.print_name, print_name_nchars);
-                       WARNING("Ignoring %ls pointing out of capture directory:\n"
-                               "          \"%ls\" -> \"%ls\"\n"
-                               "          (Use --norpfix to capture all symbolic links "
-                               "and junction points as-is)",
-                               (rpdata.rptag == WIM_IO_REPARSE_TAG_SYMLINK) ?
-                                       L"absolute symbolic link" : L"junction point",
-                               path, print_name0);
+
+                       params->progress.scan.cur_path = path;
+                       params->progress.scan.symlink_target = print_name0;
+                       do_capture_progress(params,
+                                           WIMLIB_SCAN_DENTRY_EXCLUDED_SYMLINK,
+                                           NULL);
                }
                ret = rp_status;
        }
@@ -799,7 +738,8 @@ win32_get_reparse_data(HANDLE hFile, const wchar_t *path,
                                              &rpbuflen,
                                              params->capture_root_ino,
                                              params->capture_root_dev,
-                                             path);
+                                             path,
+                                             params);
        } else {
                ret = RP_NOT_FIXED;
        }
@@ -962,10 +902,10 @@ win32_capture_stream(const wchar_t *path,
                ret = win32_get_encrypted_file_size(path, &encrypted_size);
                if (ret)
                        goto out_free_spath;
-               lte->resource_entry.original_size = encrypted_size;
+               lte->size = encrypted_size;
        } else {
                lte->resource_location = RESOURCE_IN_FILE_ON_DISK;
-               lte->resource_entry.original_size = (u64)dat->StreamSize.QuadPart;
+               lte->size = (u64)dat->StreamSize.QuadPart;
        }
 
        u32 stream_id;
@@ -1019,28 +959,27 @@ win32_capture_streams(HANDLE *hFile_p,
        IO_STATUS_BLOCK io_status;
        NTSTATUS status;
        const FILE_STREAM_INFORMATION *info;
-#else
+#endif
        HANDLE hFind;
        DWORD err;
-#endif
 
        DEBUG("Capturing streams from \"%ls\"", path);
 
        if (!(vol_flags & FILE_NAMED_STREAMS))
                goto unnamed_only;
-#ifndef WITH_NTDLL
-       if (win32func_FindFirstStreamW == NULL)
-               goto unnamed_only;
-#endif
 
 #ifdef WITH_NTDLL
+       if (!func_NtQueryInformationFile)
+               goto use_FindFirstStream;
+
        buf = _buf;
        bufsize = sizeof(_buf);
 
        /* Get a buffer containing the stream information.  */
        for (;;) {
-               status = NtQueryInformationFile(*hFile_p, &io_status, buf, bufsize,
-                                               FileStreamInformation);
+               status = (*func_NtQueryInformationFile)(*hFile_p, &io_status,
+                                                       buf, bufsize,
+                                                       FileStreamInformation);
                if (status == STATUS_SUCCESS) {
                        break;
                } else if (status == STATUS_BUFFER_OVERFLOW) {
@@ -1057,6 +996,10 @@ win32_capture_streams(HANDLE *hFile_p,
                                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);
@@ -1107,12 +1050,18 @@ out_free_buf:
        if (buf != _buf)
                FREE(buf);
        return ret;
+#endif /* WITH_NTDLL */
 
-#else /* 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
@@ -1155,25 +1104,19 @@ out_free_buf:
 out_find_close:
        FindClose(hFind);
        return ret;
-#endif /* !WITH_NTDLL */
 
 unnamed_only:
        /* 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_DIRECTORY |
-                                    FILE_ATTRIBUTE_REPARSE_POINT)))
-       {
-               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;
+       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
@@ -1185,17 +1128,16 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
                                  unsigned vol_flags)
 {
        struct wim_dentry *root = NULL;
-       struct wim_inode *inode;
+       struct wim_inode *inode = NULL;
        DWORD err;
        u64 file_size;
        int ret;
        u8 *rpbuf;
        u16 rpbuflen;
        u16 not_rpfixed;
-       HANDLE hFile;
+       HANDLE hFile = INVALID_HANDLE_VALUE;
        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) {
@@ -1203,9 +1145,8 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
                        ret = WIMLIB_ERR_INVALID_CAPTURE_CONFIG;
                        goto out;
                }
-               do_capture_progress(params, WIMLIB_SCAN_DENTRY_EXCLUDED);
                ret = 0;
-               goto out;
+               goto out_progress;
        }
 
 #if 0
@@ -1220,8 +1161,6 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
        }
 #endif
 
-       do_capture_progress(params, WIMLIB_SCAN_DENTRY_OK);
-
        desiredAccess = FILE_READ_DATA | FILE_READ_ATTRIBUTES |
                        READ_CONTROL | ACCESS_SYSTEM_SECURITY;
 again:
@@ -1250,7 +1189,7 @@ again:
                ERROR_WITH_ERRNO("Failed to get file information for \"%ls\"",
                                 path);
                ret = WIMLIB_ERR_STAT;
-               goto out_close_handle;
+               goto out;
        }
 
        if (file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
@@ -1260,12 +1199,12 @@ again:
                if (ret < 0) {
                        /* WIMLIB_ERR_* (inverted) */
                        ret = -ret;
-                       goto out_close_handle;
+                       goto out;
                } else if (ret & RP_FIXED) {
                        not_rpfixed = 0;
                } else if (ret == RP_EXCLUDED) {
                        ret = 0;
-                       goto out_close_handle;
+                       goto out;
                } else {
                        not_rpfixed = 1;
                }
@@ -1286,16 +1225,18 @@ again:
                                        (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)),
                                     &root);
        if (ret)
-               goto out_close_handle;
+               goto out;
 
        ret = win32_get_short_name(hFile, path, root);
        if (ret)
-               goto out_close_handle;
+               goto out;
 
        inode = root->d_inode;
 
-       if (inode->i_nlink > 1) /* Shared inode; nothing more to do */
-               goto out_close_handle;
+       if (inode->i_nlink > 1) {
+               /* Shared inode; nothing more to do */
+               goto out_progress;
+       }
 
        inode->i_attributes = file_info.dwFileAttributes;
        inode->i_creation_time = FILETIME_to_u64(&file_info.ftCreationTime);
@@ -1312,7 +1253,7 @@ again:
                                                    &params->sd_set, state,
                                                    params->add_flags);
                if (ret)
-                       goto out_close_handle;
+                       goto out;
        }
 
        file_size = ((u64)file_info.nFileSizeHigh << 32) |
@@ -1329,7 +1270,7 @@ again:
                                    file_size,
                                    vol_flags);
        if (ret)
-               goto out_close_handle;
+               goto out;
 
        if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
                /* Reparse point: set the reparse data (which we read already)
@@ -1350,7 +1291,7 @@ again:
                                ERROR_WITH_ERRNO("Failed to reopen \"%ls\"",
                                                 path);
                                ret = WIMLIB_ERR_OPEN;
-                               goto out_close_handle;
+                               goto out;
                        }
                }
                ret = win32_recurse_directory(hFile,
@@ -1361,9 +1302,19 @@ again:
                                              state,
                                              vol_flags);
        }
-out_close_handle:
-       CloseHandle(hFile);
+       if (ret)
+               goto out;
+
+       path[path_num_chars] = '\0';
+out_progress:
+       params->progress.scan.cur_path = path;
+       if (root == NULL)
+               do_capture_progress(params, WIMLIB_SCAN_DENTRY_EXCLUDED, NULL);
+       else
+               do_capture_progress(params, WIMLIB_SCAN_DENTRY_OK, inode);
 out:
+       if (hFile != INVALID_HANDLE_VALUE)
+               CloseHandle(hFile);
        if (ret == 0)
                *root_ret = root;
        else
@@ -1409,14 +1360,16 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
        struct win32_capture_state state;
        unsigned vol_flags;
        DWORD dret;
-       bool need_prefix_free = false;
 
-#ifndef WITH_NTDLL
-       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.");
        }
-#endif
 
        path_nchars = wcslen(root_disk_path);
        if (path_nchars > WINDOWS_NT_MAX_PATH)
@@ -1435,8 +1388,8 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
        /* 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)
+       path = MALLOC((WINDOWS_NT_MAX_PATH + 1) * sizeof(wchar_t));
+       if (path == NULL)
                return WIMLIB_ERR_NOMEM;
 
        /* Work around defective behavior in Windows where paths longer than 260
@@ -1444,37 +1397,45 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
         * turned into absolute paths and prefixed with "\\?\".  */
 
        if (wcsncmp(root_disk_path, L"\\\\?\\", 4)) {
-               dret = GetFullPathName(root_disk_path, WINDOWS_NT_MAX_PATH - 4,
+               dret = GetFullPathName(root_disk_path, WINDOWS_NT_MAX_PATH - 3,
                                       &path[4], NULL);
 
-               if (dret == 0 || dret >= WINDOWS_NT_MAX_PATH - 4) {
+               if (dret == 0 || dret >= WINDOWS_NT_MAX_PATH - 3) {
                        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);
        }
 
+       /* Strip trailing slashes.  */
+       while (path_nchars >= 2 &&
+              is_any_path_separator(path[path_nchars - 1]) &&
+              path[path_nchars - 2] != L':')
+       {
+               path[--path_nchars] = L'\0';
+       }
+
+       /* 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;
+               }
+       }
+
        memset(&state, 0, sizeof(state));
        ret = win32_build_dentry_tree_recursive(root_ret, path,
                                                path_nchars, params,
                                                &state, vol_flags);
-       if (need_prefix_free)
+       if (params->config != NULL)
                FREE(params->config->_prefix);
 out_free_path:
        FREE(path);