+ /* 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;
+
+ build_extraction_path(dentry, ctx);
+
+ /* Add :NAME for named data stream */
+ p = ctx->pathbuf.Buffer + (ctx->pathbuf.Length / sizeof(wchar_t));
+ *p++ = L':';
+ wmemcpy(p, stream_name, stream_name_nchars);
+ ctx->pathbuf.Length += (1 + stream_name_nchars) * sizeof(wchar_t);
+}
+
+/* Build the Win32 namespace path to the specified @dentry when extracted.
+ *
+ * The path is saved in ctx->pathbuf and will be null terminated.
+ *
+ * XXX: We could get rid of this if it wasn't needed for the file encryption
+ * APIs, and the registry manipulation in WIMBoot mode. */
+static void
+build_win32_extraction_path(const struct wim_dentry *dentry,
+ struct win32_apply_ctx *ctx)
+{
+ build_extraction_path(dentry, ctx);
+
+ /* Prepend target_ntpath to our relative path, then change \??\ into \\?\ */
+
+ memmove(ctx->pathbuf.Buffer +
+ (ctx->target_ntpath.Length / sizeof(wchar_t)) + 1,
+ ctx->pathbuf.Buffer, ctx->pathbuf.Length);
+ memcpy(ctx->pathbuf.Buffer, ctx->target_ntpath.Buffer,
+ ctx->target_ntpath.Length);
+ ctx->pathbuf.Buffer[ctx->target_ntpath.Length / sizeof(wchar_t)] = L'\\';
+ ctx->pathbuf.Length += ctx->target_ntpath.Length + sizeof(wchar_t);
+ ctx->pathbuf.Buffer[ctx->pathbuf.Length / sizeof(wchar_t)] = L'\0';
+
+ wimlib_assert(ctx->pathbuf.Length >= 4 * sizeof(wchar_t) &&
+ !wmemcmp(ctx->pathbuf.Buffer, L"\\??\\", 4));
+
+ ctx->pathbuf.Buffer[1] = L'\\';
+
+}
+
+/* Returns a "printable" representation of the last relative NT path that was
+ * constructed with build_extraction_path() or build_extraction_path_with_ads().
+ *
+ * This will be overwritten by the next call to this function. */
+static const wchar_t *
+current_path(struct win32_apply_ctx *ctx)
+{
+ wchar_t *p = ctx->print_buffer;
+
+ p = wmempcpy(p, ctx->common.target, ctx->common.target_nchars);
+ *p++ = L'\\';
+ p = wmempcpy(p, ctx->pathbuf.Buffer, ctx->pathbuf.Length / sizeof(wchar_t));
+ *p = L'\0';
+ return ctx->print_buffer;
+}
+
+/*
+ * Ensures the target directory exists and opens a handle to it, in preparation
+ * of using paths relative to it.
+ */
+static int
+prepare_target(struct list_head *dentry_list, struct win32_apply_ctx *ctx)
+{
+ int ret;
+ NTSTATUS status;
+ size_t path_max;
+
+ /* Open handle to the target directory (possibly creating it). */
+
+ ret = win32_path_to_nt_path(ctx->common.target, &ctx->target_ntpath);
+ if (ret)
+ return ret;
+
+ ctx->attr.Length = sizeof(ctx->attr);
+ ctx->attr.ObjectName = &ctx->target_ntpath;
+
+ status = (*func_NtCreateFile)(&ctx->h_target,
+ FILE_TRAVERSE,
+ &ctx->attr,
+ &ctx->iosb,
+ NULL,
+ 0,
+ FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_IF,
+ FILE_DIRECTORY_FILE |
+ FILE_OPEN_REPARSE_POINT |
+ FILE_OPEN_FOR_BACKUP_INTENT,
+ NULL,
+ 0);
+
+ if (!NT_SUCCESS(status)) {
+ set_errno_from_nt_status(status);
+ ERROR_WITH_ERRNO("Can't open or create directory \"%ls\" "
+ "(status=0x%08"PRIx32")",
+ ctx->common.target, (u32)status);
+ return WIMLIB_ERR_OPENDIR;
+ }
+
+ path_max = compute_path_max(dentry_list);
+
+ /* Add some extra for building Win32 paths for the file encryption APIs
+ * ... */
+ path_max += 2 + (ctx->target_ntpath.Length / sizeof(wchar_t));
+
+ ctx->pathbuf.MaximumLength = path_max * sizeof(wchar_t);
+ ctx->pathbuf.Buffer = MALLOC(ctx->pathbuf.MaximumLength);
+ if (!ctx->pathbuf.Buffer)
+ return WIMLIB_ERR_NOMEM;
+
+ ctx->attr.RootDirectory = ctx->h_target;
+ ctx->attr.ObjectName = &ctx->pathbuf;
+
+ ctx->print_buffer = MALLOC((ctx->common.target_nchars + 1 + path_max + 1) *
+ sizeof(wchar_t));
+ if (!ctx->print_buffer)
+ return WIMLIB_ERR_NOMEM;
+
+ return 0;
+}
+
+/* When creating an inode that will have a short (DOS) name, we create it using
+ * the long name associated with the short name. This ensures that the short
+ * name gets associated with the correct long name. */
+static struct wim_dentry *
+first_extraction_alias(const struct wim_inode *inode)
+{
+ struct list_head *next = inode->i_extraction_aliases.next;
+ struct wim_dentry *dentry;
+
+ do {
+ dentry = list_entry(next, struct wim_dentry,
+ d_extraction_alias_node);
+ if (dentry_has_short_name(dentry))
+ break;
+ next = next->next;
+ } while (next != &inode->i_extraction_aliases);
+ return dentry;
+}
+
+/*
+ * Set or clear FILE_ATTRIBUTE_COMPRESSED if the inherited value is different
+ * from the desired value.
+ *
+ * Note that you can NOT override the inherited value of
+ * FILE_ATTRIBUTE_COMPRESSED directly with NtCreateFile().
+ */
+static int
+adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
+ struct win32_apply_ctx *ctx)
+{
+ const bool compressed = (dentry->d_inode->i_attributes &
+ FILE_ATTRIBUTE_COMPRESSED);
+
+ if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)
+ return 0;
+
+ if (!ctx->common.supported_features.compressed_files)
+ return 0;
+
+ FILE_BASIC_INFORMATION info;
+ NTSTATUS status;
+ USHORT compression_state;
+
+ /* Get current attributes */
+ status = (*func_NtQueryInformationFile)(h, &ctx->iosb,
+ &info, sizeof(info),
+ FileBasicInformation);
+ if (NT_SUCCESS(status) &&
+ compressed == !!(info.FileAttributes & FILE_ATTRIBUTE_COMPRESSED))
+ {
+ /* Nothing needs to be done. */
+ return 0;
+ }
+
+ /* Set the new compression state */
+
+ if (compressed)
+ compression_state = COMPRESSION_FORMAT_DEFAULT;
+ else
+ compression_state = COMPRESSION_FORMAT_NONE;
+
+ status = (*func_NtFsControlFile)(h,
+ NULL,
+ NULL,
+ NULL,
+ &ctx->iosb,
+ FSCTL_SET_COMPRESSION,
+ &compression_state,
+ sizeof(USHORT),
+ NULL,
+ 0);
+ if (NT_SUCCESS(status))
+ return 0;
+
+ set_errno_from_nt_status(status);
+ ERROR_WITH_ERRNO("Can't %s compression attribute on \"%ls\" "
+ "(status=0x%08"PRIx32")",
+ (compressed ? "set" : "clear"),
+ current_path(ctx), status);
+ return WIMLIB_ERR_SET_ATTRIBUTES;
+}
+
+/*
+ * Clear FILE_ATTRIBUTE_ENCRYPTED if the file or directory is not supposed to be
+ * encrypted.
+ *
+ * You can provide FILE_ATTRIBUTE_ENCRYPTED to NtCreateFile() to set it on the
+ * created file. However, the file or directory will otherwise default to the
+ * encryption state of the parent directory. This function works around this
+ * limitation by using DecryptFile() to remove FILE_ATTRIBUTE_ENCRYPTED on files
+ * (and directories) that are not supposed to have it set.
+ *
+ * Regardless of whether it succeeds or fails, this function may close the
+ * handle to the file. If it does, it sets it to NULL.
+ */
+static int
+maybe_clear_encryption_attribute(HANDLE *h_ptr, const struct wim_dentry *dentry,
+ struct win32_apply_ctx *ctx)
+{
+ if (dentry->d_inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)
+ return 0;
+
+ if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)
+ return 0;
+
+ if (!ctx->common.supported_features.encrypted_files)
+ return 0;
+
+ FILE_BASIC_INFORMATION info;
+ NTSTATUS status;
+ BOOL bret;
+
+ /* Get current attributes */
+ status = (*func_NtQueryInformationFile)(*h_ptr, &ctx->iosb,
+ &info, sizeof(info),
+ FileBasicInformation);
+ if (NT_SUCCESS(status) &&
+ !(info.FileAttributes & FILE_ATTRIBUTE_ENCRYPTED))
+ {
+ /* Nothing needs to be done. */
+ return 0;
+ }
+
+ /* Set the new encryption state */
+
+ /* Due to Windows' crappy file encryption APIs, we need to close the
+ * handle to the file so we don't get ERROR_SHARING_VIOLATION. We also
+ * hack together a Win32 path, although we will use the \\?\ prefix so
+ * it will actually be a NT path in disguise... */
+ (*func_NtClose)(*h_ptr);
+ *h_ptr = NULL;
+
+ build_win32_extraction_path(dentry, ctx);
+
+ bret = DecryptFile(ctx->pathbuf.Buffer, 0);
+
+ /* Restore the NT namespace path */
+ build_extraction_path(dentry, ctx);
+
+ if (!bret) {
+ DWORD err = GetLastError();
+ set_errno_from_win32_error(err);
+ ERROR_WITH_ERRNO("Can't decrypt file \"%ls\" (err=%"PRIu32")",
+ current_path(ctx), (u32)err);
+ return WIMLIB_ERR_SET_ATTRIBUTES;
+ }
+ return 0;
+}
+
+/* Try to enable short name support on the target volume. If successful, return
+ * true. If unsuccessful, issue a warning and return false. */
+static bool
+try_to_enable_short_names(const wchar_t *volume)
+{
+ HANDLE h;
+ FILE_FS_PERSISTENT_VOLUME_INFORMATION info;
+ BOOL bret;
+ DWORD bytesReturned;
+
+ h = CreateFile(volume, GENERIC_WRITE,
+ FILE_SHARE_VALID_FLAGS, NULL, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ if (h == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ info.VolumeFlags = 0;
+ info.FlagMask = PERSISTENT_VOLUME_STATE_SHORT_NAME_CREATION_DISABLED;
+ info.Version = 1;
+ info.Reserved = 0;
+
+ bret = DeviceIoControl(h, FSCTL_SET_PERSISTENT_VOLUME_STATE,
+ &info, sizeof(info), NULL, 0,
+ &bytesReturned, NULL);
+
+ CloseHandle(h);
+
+ if (!bret)
+ goto fail;
+ return true;
+
+fail:
+ WARNING("Failed to enable short name support on %ls "
+ "(err=%"PRIu32")", volume + 4, (u32)GetLastError());
+ return false;
+}
+
+/* Set the short name on the open file @h which has been created at the location
+ * indicated by @dentry.
+ *
+ * Note that this may add, change, or remove the short name.
+ *
+ * @h must be opened with DELETE access.
+ *
+ * Returns 0 or WIMLIB_ERR_SET_SHORT_NAME. The latter only happens in
+ * STRICT_SHORT_NAMES mode.
+ */
+static int
+set_short_name(HANDLE h, const struct wim_dentry *dentry,
+ struct win32_apply_ctx *ctx)
+{
+
+ if (!ctx->common.supported_features.short_names)
+ return 0;
+
+ /*
+ * Note: The size of the FILE_NAME_INFORMATION buffer must be such that
+ * FileName contains at least 2 wide characters (4 bytes). Otherwise,
+ * NtSetInformationFile() will return STATUS_INFO_LENGTH_MISMATCH. This
+ * is despite the fact that FileNameLength can validly be 0 or 2 bytes,
+ * with the former case being removing the existing short name if
+ * present, rather than setting one.
+ *
+ * The null terminator is seemingly optional, but to be safe we include
+ * space for it and zero all unused space.
+ */
+
+ size_t bufsize = offsetof(FILE_NAME_INFORMATION, FileName) +
+ max(dentry->short_name_nbytes, sizeof(wchar_t)) +
+ sizeof(wchar_t);
+ u8 buf[bufsize] _aligned_attribute(8);
+ FILE_NAME_INFORMATION *info = (FILE_NAME_INFORMATION *)buf;
+ NTSTATUS status;
+
+ memset(buf, 0, bufsize);
+
+ info->FileNameLength = dentry->short_name_nbytes;
+ memcpy(info->FileName, dentry->short_name, dentry->short_name_nbytes);
+
+retry:
+ status = (*func_NtSetInformationFile)(h, &ctx->iosb, info, bufsize,
+ FileShortNameInformation);
+ if (NT_SUCCESS(status))
+ return 0;
+
+ if (status == STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME) {
+ if (dentry->short_name_nbytes == 0)
+ return 0;
+ if (!ctx->tried_to_enable_short_names) {
+ wchar_t volume[7];
+ int ret;
+
+ ctx->tried_to_enable_short_names = true;
+
+ ret = win32_get_drive_path(ctx->common.target,
+ volume);
+ if (ret)
+ return ret;
+ if (try_to_enable_short_names(volume))
+ goto retry;
+ }
+ }
+
+ /* By default, failure to set short names is not an error (since short
+ * names aren't too important anymore...). */
+ if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES)) {
+ ctx->num_short_name_failures++;
+ return 0;
+ }
+
+ if (status == STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME) {
+ ERROR("Can't set short name when short "
+ "names are not enabled on the volume!");
+ } else {
+ ERROR("Can't set short name on \"%ls\" (status=0x%08"PRIx32")",
+ current_path(ctx), (u32)status);
+ }
+ return WIMLIB_ERR_SET_SHORT_NAME;
+}
+
+/*
+ * A wrapper around NtCreateFile() to make it slightly more usable...
+ * This uses the path currently constructed in ctx->pathbuf.
+ *
+ * Also, we always specify FILE_OPEN_FOR_BACKUP_INTENT and
+ * FILE_OPEN_REPARSE_POINT.
+ */
+static NTSTATUS
+do_create_file(PHANDLE FileHandle,
+ ACCESS_MASK DesiredAccess,
+ PLARGE_INTEGER AllocationSize,
+ ULONG FileAttributes,
+ ULONG CreateDisposition,
+ ULONG CreateOptions,
+ struct win32_apply_ctx *ctx)
+{
+ return (*func_NtCreateFile)(FileHandle,
+ DesiredAccess,
+ &ctx->attr,
+ &ctx->iosb,
+ AllocationSize,
+ FileAttributes,
+ FILE_SHARE_VALID_FLAGS,
+ CreateDisposition,
+ CreateOptions |
+ FILE_OPEN_FOR_BACKUP_INTENT |
+ FILE_OPEN_REPARSE_POINT,
+ NULL,
+ 0);
+}
+
+/* Like do_create_file(), but builds the extraction path of the @dentry first.
+ */
+static NTSTATUS
+create_file(PHANDLE FileHandle,
+ ACCESS_MASK DesiredAccess,
+ PLARGE_INTEGER AllocationSize,
+ ULONG FileAttributes,
+ ULONG CreateDisposition,
+ ULONG CreateOptions,
+ const struct wim_dentry *dentry,
+ struct win32_apply_ctx *ctx)
+{
+ build_extraction_path(dentry, ctx);
+ return do_create_file(FileHandle,
+ DesiredAccess,
+ AllocationSize,
+ FileAttributes,
+ CreateDisposition,
+ CreateOptions,
+ ctx);
+}
+
+/* Create empty named data streams.
+ *
+ * Since these won't have 'struct wim_lookup_table_entry's, they won't show up
+ * in the call to extract_stream_list(). Hence the need for the special case.
+ */
+static int
+create_any_empty_ads(const struct wim_dentry *dentry,
+ struct win32_apply_ctx *ctx)
+{
+ const struct wim_inode *inode = dentry->d_inode;
+ LARGE_INTEGER allocation_size;
+ bool path_modified = false;
+ int ret = 0;
+
+ if (!ctx->common.supported_features.named_data_streams)
+ return 0;
+
+ for (u16 i = 0; i < inode->i_num_ads; i++) {
+ const struct wim_ads_entry *entry;
+ NTSTATUS status;
+ HANDLE h;
+
+ entry = &inode->i_ads_entries[i];
+
+ /* Not named? */
+ if (!entry->stream_name_nbytes)