+ /* Temporary I/O status block for system calls */
+ IO_STATUS_BLOCK iosb;
+
+ /* Allocated buffer for creating "printable" paths from our
+ * target-relative NT paths */
+ wchar_t *print_buffer;
+
+ /* Allocated buffer for reading stream data when it cannot be extracted
+ * directly */
+ u8 *data_buffer;
+
+ /* Pointer to the next byte in @data_buffer to fill */
+ u8 *data_buffer_ptr;
+
+ /* Size allocated in @data_buffer */
+ size_t data_buffer_size;
+
+ /* Current offset in the raw encrypted file being written */
+ size_t encrypted_offset;
+
+ /* Current size of the raw encrypted file being written */
+ size_t encrypted_size;
+
+ /* Temporary buffer for reparse data */
+ struct reparse_buffer_disk rpbuf;
+
+ /* Temporary buffer for reparse data of "fixed" absolute symbolic links
+ * and junctions */
+ struct reparse_buffer_disk rpfixbuf;
+
+ /* Array of open handles to filesystem streams currently being written
+ */
+ HANDLE open_handles[MAX_OPEN_STREAMS];
+
+ /* Number of handles in @open_handles currently open (filled in from the
+ * beginning of the array) */
+ unsigned num_open_handles;
+
+ /* List of dentries, joined by @tmp_list, that need to have reparse data
+ * extracted as soon as the whole stream has been read into
+ * @data_buffer. */
+ struct list_head reparse_dentries;
+
+ /* List of dentries, joined by @tmp_list, that need to have raw
+ * encrypted data extracted as soon as the whole stream has been read
+ * into @data_buffer. */
+ struct list_head encrypted_dentries;
+
+ /* Number of files for which we didn't have permission to set the full
+ * security descriptor. */
+ unsigned long partial_security_descriptors;
+
+ /* Number of files for which we didn't have permission to set any part
+ * of the security descriptor. */
+ unsigned long no_security_descriptors;
+
+ /* Number of files for which we couldn't set the short name. */
+ unsigned long num_short_name_failures;
+
+ /* Have we tried to enable short name support on the target volume yet?
+ */
+ bool tried_to_enable_short_names;
+};
+
+/* Get the drive letter from a Windows path, or return the null character if the
+ * path is relative. */
+static wchar_t
+get_drive_letter(const wchar_t *path)
+{
+ /* Skip \\?\ prefix */
+ if (!wcsncmp(path, L"\\\\?\\", 4))
+ path += 4;
+
+ /* Return drive letter if valid */
+ if (((path[0] >= L'a' && path[0] <= L'z') ||
+ (path[0] >= L'A' && path[0] <= L'Z')) && path[1] == L':')
+ return path[0];
+
+ return L'\0';
+}
+
+static void
+get_vol_flags(const wchar_t *target, DWORD *vol_flags_ret,
+ bool *short_names_supported_ret)
+{
+ wchar_t filesystem_name[MAX_PATH + 1];
+ wchar_t drive[4];
+ wchar_t *volume = NULL;
+
+ *vol_flags_ret = 0;
+ *short_names_supported_ret = false;
+
+ drive[0] = get_drive_letter(target);
+ if (drive[0]) {
+ drive[1] = L':';
+ drive[2] = L'\\';
+ drive[3] = L'\0';
+ volume = drive;
+ }
+
+ if (!GetVolumeInformation(volume, NULL, 0, NULL, NULL,
+ vol_flags_ret, filesystem_name,
+ ARRAY_LEN(filesystem_name)))
+ {
+ DWORD err = GetLastError();
+ set_errno_from_win32_error(err);
+ WARNING_WITH_ERRNO("Failed to get volume information for "
+ "\"%ls\" (err=%"PRIu32")",
+ target, (u32)err);
+ return;
+ }
+
+ if (wcsstr(filesystem_name, L"NTFS")) {
+ /* FILE_SUPPORTS_HARD_LINKS is only supported on Windows 7 and
+ * later. Force it on anyway if filesystem is NTFS. */
+ *vol_flags_ret |= FILE_SUPPORTS_HARD_LINKS;
+
+ /* There's no volume flag for short names, but according to the
+ * MS documentation they are only user-settable on NTFS. */
+ *short_names_supported_ret = true;
+ }
+}
+
+static int
+win32_get_supported_features(const wchar_t *target,
+ struct wim_features *supported_features)
+{
+ DWORD vol_flags;
+ bool short_names_supported;
+
+ /* Query the features of the target volume. */
+
+ get_vol_flags(target, &vol_flags, &short_names_supported);
+
+ supported_features->archive_files = 1;
+ supported_features->hidden_files = 1;
+ supported_features->system_files = 1;
+
+ if (vol_flags & FILE_FILE_COMPRESSION)
+ supported_features->compressed_files = 1;
+
+ if (vol_flags & FILE_SUPPORTS_ENCRYPTION) {
+ supported_features->encrypted_files = 1;
+ supported_features->encrypted_directories = 1;
+ }
+
+ supported_features->not_context_indexed_files = 1;
+
+ /* Don't do anything with FILE_SUPPORTS_SPARSE_FILES. */
+
+ if (vol_flags & FILE_NAMED_STREAMS)
+ supported_features->named_data_streams = 1;
+
+ if (vol_flags & FILE_SUPPORTS_HARD_LINKS)
+ supported_features->hard_links = 1;
+
+ if (vol_flags & FILE_SUPPORTS_REPARSE_POINTS)
+ supported_features->reparse_points = 1;
+
+ if (vol_flags & FILE_PERSISTENT_ACLS)
+ supported_features->security_descriptors = 1;
+
+ if (short_names_supported)
+ supported_features->short_names = 1;
+
+ supported_features->timestamps = 1;
+
+ /* Note: Windows does not support case sensitive filenames! At least
+ * not without changing the registry and rebooting... */
+
+ return 0;
+}
+
+/* Load the patterns from [PrepopulateList] of WimBootCompress.ini in the WIM
+ * image being extracted. */
+static int
+load_prepopulate_pats(struct win32_apply_ctx *ctx)
+{
+ const wchar_t *path = L"\\Windows\\System32\\WimBootCompress.ini";
+ struct wim_dentry *dentry;
+ struct wim_lookup_table_entry *lte;
+ int ret;
+ void *buf;
+ struct string_set *s;
+ void *mem;
+ struct text_file_section sec;
+
+ ctx->wimboot.tried_to_load_prepopulate_list = true;
+
+ dentry = get_dentry(ctx->common.wim, path, WIMLIB_CASE_INSENSITIVE);
+ if (!dentry ||
+ (dentry->d_inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_REPARSE_POINT |
+ FILE_ATTRIBUTE_ENCRYPTED)) ||
+ !(lte = inode_unnamed_lte(dentry->d_inode, ctx->common.wim->lookup_table)))
+ {
+ WARNING("%ls does not exist in WIM image!", path);
+ return WIMLIB_ERR_PATH_DOES_NOT_EXIST;
+ }
+
+ ret = read_full_stream_into_alloc_buf(lte, &buf);
+ if (ret)
+ return ret;
+
+ s = CALLOC(1, sizeof(struct string_set));
+ if (!s) {
+ FREE(buf);
+ return WIMLIB_ERR_NOMEM;
+ }
+
+ sec.name = T("PrepopulateList");
+ sec.strings = s;
+
+ ret = do_load_text_file(path, buf, lte->size, &mem, &sec, 1,
+ LOAD_TEXT_FILE_REMOVE_QUOTES |
+ LOAD_TEXT_FILE_NO_WARNINGS,
+ mangle_pat);
+ BUILD_BUG_ON(OS_PREFERRED_PATH_SEPARATOR != WIM_PATH_SEPARATOR);
+ FREE(buf);
+ if (ret) {
+ FREE(s);
+ return ret;
+ }
+ ctx->wimboot.prepopulate_pats = s;
+ ctx->wimboot.mem_prepopulate_pats = mem;
+ return 0;
+}
+
+/* Returns %true if the path to @dentry matches a pattern in [PrepopulateList]
+ * of WimBootCompress.ini. Otherwise returns %false.
+ *
+ * @dentry must have had its full path calculated. */
+static bool
+in_prepopulate_list(struct wim_dentry *dentry,
+ const struct win32_apply_ctx *ctx)
+{
+ const struct string_set *pats = ctx->wimboot.prepopulate_pats;
+
+ if (!pats || !pats->num_strings)
+ return false;
+
+ return match_pattern_list(dentry->_full_path,
+ wcslen(dentry->_full_path), pats);
+}
+
+static const wchar_t *
+current_path(struct win32_apply_ctx *ctx);
+
+static void
+build_extraction_path(const struct wim_dentry *dentry,
+ struct win32_apply_ctx *ctx);
+
+#define WIM_BACKING_NOT_ENABLED -1
+#define WIM_BACKING_NOT_POSSIBLE -2
+#define WIM_BACKING_EXCLUDED -3
+
+/*
+ * Determines if the unnamed data stream of a file will be created as an
+ * external backing, as opposed to a standard extraction.
+ */
+static int
+win32_will_externally_back(struct wim_dentry *dentry, struct apply_ctx *_ctx)
+{
+ struct win32_apply_ctx *ctx = (struct win32_apply_ctx *)_ctx;
+ struct wim_lookup_table_entry *stream;
+ int ret;
+
+ if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT))
+ return WIM_BACKING_NOT_ENABLED;
+
+ if (!ctx->wimboot.tried_to_load_prepopulate_list) {
+ ret = load_prepopulate_pats(ctx);
+ if (ret == WIMLIB_ERR_NOMEM)
+ return ret;
+ }
+
+ if (dentry->d_inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_REPARSE_POINT |
+ FILE_ATTRIBUTE_ENCRYPTED))
+ return WIM_BACKING_NOT_POSSIBLE;
+
+ stream = inode_unnamed_lte_resolved(dentry->d_inode);
+
+ if (!stream ||
+ stream->resource_location != RESOURCE_IN_WIM ||
+ stream->rspec->wim != ctx->common.wim ||
+ stream->size != stream->rspec->uncompressed_size)
+ return WIM_BACKING_NOT_POSSIBLE;
+
+ ret = calculate_dentry_full_path(dentry);
+ if (ret)
+ return ret;
+
+ if (in_prepopulate_list(dentry, ctx))
+ return WIM_BACKING_EXCLUDED;
+
+ return 0;
+}
+
+static int
+set_external_backing(HANDLE h, struct wim_dentry *dentry, struct win32_apply_ctx *ctx)
+{
+ int ret;
+
+ ret = win32_will_externally_back(dentry, &ctx->common);
+ if (ret > 0) /* Error. */
+ return ret;
+
+ if (ret < 0 && ret != WIM_BACKING_EXCLUDED)
+ return 0; /* Not externally backing, other than due to exclusion. */
+
+ build_extraction_path(dentry, ctx);
+
+ if (ret == WIM_BACKING_EXCLUDED) {
+ /* Not externally backing due to exclusion. */
+ union wimlib_progress_info info;
+
+ info.wimboot_exclude.path_in_wim = dentry->_full_path;
+ info.wimboot_exclude.extraction_path = current_path(ctx);
+
+ return call_progress(ctx->common.progfunc,
+ WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE,
+ &info, ctx->common.progctx);
+ } else {
+ /* Externally backing. */
+ return wimboot_set_pointer(h,
+ current_path(ctx),
+ inode_unnamed_lte_resolved(dentry->d_inode),
+ ctx->wimboot.data_source_id,
+ ctx->wimboot.wim_lookup_table_hash,
+ ctx->wimboot.wof_running);
+ }
+}
+
+/* Calculates the SHA-1 message digest of the WIM's lookup table. */
+static int
+hash_lookup_table(WIMStruct *wim, u8 hash[SHA1_HASH_SIZE])
+{
+ return wim_reshdr_to_hash(&wim->hdr.lookup_table_reshdr, wim, hash);
+}
+
+/* Prepare for doing a "WIMBoot" extraction by loading patterns from
+ * [PrepopulateList] of WimBootCompress.ini and allocating a WOF data source ID
+ * on the target volume. */
+static int
+start_wimboot_extraction(struct win32_apply_ctx *ctx)
+{
+ int ret;
+ WIMStruct *wim = ctx->common.wim;
+
+ if (!ctx->wimboot.tried_to_load_prepopulate_list) {
+ ret = load_prepopulate_pats(ctx);
+ if (ret == WIMLIB_ERR_NOMEM)
+ return ret;
+ }
+
+ if (!wim_info_get_wimboot(wim->wim_info, wim->current_image))
+ WARNING("Image is not marked as WIMBoot compatible!");
+
+ ret = hash_lookup_table(ctx->common.wim,
+ ctx->wimboot.wim_lookup_table_hash);
+ if (ret)
+ return ret;
+
+ return wimboot_alloc_data_source_id(wim->filename,
+ wim->hdr.guid,
+ wim->current_image,
+ ctx->common.target,
+ &ctx->wimboot.data_source_id,
+ &ctx->wimboot.wof_running);
+}
+
+static void
+build_win32_extraction_path(const struct wim_dentry *dentry,
+ struct win32_apply_ctx *ctx);
+
+/* Sets WimBoot=1 in the extracted SYSTEM registry hive.
+ *
+ * WIMGAPI does this, and it's possible that it's important.
+ * But I don't know exactly what this value means to Windows. */
+static int
+end_wimboot_extraction(struct win32_apply_ctx *ctx)
+{
+ struct wim_dentry *dentry;
+ wchar_t subkeyname[32];
+ LONG res;
+ LONG res2;
+ HKEY key;
+ DWORD value;
+
+ dentry = get_dentry(ctx->common.wim, L"\\Windows\\System32\\config\\SYSTEM",
+ WIMLIB_CASE_INSENSITIVE);
+
+ if (!dentry || !will_extract_dentry(dentry))
+ goto out;
+
+ if (!will_extract_dentry(wim_get_current_root_dentry(ctx->common.wim)))
+ goto out;
+
+ /* Not bothering to use the native routines (e.g. NtLoadKey()) for this.
+ * If this doesn't work, you probably also have many other problems. */
+
+ build_win32_extraction_path(dentry, ctx);
+
+ randomize_char_array_with_alnum(subkeyname, 20);
+ subkeyname[20] = L'\0';
+
+ res = RegLoadKey(HKEY_LOCAL_MACHINE, subkeyname, ctx->pathbuf.Buffer);
+ if (res)
+ goto out_check_res;
+
+ wcscpy(&subkeyname[20], L"\\Setup");
+
+ res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, subkeyname, 0, NULL,
+ REG_OPTION_BACKUP_RESTORE, 0, NULL, &key, NULL);
+ if (res)
+ goto out_unload_key;
+
+ value = 1;
+
+ res = RegSetValueEx(key, L"WimBoot", 0, REG_DWORD,
+ (const BYTE *)&value, sizeof(DWORD));
+ if (res)
+ goto out_close_key;
+
+ res = RegFlushKey(key);
+
+out_close_key:
+ res2 = RegCloseKey(key);
+ if (!res)
+ res = res2;
+out_unload_key:
+ subkeyname[20] = L'\0';
+ RegUnLoadKey(HKEY_LOCAL_MACHINE, subkeyname);
+out_check_res:
+ if (res) {
+ /* Warning only. */
+ set_errno_from_win32_error(res);
+ WARNING_WITH_ERRNO("Failed to set \\Setup: dword \"WimBoot\"=1 value "
+ "in registry hive \"%ls\" (err=%"PRIu32")",
+ ctx->pathbuf.Buffer, (u32)res);
+ }
+out:
+ return 0;
+}
+
+/* Returns the number of wide characters needed to represent the path to the
+ * specified @dentry, relative to the target directory, when extracted.
+ *
+ * Does not include null terminator (not needed for NtCreateFile). */
+static size_t
+dentry_extraction_path_length(const struct wim_dentry *dentry)
+{
+ size_t len = 0;
+ const struct wim_dentry *d;
+
+ d = dentry;
+ do {
+ len += d->d_extraction_name_nchars + 1;
+ d = d->d_parent;
+ } while (!dentry_is_root(d) && will_extract_dentry(d));
+
+ return --len; /* No leading slash */
+}
+
+/* Returns the length of the longest string that might need to be appended to
+ * the path to an alias of an inode to open or create a named data stream.
+ *
+ * If the inode has no named data streams, this will be 0. Otherwise, this will
+ * be 1 plus the length of the longest-named data stream, since the data stream
+ * name must be separated from the path by the ':' character. */
+static size_t
+inode_longest_named_data_stream_spec(const struct wim_inode *inode)
+{
+ size_t max = 0;
+ for (u16 i = 0; i < inode->i_num_ads; i++) {
+ size_t len = inode->i_ads_entries[i].stream_name_nbytes;
+ if (len > max)
+ max = len;
+ }
+ if (max)
+ max = 1 + (max / sizeof(wchar_t));
+ return max;
+}
+
+/* Find the length, in wide characters, of the longest path needed for
+ * extraction of any file in @dentry_list relative to the target directory.
+ *
+ * Accounts for named data streams, but does not include null terminator (not
+ * needed for NtCreateFile). */
+static size_t
+compute_path_max(struct list_head *dentry_list)
+{
+ size_t max = 0;
+ const struct wim_dentry *dentry;
+
+ list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+ size_t len;
+
+ len = dentry_extraction_path_length(dentry);
+
+ /* Account for named data streams */
+ len += inode_longest_named_data_stream_spec(dentry->d_inode);
+
+ if (len > max)
+ max = len;
+ }
+
+ return max;
+}
+
+/* Build the path at which to extract the @dentry, relative to the target
+ * directory.
+ *
+ * The path is saved in ctx->pathbuf. */
+static void
+build_extraction_path(const struct wim_dentry *dentry,
+ struct win32_apply_ctx *ctx)
+{
+ size_t len;
+ wchar_t *p;
+ const struct wim_dentry *d;
+
+ len = dentry_extraction_path_length(dentry);
+
+ ctx->pathbuf.Length = len * sizeof(wchar_t);
+ p = ctx->pathbuf.Buffer + len;
+ for (d = dentry;
+ !dentry_is_root(d->d_parent) && will_extract_dentry(d->d_parent);
+ d = d->d_parent)
+ {
+ p -= d->d_extraction_name_nchars;
+ wmemcpy(p, d->d_extraction_name, d->d_extraction_name_nchars);
+ *--p = '\\';
+ }
+ /* No leading slash */
+ p -= d->d_extraction_name_nchars;
+ wmemcpy(p, d->d_extraction_name, d->d_extraction_name_nchars);
+}
+
+/* Build the path at which to extract the @dentry, relative to the target
+ * directory, adding the suffix for a named data stream.
+ *
+ * The path is saved in ctx->pathbuf. */
+static void
+build_extraction_path_with_ads(const struct wim_dentry *dentry,
+ struct win32_apply_ctx *ctx,
+ const wchar_t *stream_name,
+ size_t stream_name_nchars)
+{
+ wchar_t *p;