X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fwin32_apply.c;h=630a77f183179864cafaa4adace99e4e8ffe7449;hp=272a1dda39c9731b92c982d5d147c93ff4ebd4c5;hb=b8ebc57d493d7b6e660a50f7789fcb5451f5d77d;hpb=e8c3ca2d1d0cac3d64985b45a9f654d2029a7518 diff --git a/src/win32_apply.c b/src/win32_apply.c index 272a1dda..630a77f1 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -3,22 +3,20 @@ */ /* - * Copyright (C) 2013 Eric Biggers + * Copyright (C) 2013, 2014, 2015 Eric Biggers * - * This file is part of wimlib, a library for working with WIM files. + * This file is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. * - * wimlib is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) - * any later version. - * - * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more + * This file is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * - * You should have received a copy of the GNU General Public License - * along with wimlib; if not, see http://www.gnu.org/licenses/. + * You should have received a copy of the GNU Lesser General Public License + * along with this file; if not, see http://www.gnu.org/licenses/. */ #ifdef __WIN32__ @@ -27,1302 +25,2552 @@ # include "config.h" #endif -#include /* for SetSecurityInfo() */ - #include "wimlib/win32_common.h" #include "wimlib/apply.h" +#include "wimlib/assert.h" +#include "wimlib/capture.h" /* for mangle_pat() and match_pattern_list() */ #include "wimlib/dentry.h" -#include "wimlib/endianness.h" #include "wimlib/error.h" #include "wimlib/lookup_table.h" #include "wimlib/metadata.h" +#include "wimlib/paths.h" #include "wimlib/reparse.h" -#include "wimlib/security.h" +#include "wimlib/textfile.h" +#include "wimlib/xml.h" +#include "wimlib/wildcard.h" +#include "wimlib/wimboot.h" + +struct win32_apply_ctx { + + /* Extract flags, the pointer to the WIMStruct, etc. */ + struct apply_ctx common; + + /* WIMBoot information, only filled in if WIMLIB_EXTRACT_FLAG_WIMBOOT + * was provided */ + struct { + u64 data_source_id; + struct string_set *prepopulate_pats; + void *mem_prepopulate_pats; + u8 wim_lookup_table_hash[SHA1_HASH_SIZE]; + bool wof_running; + bool tried_to_load_prepopulate_list; + } wimboot; + + /* Open handle to the target directory */ + HANDLE h_target; + + /* NT namespace path to the target directory (buffer allocated) */ + UNICODE_STRING target_ntpath; + + /* Temporary buffer for building paths (buffer allocated) */ + UNICODE_STRING pathbuf; + + /* Object attributes to reuse for opening files in the target directory. + * (attr.ObjectName == &pathbuf) and (attr.RootDirectory == h_target). + */ + OBJECT_ATTRIBUTES attr; -#define MAX_CREATE_HARD_LINK_WARNINGS 5 -#define MAX_CREATE_SOFT_LINK_WARNINGS 5 + /* Temporary I/O status block for system calls */ + IO_STATUS_BLOCK iosb; -#define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1 -#define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1 + /* Allocated buffer for creating "printable" paths from our + * target-relative NT paths */ + wchar_t *print_buffer; -static const wchar_t *apply_access_denied_msg = -L"If you are not running this program as the administrator, you may\n" - " need to do so, so that all data and metadata can be extracted\n" - " exactly as the origignal copy. However, if you do not care that\n" - " the security descriptors are extracted correctly, you could run\n" - " `wimlib-imagex apply' with the --no-acls flag instead.\n" - ; + /* 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; -static int -win32_extract_try_rpfix(u8 *rpbuf, - const wchar_t *extract_root_realpath, - unsigned extract_root_realpath_nchars) -{ - struct reparse_data rpdata; - wchar_t *target; - size_t target_nchars; - size_t stripped_nchars; - wchar_t *stripped_target; - wchar_t stripped_target_nchars; - int ret; + /* Size allocated in @data_buffer */ + size_t data_buffer_size; - utf16lechar *new_target; - utf16lechar *new_print_name; - size_t new_target_nchars; - size_t new_print_name_nchars; - utf16lechar *p; + /* Current offset in the raw encrypted file being written */ + size_t encrypted_offset; - ret = parse_reparse_data(rpbuf, 8 + le16_to_cpu(*(u16*)(rpbuf + 4)), - &rpdata); - if (ret) - return ret; + /* Current size of the raw encrypted file being written */ + size_t encrypted_size; - if (extract_root_realpath[0] == L'\0' || - extract_root_realpath[1] != L':' || - extract_root_realpath[2] != L'\\') - { - ERROR("Can't understand full path format \"%ls\". " - "Try turning reparse point fixups off...", - extract_root_realpath); - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; - } + /* Temporary buffer for reparse data */ + struct reparse_buffer_disk rpbuf; - ret = parse_substitute_name(rpdata.substitute_name, - rpdata.substitute_name_nbytes, - rpdata.rptag); - if (ret < 0) - return 0; - stripped_nchars = ret; - target = rpdata.substitute_name; - target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar); - stripped_target = target + 6; - stripped_target_nchars = target_nchars - stripped_nchars; - - new_target = alloca((6 + extract_root_realpath_nchars + - stripped_target_nchars) * sizeof(utf16lechar)); - - p = new_target; - if (stripped_nchars == 6) { - /* Include \??\ prefix if it was present before */ - wmemcpy(p, L"\\??\\", 4); - p += 4; - } + /* Temporary buffer for reparse data of "fixed" absolute symbolic links + * and junctions */ + struct reparse_buffer_disk rpfixbuf; - /* Print name excludes the \??\ if present. */ - new_print_name = p; - if (stripped_nchars != 0) { - /* Get drive letter from real path to extract root, if a drive - * letter was present before. */ - *p++ = extract_root_realpath[0]; - *p++ = extract_root_realpath[1]; - } - /* Copy the rest of the extract root */ - wmemcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2); - p += extract_root_realpath_nchars - 2; - - /* Append the stripped target */ - wmemcpy(p, stripped_target, stripped_target_nchars); - p += stripped_target_nchars; - new_target_nchars = p - new_target; - new_print_name_nchars = p - new_print_name; - - if (new_target_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE || - new_print_name_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE) - { - ERROR("Path names too long to do reparse point fixup!"); - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; - } - rpdata.substitute_name = new_target; - rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar); - rpdata.print_name = new_print_name; - rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar); - return make_reparse_buffer(&rpdata, rpbuf); -} + /* Array of open handles to filesystem streams currently being written + */ + HANDLE open_handles[MAX_OPEN_STREAMS]; -/* Wrapper around the FSCTL_SET_REPARSE_POINT ioctl to set the reparse data on - * an extracted reparse point. */ -static int -win32_set_reparse_data(HANDLE h, - const struct wim_inode *inode, - const struct wim_lookup_table_entry *lte, - const wchar_t *path, - struct apply_args *args) + /* 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_set_short_name_failures; + + /* Number of files for which we couldn't remove the short name. */ + unsigned long num_remove_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) { - int ret; - u8 rpbuf[REPARSE_POINT_MAX_SIZE]; - DWORD bytesReturned; + /* Skip \\?\ prefix */ + if (!wcsncmp(path, L"\\\\?\\", 4)) + path += 4; - DEBUG("Setting reparse data on \"%ls\"", path); + /* 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]; - ret = wim_inode_get_reparse_data(inode, rpbuf); - if (ret) - return ret; + return L'\0'; +} - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX && - (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) && - !inode->i_not_rpfixed) - { - ret = win32_extract_try_rpfix(rpbuf, - args->target_realpath, - args->target_realpath_len); - if (ret) - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; +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; } - /* Set the reparse data on the open file using the - * FSCTL_SET_REPARSE_POINT ioctl. - * - * There are contradictions in Microsoft's documentation for this: - * - * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED, - * lpOverlapped is ignored." - * - * --- So setting lpOverlapped to NULL is okay since it's ignored. - * - * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an - * operation returns no output data and lpOutBuffer is NULL, - * DeviceIoControl makes use of lpBytesReturned. After such an - * operation, the value of lpBytesReturned is meaningless." - * - * --- So lpOverlapped not really ignored, as it affects another - * parameter. This is the actual behavior: lpBytesReturned must be - * specified, even though lpBytesReturned is documented as: - * - * "Not used with this operation; set to NULL." - */ - if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, rpbuf, - 8 + le16_to_cpu(*(u16*)(rpbuf + 4)), - NULL, 0, - &bytesReturned /* lpBytesReturned */, - NULL /* lpOverlapped */)) + if (!GetVolumeInformation(volume, NULL, 0, NULL, NULL, + vol_flags_ret, filesystem_name, + ARRAY_LEN(filesystem_name))) { - DWORD err = GetLastError(); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - { - args->num_soft_links_failed++; - if (args->num_soft_links_failed <= MAX_CREATE_SOFT_LINK_WARNINGS) { - WARNING("Can't set reparse data on \"%ls\": Access denied!\n" - " You may be trying to extract a symbolic " - "link without the\n" - " SeCreateSymbolicLink privilege, which by " - "default non-Administrator\n" - " accounts do not have.", path); - } - if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) { - WARNING("Suppressing further warnings regarding failure to extract\n" - " reparse points due to insufficient privileges..."); - } - } else { - ERROR("Failed to set reparse data on \"%ls\"", path); - win32_error(err); - if (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) - return WIMLIB_ERR_LINK; - else - return WIMLIB_ERR_WRITE; - } + win32_warning(GetLastError(), + L"Failed to get volume information for \"%ls\"", + target); + 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; } - return 0; } -/* Wrapper around the FSCTL_SET_COMPRESSION ioctl to change the - * FILE_ATTRIBUTE_COMPRESSED flag of a file or directory. */ +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); + static int -win32_set_compression_state(HANDLE hFile, USHORT format, const wchar_t *path) +report_dentry_apply_error(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx, int ret) { - DWORD bytesReturned; - if (!DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, - &format, sizeof(USHORT), - NULL, 0, - &bytesReturned, NULL)) - { - /* Could be a warning only, but we only call this if the volume - * supports compression. So I'm calling this an error. */ - DWORD err = GetLastError(); - ERROR("Failed to set compression flag on \"%ls\"", path); - win32_error(err); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; - else - return WIMLIB_ERR_WRITE; - } - return 0; + build_extraction_path(dentry, ctx); + return report_apply_error(&ctx->common, ret, current_path(ctx)); } -/* Wrapper around FSCTL_SET_SPARSE ioctl to set a file as sparse. */ -static int -win32_set_sparse(HANDLE hFile, const wchar_t *path) +static inline int +check_apply_error(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx, int ret) { - DWORD bytesReturned; - if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE, - NULL, 0, - NULL, 0, - &bytesReturned, NULL)) - { - /* Could be a warning only, but we only call this if the volume - * supports sparse files. So I'm calling this an error. */ - DWORD err = GetLastError(); - WARNING("Failed to set sparse flag on \"%ls\"", path); - win32_error(err); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; - else - return WIMLIB_ERR_WRITE; - } - return 0; + if (unlikely(ret)) + ret = report_dentry_apply_error(dentry, ctx, ret); + return ret; } -/* - * Sets the security descriptor on an extracted file. - */ static int -win32_set_security_data(const struct wim_inode *inode, - HANDLE hFile, - const wchar_t *path, - struct apply_args *args) +win32_get_supported_features(const wchar_t *target, + struct wim_features *supported_features) { - PSECURITY_DESCRIPTOR descriptor; - unsigned long n; - DWORD err; - const struct wim_security_data *sd; - - SECURITY_INFORMATION securityInformation = 0; + DWORD vol_flags; + bool short_names_supported; - void *owner = NULL; - void *group = NULL; - ACL *dacl = NULL; - ACL *sacl = NULL; + /* Query the features of the target volume. */ - BOOL owner_defaulted; - BOOL group_defaulted; - BOOL dacl_present; - BOOL dacl_defaulted; - BOOL sacl_present; - BOOL sacl_defaulted; + get_vol_flags(target, &vol_flags, &short_names_supported); - sd = wim_const_security_data(args->w); - descriptor = sd->descriptors[inode->i_security_id]; + supported_features->archive_files = 1; + supported_features->hidden_files = 1; + supported_features->system_files = 1; - GetSecurityDescriptorOwner(descriptor, &owner, &owner_defaulted); - if (owner) - securityInformation |= OWNER_SECURITY_INFORMATION; + if (vol_flags & FILE_FILE_COMPRESSION) + supported_features->compressed_files = 1; - GetSecurityDescriptorGroup(descriptor, &group, &group_defaulted); - if (group) - securityInformation |= GROUP_SECURITY_INFORMATION; - - GetSecurityDescriptorDacl(descriptor, &dacl_present, - &dacl, &dacl_defaulted); - if (dacl) - securityInformation |= DACL_SECURITY_INFORMATION; - - GetSecurityDescriptorSacl(descriptor, &sacl_present, - &sacl, &sacl_defaulted); - if (sacl) - securityInformation |= SACL_SECURITY_INFORMATION; - -again: - if (securityInformation == 0) - return 0; - if (SetSecurityInfo(hFile, SE_FILE_OBJECT, - securityInformation, owner, group, dacl, sacl)) - return 0; - err = GetLastError(); - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) - goto fail; - switch (err) { - case ERROR_PRIVILEGE_NOT_HELD: - if (securityInformation & SACL_SECURITY_INFORMATION) { - n = args->num_set_sacl_priv_notheld++; - securityInformation &= ~SACL_SECURITY_INFORMATION; - sacl = NULL; - if (n < MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) { - WARNING( -"We don't have enough privileges to set the full security\n" -" descriptor on \"%ls\"!\n", path); - if (args->num_set_sd_access_denied + - args->num_set_sacl_priv_notheld == 1) - { - WARNING("%ls", apply_access_denied_msg); - } - WARNING("Re-trying with SACL omitted.\n", path); - } else if (n == MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) { - WARNING( -"Suppressing further 'privileges not held' error messages when setting\n" -" security descriptors."); - } - goto again; - } - /* Fall through */ - case ERROR_INVALID_OWNER: - case ERROR_ACCESS_DENIED: - n = args->num_set_sd_access_denied++; - if (n < MAX_SET_SD_ACCESS_DENIED_WARNINGS) { - WARNING("Failed to set security descriptor on \"%ls\": " - "Access denied!\n", path); - if (args->num_set_sd_access_denied + - args->num_set_sacl_priv_notheld == 1) - { - WARNING("%ls", apply_access_denied_msg); - } - } else if (n == MAX_SET_SD_ACCESS_DENIED_WARNINGS) { - WARNING( -"Suppressing further access denied error messages when setting\n" -" security descriptors"); - } - return 0; - default: -fail: - ERROR("Failed to set security descriptor on \"%ls\"", path); - win32_error(err); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; - else - return WIMLIB_ERR_WRITE; + if (vol_flags & FILE_SUPPORTS_ENCRYPTION) { + supported_features->encrypted_files = 1; + supported_features->encrypted_directories = 1; } -} + supported_features->not_context_indexed_files = 1; -static int -win32_extract_chunk(const void *buf, size_t len, void *arg) -{ - HANDLE hStream = arg; + /* Don't do anything with FILE_SUPPORTS_SPARSE_FILES. */ - DWORD nbytes_written; - wimlib_assert(len <= 0xffffffff); + if (vol_flags & FILE_NAMED_STREAMS) + supported_features->named_data_streams = 1; - if (!WriteFile(hStream, buf, len, &nbytes_written, NULL) || - nbytes_written != len) - { - DWORD err = GetLastError(); - ERROR("WriteFile(): write error"); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - return 0; -} + if (vol_flags & FILE_SUPPORTS_HARD_LINKS) + supported_features->hard_links = 1; -static int -do_win32_extract_stream(HANDLE hStream, const struct wim_lookup_table_entry *lte) -{ - return extract_wim_resource(lte, wim_resource_size(lte), - win32_extract_chunk, hStream); -} + if (vol_flags & FILE_SUPPORTS_REPARSE_POINTS) + supported_features->reparse_points = 1; -struct win32_encrypted_extract_ctx { - const struct wim_lookup_table_entry *lte; - u64 offset; -}; + if (vol_flags & FILE_PERSISTENT_ACLS) + supported_features->security_descriptors = 1; -static DWORD WINAPI -win32_encrypted_import_cb(unsigned char *data, void *_ctx, - unsigned long *len_p) -{ - struct win32_encrypted_extract_ctx *ctx = _ctx; - unsigned long len = *len_p; - const struct wim_lookup_table_entry *lte = ctx->lte; + if (short_names_supported) + supported_features->short_names = 1; - len = min(len, wim_resource_size(lte) - ctx->offset); + supported_features->timestamps = 1; - if (read_partial_wim_resource_into_buf(lte, len, ctx->offset, data)) - return ERROR_READ_FAULT; + /* Note: Windows does not support case sensitive filenames! At least + * not without changing the registry and rebooting... */ - ctx->offset += len; - *len_p = len; - return ERROR_SUCCESS; + return 0; } -/* Create an encrypted file and extract the raw encrypted data to it. - * - * @path: Path to encrypted file to create. - * @lte: WIM lookup_table entry for the raw encrypted data. - * - * This is separate from do_win32_extract_stream() because the WIM is supposed - * to contain the *raw* encrypted data, which needs to be extracted ("imported") - * using the special APIs OpenEncryptedFileRawW(), WriteEncryptedFileRaw(), and - * CloseEncryptedFileRaw(). - * - * Returns 0 on success; nonzero on failure. - */ +/* Load the patterns from [PrepopulateList] of WimBootCompress.ini in the WIM + * image being extracted. */ static int -do_win32_extract_encrypted_stream(const wchar_t *path, - const struct wim_lookup_table_entry *lte) +load_prepopulate_pats(struct win32_apply_ctx *ctx) { - void *file_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; + } - DEBUG("Opening file \"%ls\" to extract raw encrypted data", path); + ret = read_full_stream_into_alloc_buf(lte, &buf); + if (ret) + return ret; - ret = OpenEncryptedFileRawW(path, CREATE_FOR_IMPORT, &file_ctx); - if (ret) { - ERROR("Failed to open \"%ls\" to write raw encrypted data", path); - win32_error(ret); - return WIMLIB_ERR_OPEN; + s = CALLOC(1, sizeof(struct string_set)); + if (!s) { + FREE(buf); + return WIMLIB_ERR_NOMEM; } - if (lte) { - struct win32_encrypted_extract_ctx ctx; + sec.name = T("PrepopulateList"); + sec.strings = s; - ctx.lte = lte; - ctx.offset = 0; - ret = WriteEncryptedFileRaw(win32_encrypted_import_cb, &ctx, file_ctx); - if (ret == ERROR_SUCCESS) { - ret = 0; - } else { - ret = WIMLIB_ERR_WRITE; - ERROR("Failed to extract encrypted file \"%ls\"", path); - } + 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; } - CloseEncryptedFileRaw(file_ctx); - return ret; + ctx->wimboot.prepopulate_pats = s; + ctx->wimboot.mem_prepopulate_pats = mem; + return 0; } +/* Returns %true if the specified absolute path to a file in the WIM image + * matches a pattern in [PrepopulateList] of WimBootCompress.ini. Otherwise + * returns %false. */ static bool -path_is_root_of_drive(const wchar_t *path) +in_prepopulate_list(const wchar_t *path, size_t path_nchars, + const struct win32_apply_ctx *ctx) { - if (!*path) + const struct string_set *pats = ctx->wimboot.prepopulate_pats; + + if (!pats || !pats->num_strings) return false; - if (*path != L'/' && *path != L'\\') { - if (*(path + 1) == L':') - path += 2; - else - return false; - } - while (*path == L'/' || *path == L'\\') - path++; - return (*path == L'\0'); + return match_pattern_list(path, path_nchars, pats); } -static inline DWORD -win32_mask_attributes(DWORD i_attributes) +/* Returns %true if the specified absolute path to a file in the WIM image can + * be subject to external backing when extracted. Otherwise returns %false. */ +static bool +can_externally_back_path(const wchar_t *path, size_t path_nchars, + const struct win32_apply_ctx *ctx) { - return i_attributes & ~(FILE_ATTRIBUTE_SPARSE_FILE | - FILE_ATTRIBUTE_COMPRESSED | - FILE_ATTRIBUTE_REPARSE_POINT | - FILE_ATTRIBUTE_DIRECTORY | - FILE_ATTRIBUTE_ENCRYPTED | - FILE_FLAG_DELETE_ON_CLOSE | - FILE_FLAG_NO_BUFFERING | - FILE_FLAG_OPEN_NO_RECALL | - FILE_FLAG_OVERLAPPED | - FILE_FLAG_RANDOM_ACCESS | - /*FILE_FLAG_SESSION_AWARE |*/ - FILE_FLAG_SEQUENTIAL_SCAN | - FILE_FLAG_WRITE_THROUGH); -} + if (in_prepopulate_list(path, path_nchars, ctx)) + return false; -static inline DWORD -win32_get_create_flags_and_attributes(DWORD i_attributes) -{ - /* - * Some attributes cannot be set by passing them to CreateFile(). In - * particular: - * - * FILE_ATTRIBUTE_DIRECTORY: - * CreateDirectory() must be called instead of CreateFile(). - * - * FILE_ATTRIBUTE_SPARSE_FILE: - * Needs an ioctl. - * See: win32_set_sparse(). - * - * FILE_ATTRIBUTE_COMPRESSED: - * Not clear from the documentation, but apparently this needs an - * ioctl as well. - * See: win32_set_compressed(). - * - * FILE_ATTRIBUTE_REPARSE_POINT: - * Needs an ioctl, with the reparse data specified. - * See: win32_set_reparse_data(). - * - * In addition, clear any file flags in the attributes that we don't - * want, but also specify FILE_FLAG_OPEN_REPARSE_POINT and - * FILE_FLAG_BACKUP_SEMANTICS as we are a backup application. - */ - return win32_mask_attributes(i_attributes) | - FILE_FLAG_OPEN_REPARSE_POINT | - FILE_FLAG_BACKUP_SEMANTICS; + /* Since we attempt to modify the SYSTEM registry after it's extracted + * (see end_wimboot_extraction()), it can't be extracted as externally + * backed. This extends to associated files such as SYSTEM.LOG that + * also must be writable in order to write to the registry. Normally, + * SYSTEM is in [PrepopulateList], and the SYSTEM.* files match patterns + * in [ExclusionList] and therefore are not captured in the WIM at all. + * However, a WIM that wasn't specifically captured in "WIMBoot mode" + * may contain SYSTEM.* files. So to make things "just work", hard-code + * the pattern. */ + if (match_path(path, path_nchars, L"\\Windows\\System32\\config\\SYSTEM*", + OS_PREFERRED_PATH_SEPARATOR, false)) + return false; + + return true; } -/* Set compression and/or sparse attributes on a stream, if supported by the - * volume. */ +#define WIM_BACKING_NOT_ENABLED -1 +#define WIM_BACKING_NOT_POSSIBLE -2 +#define WIM_BACKING_EXCLUDED -3 + static int -win32_set_special_stream_attributes(HANDLE hFile, const struct wim_inode *inode, - struct wim_lookup_table_entry *unnamed_stream_lte, - const wchar_t *path, unsigned vol_flags) +will_externally_back_inode(struct wim_inode *inode, struct win32_apply_ctx *ctx, + const struct wim_dentry **excluded_dentry_ret) { + struct list_head *next; + struct wim_dentry *dentry; + struct wim_lookup_table_entry *stream; int ret; - if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) { - if (vol_flags & FILE_FILE_COMPRESSION) { - ret = win32_set_compression_state(hFile, - COMPRESSION_FORMAT_DEFAULT, - path); - if (ret) - return ret; - } else { - DEBUG("Cannot set compression attribute on \"%ls\": " - "volume does not support transparent compression", - path); - } - } + if (inode->i_can_externally_back) + return 0; - if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) { - if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) { - DEBUG("Setting sparse flag on \"%ls\"", path); - ret = win32_set_sparse(hFile, path); - if (ret) - return ret; - } else { - DEBUG("Cannot set sparse attribute on \"%ls\": " - "volume does not support sparse files", - path); + /* This may do redundant checks because the cached value + * i_can_externally_back is 2-state (as opposed to 3-state: + * unknown/no/yes). But most files can be externally backed, so this + * way is fine. */ + + if (inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_REPARSE_POINT | + FILE_ATTRIBUTE_ENCRYPTED)) + return WIM_BACKING_NOT_POSSIBLE; + + stream = inode_unnamed_lte_resolved(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; + + /* + * We need to check the patterns in [PrepopulateList] against every name + * of the inode, in case any of them match. + */ + next = inode->i_extraction_aliases.next; + do { + dentry = list_entry(next, struct wim_dentry, + d_extraction_alias_node); + + ret = calculate_dentry_full_path(dentry); + if (ret) + return ret; + + if (!can_externally_back_path(dentry->_full_path, + wcslen(dentry->_full_path), ctx)) + { + if (excluded_dentry_ret) + *excluded_dentry_ret = dentry; + return WIM_BACKING_EXCLUDED; } - } + next = next->next; + } while (next != &inode->i_extraction_aliases); + + inode->i_can_externally_back = 1; return 0; } -/* Pre-create directories; extract encrypted streams */ +/* + * 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_begin_extract_unnamed_stream(const struct wim_inode *inode, - const struct wim_lookup_table_entry *lte, - const wchar_t *path, - DWORD *creationDisposition_ret, - unsigned int vol_flags) +win32_will_externally_back(struct wim_dentry *dentry, struct apply_ctx *_ctx) { - DWORD err; - int ret; + struct win32_apply_ctx *ctx = (struct win32_apply_ctx *)_ctx; - /* Directories must be created with CreateDirectoryW(). Then the call - * to CreateFileW() will merely open the directory that was already - * created rather than creating a new file. */ - if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY && - !path_is_root_of_drive(path)) { - if (!CreateDirectoryW(path, NULL)) { - err = GetLastError(); - if (err != ERROR_ALREADY_EXISTS) { - ERROR("Failed to create directory \"%ls\"", - path); - win32_error(err); - return WIMLIB_ERR_MKDIR; - } - } - DEBUG("Created directory \"%ls\"", path); - *creationDisposition_ret = OPEN_EXISTING; - } - if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED && - vol_flags & FILE_SUPPORTS_ENCRYPTION) - { - if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) { - unsigned remaining_sharing_violations = 100; - while (!EncryptFile(path)) { - if (remaining_sharing_violations && - err == ERROR_SHARING_VIOLATION) - { - WARNING("Couldn't encrypt directory \"%ls\" " - "due to sharing violation; re-trying " - "after 100 ms", path); - Sleep(100); - remaining_sharing_violations--; - } else { - err = GetLastError(); - ERROR("Failed to encrypt directory \"%ls\"", - path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - } - } else { - ret = do_win32_extract_encrypted_stream(path, lte); - if (ret) - return ret; - DEBUG("Extracted encrypted file \"%ls\"", path); - } - *creationDisposition_ret = OPEN_EXISTING; - } + if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) + return WIM_BACKING_NOT_ENABLED; - /* Set file attributes if we created the file. Otherwise, we haven't - * created the file set and we will set the attributes in the call to - * CreateFileW(). - * - * The FAT filesystem does not let you change the attributes of the root - * directory, so treat that as a special case and do not set attributes. - * */ - if (*creationDisposition_ret == OPEN_EXISTING && - !path_is_root_of_drive(path)) - { - if (!SetFileAttributesW(path, - win32_mask_attributes(inode->i_attributes))) - { - err = GetLastError(); - ERROR("Failed to set attributes on \"%ls\"", path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - } - return 0; + if (!ctx->wimboot.tried_to_load_prepopulate_list) + if (load_prepopulate_pats(ctx) == WIMLIB_ERR_NOMEM) + return WIMLIB_ERR_NOMEM; + + return will_externally_back_inode(dentry->d_inode, ctx, NULL); } -/* Set security descriptor and extract stream data or reparse data (skip the - * unnamed data stream of encrypted files, which was already extracted). */ static int -win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry, - const struct wim_lookup_table_entry *lte, - const wchar_t *stream_path, - const wchar_t *stream_name_utf16, - struct apply_args *args) +set_external_backing(HANDLE h, struct wim_inode *inode, struct win32_apply_ctx *ctx) { - int ret = 0; - const struct wim_inode *inode = dentry->d_inode; - if (stream_name_utf16 == NULL) { - /* Unnamed stream. */ - - /* Set security descriptor, unless the extract_flags indicate - * not to or the volume does not supported it. Note that this - * is only done when the unnamed stream is being extracted, as - * security descriptors are per-file and not per-stream. */ - if (inode->i_security_id >= 0 && - !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS) - && (args->vol_flags & FILE_PERSISTENT_ACLS)) - { - ret = win32_set_security_data(inode, h, stream_path, args); - if (ret) - return ret; - } + int ret; + const struct wim_dentry *excluded_dentry; - /* Handle reparse points. The data for them needs to be set - * using a special ioctl. Note that the reparse point may have - * been created using CreateFileW() in the case of - * non-directories or CreateDirectoryW() in the case of - * directories; but the ioctl works either way. Also, it is - * only this step that actually sets the - * FILE_ATTRIBUTE_REPARSE_POINT, as it is not valid to set it - * using SetFileAttributesW() or CreateFileW(). - * - * If the volume does not support reparse points we simply - * ignore the reparse data. (N.B. the code currently doesn't - * actually reach this case because reparse points are skipped - * entirely on such volumes.) */ - if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { - if (args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS) { - ret = win32_set_reparse_data(h, inode, - lte, stream_path, - args); - if (ret) - return ret; - } else { - DEBUG("Cannot set reparse data on \"%ls\": volume " - "does not support reparse points", stream_path); - } - } else if (lte != NULL && - !(args->vol_flags & FILE_SUPPORTS_ENCRYPTION && - inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)) - { - /* Extract the data of the unnamed stream, unless the - * lookup table entry is NULL (indicating an empty - * stream for which no data needs to be extracted), or - * the stream is encrypted and therefore was already - * extracted as a special case. */ - ret = do_win32_extract_stream(h, lte); - if (ret) - return ret; - } + ret = will_externally_back_inode(inode, ctx, &excluded_dentry); + if (ret > 0) /* Error. */ + return ret; - if (dentry_has_short_name(dentry)) - SetFileShortNameW(h, dentry->short_name); - else if (running_on_windows_7_or_later()) - SetFileShortNameW(h, L""); + if (ret < 0 && ret != WIM_BACKING_EXCLUDED) + return 0; /* Not externally backing, other than due to exclusion. */ + + if (unlikely(ret == WIM_BACKING_EXCLUDED)) { + /* Not externally backing due to exclusion. */ + union wimlib_progress_info info; + + build_extraction_path(excluded_dentry, ctx); + + info.wimboot_exclude.path_in_wim = excluded_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 { - /* Extract the data for a named data stream. */ - if (lte != NULL) { - DEBUG("Extracting named data stream \"%ls\" (len = %"PRIu64")", - stream_path, wim_resource_size(lte)); - ret = do_win32_extract_stream(h, lte); + /* Externally backing. */ + if (unlikely(!wimboot_set_pointer(h, + inode_unnamed_lte_resolved(inode), + ctx->wimboot.data_source_id, + ctx->wimboot.wim_lookup_table_hash, + ctx->wimboot.wof_running))) + { + const DWORD err = GetLastError(); + + build_extraction_path(inode_first_extraction_dentry(inode), ctx); + win32_error(err, L"\"%ls\": Couldn't set WIMBoot pointer data", + current_path(ctx)); + return WIMLIB_ERR_WIMBOOT; } + return 0; } - return ret; } +/* Calculates the SHA-1 message digest of the WIM's lookup table. */ static int -win32_decrypt_file(HANDLE open_handle, const wchar_t *path) +hash_lookup_table(WIMStruct *wim, u8 hash[SHA1_HASH_SIZE]) { - DWORD err; - /* We cannot call DecryptFileW() while there is an open handle to the - * file. So close it first. */ - if (!CloseHandle(open_handle)) { - err = GetLastError(); - ERROR("Failed to close handle for \"%ls\"", path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - if (!DecryptFileW(path, 0 /* reserved parameter; set to 0 */)) { - err = GetLastError(); - ERROR("Failed to decrypt file \"%ls\"", path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - return 0; + return wim_reshdr_to_hash(&wim->hdr.lookup_table_reshdr, wim, hash); } -/* - * Create and extract a stream to a file, or create a directory, using the - * Windows API. - * - * This handles reparse points, directories, alternate data streams, encrypted - * files, compressed files, etc. - * - * @dentry: WIM dentry for the file or directory being extracted. - * - * @path: Path to extract the file to. - * - * @stream_name_utf16: - * Name of the stream, or NULL if the stream is unnamed. This will - * be called with a NULL stream_name_utf16 before any non-NULL - * stream_name_utf16's. - * - * @lte: WIM lookup table entry for the stream. May be NULL to indicate - * a stream of length 0. - * - * @args: Additional apply context, including flags indicating supported - * volume features. - * - * Returns 0 on success; nonzero on failure. - */ +/* 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 -win32_extract_stream(const struct wim_dentry *dentry, - const wchar_t *path, - const wchar_t *stream_name_utf16, - struct wim_lookup_table_entry *lte, - struct apply_args *args) +start_wimboot_extraction(struct win32_apply_ctx *ctx) { - wchar_t *stream_path; - HANDLE h; int ret; - DWORD err; - DWORD creationDisposition = CREATE_ALWAYS; - DWORD requestedAccess; - BY_HANDLE_FILE_INFORMATION file_info; - unsigned remaining_sharing_violations = 1000; - const struct wim_inode *inode = dentry->d_inode; + WIMStruct *wim = ctx->common.wim; - if (stream_name_utf16) { - /* Named stream. Create a buffer that contains the UTF-16LE - * string [./]path:stream_name_utf16. This is needed to - * create and open the stream using CreateFileW(). I'm not - * aware of any other APIs to do this. Note: the '$DATA' suffix - * seems to be unneeded. Additional note: a "./" prefix needs - * to be added when the path is not absolute to avoid ambiguity - * with drive letters. */ - size_t stream_path_nchars; - size_t path_nchars; - size_t stream_name_nchars; - const wchar_t *prefix; - - path_nchars = wcslen(path); - stream_name_nchars = wcslen(stream_name_utf16); - stream_path_nchars = path_nchars + 1 + stream_name_nchars; - if (path[0] != cpu_to_le16(L'\0') && - path[0] != cpu_to_le16(L'/') && - path[0] != cpu_to_le16(L'\\') && - path[1] != cpu_to_le16(L':')) - { - prefix = L"./"; - stream_path_nchars += 2; - } else { - prefix = L""; - } - stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t)); - swprintf(stream_path, L"%ls%ls:%ls", - prefix, path, stream_name_utf16); - } else { - /* Unnamed stream; its path is just the path to the file itself. - * */ - stream_path = (wchar_t*)path; + if (!ctx->wimboot.tried_to_load_prepopulate_list) + if (load_prepopulate_pats(ctx) == WIMLIB_ERR_NOMEM) + return WIMLIB_ERR_NOMEM; - ret = win32_begin_extract_unnamed_stream(inode, lte, path, - &creationDisposition, - args->vol_flags); - if (ret) - goto fail; - } + if (!wim_info_get_wimboot(wim->wim_info, wim->current_image)) + WARNING("Image is not marked as WIMBoot compatible!"); - DEBUG("Opening \"%ls\"", stream_path); - /* DELETE access is needed for SetFileShortNameW(), for some reason. */ - requestedAccess = GENERIC_READ | GENERIC_WRITE | DELETE | - ACCESS_SYSTEM_SECURITY; -try_open_again: - /* Open the stream to be extracted. Depending on what we have set - * creationDisposition to, we may be creating this for the first time, - * or we may be opening on existing stream we already created using - * CreateDirectoryW() or OpenEncryptedFileRawW(). */ - h = CreateFileW(stream_path, - requestedAccess, - FILE_SHARE_READ, - NULL, - creationDisposition, - win32_get_create_flags_and_attributes(inode->i_attributes), - NULL); - if (h == INVALID_HANDLE_VALUE) { - err = GetLastError(); - if (err == ERROR_ACCESS_DENIED && - path_is_root_of_drive(stream_path)) - { - ret = 0; - goto out; - } - if ((err == ERROR_PRIVILEGE_NOT_HELD || - err == ERROR_ACCESS_DENIED) && - (requestedAccess & ACCESS_SYSTEM_SECURITY)) - { - /* Try opening the file again without privilege to - * modify SACL. */ - requestedAccess &= ~ACCESS_SYSTEM_SECURITY; - goto try_open_again; - } - if (err == ERROR_SHARING_VIOLATION) { - if (remaining_sharing_violations) { - --remaining_sharing_violations; - /* This can happen when restoring encrypted directories - * for some reason. Probably a bug in EncryptFile(). */ - WARNING("Couldn't open \"%ls\" due to sharing violation; " - "re-trying after 100ms", stream_path); - Sleep(100); - goto try_open_again; - } else { - ERROR("Too many sharing violations; giving up..."); - } - } else { - if (creationDisposition == OPEN_EXISTING) - ERROR("Failed to open \"%ls\"", stream_path); - else - ERROR("Failed to create \"%ls\"", stream_path); - win32_error(err); - } - ret = WIMLIB_ERR_OPEN; - goto fail; - } + ret = hash_lookup_table(ctx->common.wim, + ctx->wimboot.wim_lookup_table_hash); + if (ret) + return ret; - /* Check the attributes of the file we just opened, and remove - * encryption or compression if either was set by default but is not - * supposed to be set based on the WIM inode attributes. */ - if (!GetFileInformationByHandle(h, &file_info)) { - err = GetLastError(); - ERROR("Failed to get attributes of \"%ls\"", stream_path); - win32_error(err); - ret = WIMLIB_ERR_STAT; - goto fail_close_handle; - } + 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); +} - /* Remove encryption? */ - if (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED && - !(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)) - { - /* File defaulted to encrypted due to being in an encrypted - * directory, but is not actually supposed to be encrypted. - * - * This is a workaround, because I'm not aware of any way to - * directly (e.g. with CreateFileW()) create an unencrypted file - * in a directory with FILE_ATTRIBUTE_ENCRYPTED set. */ - ret = win32_decrypt_file(h, stream_path); - if (ret) - goto fail; /* win32_decrypt_file() closed the handle. */ - creationDisposition = OPEN_EXISTING; - goto try_open_again; - } +static void +build_win32_extraction_path(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx); - /* Remove compression? */ - if (file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED && - !(inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)) - { - /* Similar to the encrypted case, above, if the file defaulted - * to compressed due to being in an compressed directory, but is - * not actually supposed to be compressed, explicitly set the - * compression format to COMPRESSION_FORMAT_NONE. */ - ret = win32_set_compression_state(h, COMPRESSION_FORMAT_NONE, - stream_path); - if (ret) - goto fail_close_handle; - } +/* 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; - /* Set compression and/or sparse attributes if needed */ - ret = win32_set_special_stream_attributes(h, inode, lte, path, - args->vol_flags); + 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. */ + win32_warning(res, L"Failed to set \\Setup: dword \"WimBoot\"=1 " + "value in registry hive \"%ls\"", + ctx->pathbuf.Buffer); + } +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; +} + +/* Open handle to the target directory if it is not already open. If the target + * directory does not exist, this creates it. */ +static int +open_target_directory(struct win32_apply_ctx *ctx) +{ + NTSTATUS status; + + if (ctx->h_target) + return 0; + + ctx->attr.Length = sizeof(ctx->attr); + ctx->attr.RootDirectory = NULL; + 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)) { + winnt_error(status, L"Can't open or create directory \"%ls\"", + ctx->common.target); + return WIMLIB_ERR_OPENDIR; + } + ctx->attr.RootDirectory = ctx->h_target; + ctx->attr.ObjectName = &ctx->pathbuf; + return 0; +} + +static void +close_target_directory(struct win32_apply_ctx *ctx) +{ + if (ctx->h_target) { + (*func_NtClose)(ctx->h_target); + ctx->h_target = NULL; + ctx->attr.RootDirectory = NULL; + } +} + +/* + * 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; + size_t path_max; + + ret = win32_path_to_nt_path(ctx->common.target, &ctx->target_ntpath); + if (ret) + return ret; + + ret = open_target_directory(ctx); + if (ret) + return ret; + + path_max = compute_path_max(dentry_list); + /* Add some extra for building Win32 paths for the file encryption APIs, + * and ensure we have at least enough to potentially use a 8.3 name for + * the last component. */ + path_max += max(2 + (ctx->target_ntpath.Length / sizeof(wchar_t)), + 8 + 1 + 3); + + ctx->pathbuf.MaximumLength = path_max * sizeof(wchar_t); + ctx->pathbuf.Buffer = MALLOC(ctx->pathbuf.MaximumLength); + if (!ctx->pathbuf.Buffer) + return WIMLIB_ERR_NOMEM; + + 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; + + winnt_error(status, L"Can't %s compression attribute on \"%ls\"", + (compressed ? "set" : "clear"), current_path(ctx)); + return WIMLIB_ERR_SET_ATTRIBUTES; +} + +/* 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: + win32_warning(GetLastError(), + L"Failed to enable short name support on %ls", + volume + 4); + return false; +} + +static NTSTATUS +remove_conflicting_short_name(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) +{ + wchar_t *name; + wchar_t *end; + NTSTATUS status; + HANDLE h; + size_t bufsize = offsetof(FILE_NAME_INFORMATION, FileName) + + (13 * sizeof(wchar_t)); + u8 buf[bufsize] _aligned_attribute(8); + bool retried = false; + FILE_NAME_INFORMATION *info = (FILE_NAME_INFORMATION *)buf; + + memset(buf, 0, bufsize); + + /* Build the path with the short name. */ + name = &ctx->pathbuf.Buffer[ctx->pathbuf.Length / sizeof(wchar_t)]; + while (name != ctx->pathbuf.Buffer && *(name - 1) != L'\\') + name--; + end = mempcpy(name, dentry->short_name, dentry->short_name_nbytes); + ctx->pathbuf.Length = ((u8 *)end - (u8 *)ctx->pathbuf.Buffer); + + /* Open the conflicting file (by short name). */ + status = (*func_NtOpenFile)(&h, GENERIC_WRITE | DELETE, + &ctx->attr, &ctx->iosb, + FILE_SHARE_VALID_FLAGS, + FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT); + if (!NT_SUCCESS(status)) { + winnt_warning(status, L"Can't open \"%ls\"", current_path(ctx)); + goto out; + } + +#if 0 + WARNING("Overriding conflicting short name; path=\"%ls\"", + current_path(ctx)); +#endif + + /* Try to remove the short name on the conflicting file. */ + +retry: + status = (*func_NtSetInformationFile)(h, &ctx->iosb, info, bufsize, + FileShortNameInformation); + + if (status == STATUS_INVALID_PARAMETER && !retried) { + + /* Microsoft forgot to make it possible to remove short names + * until Windows 7. Oops. Use a random short name instead. */ + + info->FileNameLength = 12 * sizeof(wchar_t); + for (int i = 0; i < 8; i++) + info->FileName[i] = 'A' + (rand() % 26); + info->FileName[8] = L'.'; + info->FileName[9] = L'W'; + info->FileName[10] = L'L'; + info->FileName[11] = L'B'; + info->FileName[12] = L'\0'; + retried = true; + goto retry; + } + (*func_NtClose)(h); +out: + build_extraction_path(dentry, ctx); + return status; +} + +/* 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; + bool tried_to_remove_existing = false; + + 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; + } + } + + /* + * Short names can conflict in several cases: + * + * - a file being extracted has a short name conflicting with an + * existing file + * + * - a file being extracted has a short name conflicting with another + * file being extracted (possible, but shouldn't happen) + * + * - a file being extracted has a short name that conflicts with the + * automatically generated short name of a file we previously + * extracted, but failed to set the short name for. Sounds unlikely, + * but this actually does happen fairly often on versions of Windows + * prior to Windows 7 because they do not support removing short names + * from files. + */ + if (unlikely(status == STATUS_OBJECT_NAME_COLLISION) && + dentry->short_name_nbytes && !tried_to_remove_existing) + { + tried_to_remove_existing = true; + status = remove_conflicting_short_name(dentry, ctx); + if (NT_SUCCESS(status)) + 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)) { + if (dentry->short_name_nbytes) + ctx->num_set_short_name_failures++; + else + ctx->num_remove_short_name_failures++; + return 0; + } + + winnt_error(status, L"Can't set short name on \"%ls\"", current_path(ctx)); + 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); +} + +static int +delete_file_or_stream(struct win32_apply_ctx *ctx) +{ + NTSTATUS status; + HANDLE h; + FILE_DISPOSITION_INFORMATION disposition_info; + FILE_BASIC_INFORMATION basic_info; + bool retried = false; + + status = do_create_file(&h, + DELETE, + NULL, + 0, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, + ctx); + if (unlikely(!NT_SUCCESS(status))) { + winnt_error(status, L"Can't open \"%ls\" for deletion", + current_path(ctx)); + return WIMLIB_ERR_OPEN; + } + +retry: + disposition_info.DoDeleteFile = TRUE; + status = (*func_NtSetInformationFile)(h, &ctx->iosb, + &disposition_info, + sizeof(disposition_info), + FileDispositionInformation); + (*func_NtClose)(h); + if (likely(NT_SUCCESS(status))) + return 0; + + if (status == STATUS_CANNOT_DELETE && !retried) { + /* Clear file attributes and try again. This is necessary for + * FILE_ATTRIBUTE_READONLY files. */ + status = do_create_file(&h, + FILE_WRITE_ATTRIBUTES | DELETE, + NULL, + 0, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, + ctx); + if (!NT_SUCCESS(status)) { + winnt_error(status, + L"Can't open \"%ls\" to reset attributes", + current_path(ctx)); + return WIMLIB_ERR_OPEN; + } + memset(&basic_info, 0, sizeof(basic_info)); + basic_info.FileAttributes = FILE_ATTRIBUTE_NORMAL; + status = (*func_NtSetInformationFile)(h, &ctx->iosb, + &basic_info, + sizeof(basic_info), + FileBasicInformation); + if (!NT_SUCCESS(status)) { + winnt_error(status, + L"Can't reset file attributes on \"%ls\"", + current_path(ctx)); + (*func_NtClose)(h); + return WIMLIB_ERR_SET_ATTRIBUTES; + } + retried = true; + goto retry; + } + winnt_error(status, L"Can't delete \"%ls\"", current_path(ctx)); + return WIMLIB_ERR_OPEN; +} + +/* + * Create a nondirectory file or named data stream at the current path, + * superseding any that already exists at that path. If successful, return an + * open handle to the file or named data stream. + */ +static int +supersede_file_or_stream(struct win32_apply_ctx *ctx, HANDLE *h_ret) +{ + NTSTATUS status; + bool retried = false; + + /* FILE_ATTRIBUTE_SYSTEM is needed to ensure that + * FILE_ATTRIBUTE_ENCRYPTED doesn't get set before we want it to be. */ +retry: + status = do_create_file(h_ret, + GENERIC_READ | GENERIC_WRITE | DELETE, + NULL, + FILE_ATTRIBUTE_SYSTEM, + FILE_CREATE, + FILE_NON_DIRECTORY_FILE, + ctx); + if (likely(NT_SUCCESS(status))) + return 0; + + /* STATUS_OBJECT_NAME_COLLISION means that the file or stream already + * exists. Delete the existing file or stream, then try again. + * + * Note: we don't use FILE_OVERWRITE_IF or FILE_SUPERSEDE because of + * problems with certain file attributes, especially + * FILE_ATTRIBUTE_ENCRYPTED. FILE_SUPERSEDE is also broken in the + * Windows PE ramdisk. */ + if (status == STATUS_OBJECT_NAME_COLLISION && !retried) { + int ret = delete_file_or_stream(ctx); + if (ret) + return ret; + retried = true; + goto retry; + } + winnt_error(status, L"Can't create \"%ls\"", current_path(ctx)); + return WIMLIB_ERR_OPEN; +} + +/* 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; + 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; + HANDLE h; + + entry = &inode->i_ads_entries[i]; + + /* Not named? */ + if (!entry->stream_name_nbytes) + continue; + + /* Not empty? */ + if (entry->lte) + continue; + + build_extraction_path_with_ads(dentry, ctx, + entry->stream_name, + entry->stream_name_nbytes / + sizeof(wchar_t)); + path_modified = true; + ret = supersede_file_or_stream(ctx, &h); + if (ret) + break; + (*func_NtClose)(h); + } + /* Restore the path to the dentry itself */ + if (path_modified) + build_extraction_path(dentry, ctx); + return ret; +} + +/* + * Creates the directory named by @dentry, or uses an existing directory at that + * location. If necessary, sets the short name and/or fixes compression and + * encryption attributes. + * + * Returns 0, WIMLIB_ERR_MKDIR, or WIMLIB_ERR_SET_SHORT_NAME. + */ +static int +create_directory(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx) +{ + HANDLE h; + NTSTATUS status; + int ret; + + /* DELETE is needed for set_short_name(); GENERIC_READ and GENERIC_WRITE + * are needed for adjust_compression_attribute(). + * + * FILE_ATTRIBUTE_SYSTEM is needed to ensure that + * FILE_ATTRIBUTE_ENCRYPTED doesn't get set before we want it to be. */ + status = create_file(&h, GENERIC_READ | GENERIC_WRITE | DELETE, NULL, + FILE_ATTRIBUTE_SYSTEM, FILE_OPEN_IF, FILE_DIRECTORY_FILE, + dentry, ctx); + if (!NT_SUCCESS(status)) { + winnt_error(status, L"Can't create directory \"%ls\"", + current_path(ctx)); + return WIMLIB_ERR_MKDIR; + } + + ret = set_short_name(h, dentry, ctx); + + if (!ret) + ret = adjust_compression_attribute(h, dentry, ctx); + + (*func_NtClose)(h); + return ret; +} + +/* + * Create all the directories being extracted, other than the target directory + * itself. + * + * Note: we don't honor directory hard links. However, we don't allow them to + * exist in WIM images anyway (see inode_fixup.c). + */ +static int +create_directories(struct list_head *dentry_list, + struct win32_apply_ctx *ctx) +{ + const struct wim_dentry *dentry; + int ret; + + list_for_each_entry(dentry, dentry_list, d_extraction_list_node) { + + if (!(dentry->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)) + continue; + + /* Note: Here we include files with + * FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT, but we + * wait until later to actually set the reparse data. */ + + /* If the root dentry is being extracted, it was already done so + * in prepare_target(). */ + if (!dentry_is_root(dentry)) { + ret = create_directory(dentry, ctx); + ret = check_apply_error(dentry, ctx, ret); + if (ret) + return ret; + + ret = create_any_empty_ads(dentry, ctx); + ret = check_apply_error(dentry, ctx, ret); + if (ret) + return ret; + } + + ret = report_file_created(&ctx->common); + if (ret) + return ret; + } + return 0; +} + +/* + * Creates the nondirectory file named by @dentry. + * + * On success, returns an open handle to the file in @h_ret, with GENERIC_READ, + * GENERIC_WRITE, and DELETE access. Also, the path to the file will be saved + * in ctx->pathbuf. On failure, returns an error code. + */ +static int +create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx) +{ + int ret; + HANDLE h; + + build_extraction_path(dentry, ctx); + + ret = supersede_file_or_stream(ctx, &h); + if (ret) + goto out; + + ret = adjust_compression_attribute(h, dentry, ctx); + if (ret) + goto out_close; + + ret = create_any_empty_ads(dentry, ctx); + if (ret) + goto out_close; + + *h_ret = h; + return 0; + +out_close: + (*func_NtClose)(h); +out: + return ret; +} + +/* Creates a hard link at the location named by @dentry to the file represented + * by the open handle @h. Or, if the target volume does not support hard links, + * create a separate file instead. */ +static int +create_link(HANDLE h, const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx) +{ + if (ctx->common.supported_features.hard_links) { + + build_extraction_path(dentry, ctx); + + size_t bufsize = offsetof(FILE_LINK_INFORMATION, FileName) + + ctx->pathbuf.Length + sizeof(wchar_t); + u8 buf[bufsize] _aligned_attribute(8); + FILE_LINK_INFORMATION *info = (FILE_LINK_INFORMATION *)buf; + NTSTATUS status; + + info->ReplaceIfExists = TRUE; + info->RootDirectory = ctx->attr.RootDirectory; + info->FileNameLength = ctx->pathbuf.Length; + memcpy(info->FileName, ctx->pathbuf.Buffer, ctx->pathbuf.Length); + info->FileName[info->FileNameLength / 2] = L'\0'; + + /* Note: the null terminator isn't actually necessary, + * but if you don't add the extra character, you get + * STATUS_INFO_LENGTH_MISMATCH when FileNameLength + * happens to be 2 */ + + status = (*func_NtSetInformationFile)(h, &ctx->iosb, + info, bufsize, + FileLinkInformation); + if (NT_SUCCESS(status)) + return 0; + winnt_error(status, L"Failed to create link \"%ls\"", + current_path(ctx)); + return WIMLIB_ERR_LINK; + } else { + HANDLE h2; + int ret; + + ret = create_nondirectory_inode(&h2, dentry, ctx); + if (ret) + return ret; + + (*func_NtClose)(h2); + return 0; + } +} + +/* Given an inode (represented by the open handle @h) for which one link has + * been created (named by @first_dentry), create the other links. + * + * Or, if the target volume does not support hard links, create separate files. + * + * Note: This uses ctx->pathbuf and does not reset it. + */ +static int +create_links(HANDLE h, const struct wim_dentry *first_dentry, + struct win32_apply_ctx *ctx) +{ + const struct wim_inode *inode; + const struct list_head *next; + const struct wim_dentry *dentry; + int ret; + + inode = first_dentry->d_inode; + next = inode->i_extraction_aliases.next; + do { + dentry = list_entry(next, struct wim_dentry, + d_extraction_alias_node); + if (dentry != first_dentry) { + ret = create_link(h, dentry, ctx); + if (ret) + return ret; + } + next = next->next; + } while (next != &inode->i_extraction_aliases); + return 0; +} + +/* Create a nondirectory file, including all links. */ +static int +create_nondirectory(struct wim_inode *inode, struct win32_apply_ctx *ctx) +{ + struct wim_dentry *first_dentry; + HANDLE h; + int ret; + + first_dentry = first_extraction_alias(inode); + + /* Create first link. */ + ret = create_nondirectory_inode(&h, first_dentry, ctx); + if (ret) + return ret; + + /* Set short name. */ + ret = set_short_name(h, first_dentry, ctx); + + /* Create additional links, OR if hard links are not supported just + * create more files. */ + if (!ret) + ret = create_links(h, first_dentry, ctx); + + /* "WIMBoot" extraction: set external backing by the WIM file if needed. */ + if (!ret && unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) + ret = set_external_backing(h, inode, ctx); + + (*func_NtClose)(h); + return ret; +} + +/* Create all the nondirectory files being extracted, including all aliases + * (hard links). */ +static int +create_nondirectories(struct list_head *dentry_list, struct win32_apply_ctx *ctx) +{ + struct wim_dentry *dentry; + struct wim_inode *inode; + int ret; + + list_for_each_entry(dentry, dentry_list, d_extraction_list_node) { + inode = dentry->d_inode; + if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) + continue; + /* Call create_nondirectory() only once per inode */ + if (dentry == inode_first_extraction_dentry(inode)) { + ret = create_nondirectory(inode, ctx); + ret = check_apply_error(dentry, ctx, ret); + if (ret) + return ret; + } + ret = report_file_created(&ctx->common); + if (ret) + return ret; + } + return 0; +} + +static void +close_handles(struct win32_apply_ctx *ctx) +{ + for (unsigned i = 0; i < ctx->num_open_handles; i++) + (*func_NtClose)(ctx->open_handles[i]); +} + +/* Prepare to read the next stream, which has size @stream_size, into an + * in-memory buffer. */ +static bool +prepare_data_buffer(struct win32_apply_ctx *ctx, u64 stream_size) +{ + if (stream_size > ctx->data_buffer_size) { + /* Larger buffer needed. */ + void *new_buffer; + if ((size_t)stream_size != stream_size) + return false; + new_buffer = REALLOC(ctx->data_buffer, stream_size); + if (!new_buffer) + return false; + ctx->data_buffer = new_buffer; + ctx->data_buffer_size = stream_size; + } + /* On the first call this changes data_buffer_ptr from NULL, which tells + * extract_chunk() that the data buffer needs to be filled while reading + * the stream data. */ + ctx->data_buffer_ptr = ctx->data_buffer; + return true; +} + +static int +begin_extract_stream_instance(const struct wim_lookup_table_entry *stream, + struct wim_dentry *dentry, + const wchar_t *stream_name, + struct win32_apply_ctx *ctx) +{ + const struct wim_inode *inode = dentry->d_inode; + size_t stream_name_nchars = 0; + FILE_ALLOCATION_INFORMATION alloc_info; + HANDLE h; + NTSTATUS status; + + if (unlikely(stream_name)) + stream_name_nchars = wcslen(stream_name); + + if (unlikely(stream_name_nchars)) { + build_extraction_path_with_ads(dentry, ctx, + stream_name, stream_name_nchars); + } else { + build_extraction_path(dentry, ctx); + } + + + /* Encrypted file? */ + if (unlikely(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) + && (stream_name_nchars == 0)) + { + if (!ctx->common.supported_features.encrypted_files) + return 0; + + /* We can't write encrypted file streams directly; we must use + * WriteEncryptedFileRaw(), which requires providing the data + * through a callback function. This can't easily be combined + * with our own callback-based approach. + * + * The current workaround is to simply read the stream into + * memory and write the encrypted file from that. + * + * TODO: This isn't sufficient for extremely large encrypted + * files. Perhaps we should create an extra thread to write + * such files... */ + if (!prepare_data_buffer(ctx, stream->size)) + return WIMLIB_ERR_NOMEM; + list_add_tail(&dentry->tmp_list, &ctx->encrypted_dentries); + return 0; + } + + /* Reparse point? + * + * Note: FILE_ATTRIBUTE_REPARSE_POINT is tested *after* + * FILE_ATTRIBUTE_ENCRYPTED since the WIM format does not store both EFS + * data and reparse data for the same file, and the EFS data takes + * precedence. */ + if (unlikely(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) + && (stream_name_nchars == 0)) + { + if (!ctx->common.supported_features.reparse_points) + return 0; + + /* We can't write the reparse stream directly; we must set it + * with FSCTL_SET_REPARSE_POINT, which requires that all the + * data be available. So, stage the data in a buffer. */ + + if (!prepare_data_buffer(ctx, stream->size)) + return WIMLIB_ERR_NOMEM; + list_add_tail(&dentry->tmp_list, &ctx->reparse_dentries); + return 0; + } + + if (ctx->num_open_handles == MAX_OPEN_STREAMS) { + /* XXX: Fix this. But because of the checks in + * extract_stream_list(), this can now only happen on a + * filesystem that does not support hard links. */ + ERROR("Can't extract data: too many open files!"); + return WIMLIB_ERR_UNSUPPORTED; + } + + /* Open a new handle */ + status = do_create_file(&h, + FILE_WRITE_DATA | SYNCHRONIZE, + NULL, 0, FILE_OPEN_IF, + FILE_SEQUENTIAL_ONLY | + FILE_SYNCHRONOUS_IO_NONALERT, + ctx); + if (!NT_SUCCESS(status)) { + winnt_error(status, L"Can't open \"%ls\" for writing", + current_path(ctx)); + return WIMLIB_ERR_OPEN; + } + + ctx->open_handles[ctx->num_open_handles++] = h; + + /* Allocate space for the data. */ + alloc_info.AllocationSize.QuadPart = stream->size; + (*func_NtSetInformationFile)(h, &ctx->iosb, + &alloc_info, sizeof(alloc_info), + FileAllocationInformation); + return 0; +} + +/* Set the reparse data @rpbuf of length @rpbuflen on the extracted file + * corresponding to the WIM dentry @dentry. */ +static int +do_set_reparse_data(const struct wim_dentry *dentry, + const void *rpbuf, u16 rpbuflen, + struct win32_apply_ctx *ctx) +{ + NTSTATUS status; + HANDLE h; + + status = create_file(&h, GENERIC_WRITE, NULL, + 0, FILE_OPEN, 0, dentry, ctx); + if (!NT_SUCCESS(status)) + goto fail; + + status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, + &ctx->iosb, FSCTL_SET_REPARSE_POINT, + (void *)rpbuf, rpbuflen, + NULL, 0); + (*func_NtClose)(h); + + if (NT_SUCCESS(status)) + return 0; + + /* On Windows, by default only the Administrator can create symbolic + * links for some reason. By default we just issue a warning if this + * appears to be the problem. Use WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS + * to get a hard error. */ + if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS) + && (status == STATUS_PRIVILEGE_NOT_HELD || + status == STATUS_ACCESS_DENIED) + && (dentry->d_inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || + dentry->d_inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)) + { + WARNING("Can't create symbolic link \"%ls\"! \n" + " (Need Administrator rights, or at least " + "the\n" + " SeCreateSymbolicLink privilege.)", + current_path(ctx)); + return 0; + } - if (ret) - goto fail_close_handle; - - /* At this point we have at least created the needed stream with the - * appropriate attributes. We have yet to set the appropriate security - * descriptor and actually extract the stream data (other than for - * extracted files, which were already extracted). - * win32_finish_extract_stream() handles these additional steps. */ - ret = win32_finish_extract_stream(h, dentry, lte, stream_path, - stream_name_utf16, args); - if (ret) - goto fail_close_handle; - - /* Done extracting the stream. Close the handle and return. */ - DEBUG("Closing \"%ls\"", stream_path); - if (!CloseHandle(h)) { - err = GetLastError(); - ERROR("Failed to close \"%ls\"", stream_path); - win32_error(err); - ret = WIMLIB_ERR_WRITE; - goto fail; - } - ret = 0; - goto out; -fail_close_handle: - CloseHandle(h); fail: - ERROR("Error extracting \"%ls\"", stream_path); -out: - return ret; + winnt_error(status, L"Can't set reparse data on \"%ls\"", + current_path(ctx)); + return WIMLIB_ERR_SET_REPARSE_DATA; } -/* - * Creates a file, directory, or reparse point and extracts all streams to it - * (unnamed data stream and/or reparse point stream, plus any alternate data - * streams). Handles sparse, compressed, and/or encrypted files. +/* Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a + * pointer to the suffix of the path that begins with the device directly, such + * as e:\Windows\System32. */ +static const wchar_t * +skip_nt_toplevel_component(const wchar_t *path, size_t path_nchars) +{ + static const wchar_t * const dirs[] = { + L"\\??\\", + L"\\DosDevices\\", + L"\\Device\\", + }; + size_t first_dir_len = 0; + const wchar_t * const end = path + path_nchars; + + for (size_t i = 0; i < ARRAY_LEN(dirs); i++) { + size_t len = wcslen(dirs[i]); + if (len <= (end - path) && !wcsnicmp(path, dirs[i], len)) { + first_dir_len = len; + break; + } + } + if (first_dir_len == 0) + return path; + path += first_dir_len; + while (path != end && *path == L'\\') + path++; + return path; +} + +/* Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a + * pointer to the suffix of the path that is device-relative, such as + * Windows\System32. * - * @dentry: WIM dentry for this file or directory. - * @path: UTF-16LE external path to extract the inode to. - * @args: Additional extraction context. + * The path has an explicit length and is not necessarily null terminated. * - * Returns 0 on success; nonzero on failure. + * If the path just something like \??\e: then the returned pointer will point + * just past the colon. In this case the length of the result will be 0 + * characters. */ +static const wchar_t * +get_device_relative_path(const wchar_t *path, size_t path_nchars) +{ + const wchar_t * const orig_path = path; + const wchar_t * const end = path + path_nchars; + + path = skip_nt_toplevel_component(path, path_nchars); + if (path == orig_path) + return orig_path; + + path = wmemchr(path, L'\\', (end - path)); + if (!path) + return end; + do { + path++; + } while (path != end && *path == L'\\'); + return path; +} + +/* + * Given a reparse point buffer for a symbolic link or junction, adjust its + * contents so that the target of the link is consistent with the new location + * of the files. */ -static int -win32_extract_streams(const struct wim_dentry *dentry, - const wchar_t *path, struct apply_args *args) +static void +try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, struct win32_apply_ctx *ctx) { - struct wim_lookup_table_entry *unnamed_lte; - int ret; - const struct wim_inode *inode = dentry->d_inode; + struct reparse_data rpdata; + size_t orig_subst_name_nchars; + const wchar_t *relpath; + size_t relpath_nchars; + size_t target_ntpath_nchars; + size_t fixed_subst_name_nchars; + const wchar_t *fixed_print_name; + size_t fixed_print_name_nchars; + + if (parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata)) { + /* Do nothing if the reparse data is invalid. */ + return; + } - /* First extract the unnamed stream. */ + if (rpdata.rptag == WIM_IO_REPARSE_TAG_SYMLINK && + (rpdata.rpflags & SYMBOLIC_LINK_RELATIVE)) + { + /* Do nothing if it's a relative symbolic link. */ + return; + } - unnamed_lte = inode_unnamed_lte_resolved(inode); - ret = win32_extract_stream(dentry, path, NULL, unnamed_lte, args); - if (ret) - goto out; + /* Build the new substitute name from the NT namespace path to the + * target directory, then a path separator, then the "device relative" + * part of the old substitute name. */ - /* Extract any named streams, if supported by the volume. */ + orig_subst_name_nchars = rpdata.substitute_name_nbytes / sizeof(wchar_t); - if (!(args->vol_flags & FILE_NAMED_STREAMS)) - goto out; - for (u16 i = 0; i < inode->i_num_ads; i++) { - const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i]; + relpath = get_device_relative_path(rpdata.substitute_name, + orig_subst_name_nchars); + relpath_nchars = orig_subst_name_nchars - + (relpath - rpdata.substitute_name); - /* Skip the unnamed stream if it's in the ADS entries (we - * already extracted it...) */ - if (ads_entry->stream_name_nbytes == 0) - continue; + target_ntpath_nchars = ctx->target_ntpath.Length / sizeof(wchar_t); - /* Skip special UNIX data entries (see documentation for - * WIMLIB_ADD_FLAG_UNIX_DATA) */ - if (ads_entry->stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES - && !memcmp(ads_entry->stream_name, - WIMLIB_UNIX_DATA_TAG_UTF16LE, - WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES)) - continue; + fixed_subst_name_nchars = target_ntpath_nchars; + if (relpath_nchars) + fixed_subst_name_nchars += 1 + relpath_nchars; + wchar_t fixed_subst_name[fixed_subst_name_nchars]; - /* Extract the named stream */ - ret = win32_extract_stream(dentry, - path, - ads_entry->stream_name, - ads_entry->lte, - args); - if (ret) - break; + wmemcpy(fixed_subst_name, ctx->target_ntpath.Buffer, + target_ntpath_nchars); + if (relpath_nchars) { + fixed_subst_name[target_ntpath_nchars] = L'\\'; + wmemcpy(&fixed_subst_name[target_ntpath_nchars + 1], + relpath, relpath_nchars); } -out: - return ret; + /* Doesn't need to be null-terminated. */ + + /* Print name should be Win32, but not all NT names can even be + * translated to Win32 names. But we can at least delete the top-level + * directory, such as \??\, and this will have the expected result in + * the usual case. */ + fixed_print_name = skip_nt_toplevel_component(fixed_subst_name, + fixed_subst_name_nchars); + fixed_print_name_nchars = fixed_subst_name_nchars - (fixed_print_name - + fixed_subst_name); + + rpdata.substitute_name = fixed_subst_name; + rpdata.substitute_name_nbytes = fixed_subst_name_nchars * sizeof(wchar_t); + rpdata.print_name = (wchar_t *)fixed_print_name; + rpdata.print_name_nbytes = fixed_print_name_nchars * sizeof(wchar_t); + make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p); } +/* Sets reparse data on the specified file. This handles "fixing" the targets + * of absolute symbolic links and junctions if WIMLIB_EXTRACT_FLAG_RPFIX was + * specified. */ static int -dentry_clear_inode_visited(struct wim_dentry *dentry, void *_ignore) +set_reparse_data(const struct wim_dentry *dentry, + const void *_rpbuf, u16 rpbuflen, struct win32_apply_ctx *ctx) { - dentry->d_inode->i_visited = 0; - return 0; + const struct wim_inode *inode = dentry->d_inode; + const void *rpbuf = _rpbuf; + + if ((ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) + && !inode->i_not_rpfixed + && (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || + inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)) + { + memcpy(&ctx->rpfixbuf, _rpbuf, rpbuflen); + try_rpfix((u8 *)&ctx->rpfixbuf, &rpbuflen, ctx); + rpbuf = &ctx->rpfixbuf; + } + return do_set_reparse_data(dentry, rpbuf, rpbuflen, ctx); + } +/* Import the next block of raw encrypted data */ +static DWORD WINAPI +import_encrypted_data(PBYTE pbData, PVOID pvCallbackContext, PULONG Length) +{ + struct win32_apply_ctx *ctx = pvCallbackContext; + ULONG copy_len; + + copy_len = min(ctx->encrypted_size - ctx->encrypted_offset, *Length); + memcpy(pbData, &ctx->data_buffer[ctx->encrypted_offset], copy_len); + ctx->encrypted_offset += copy_len; + *Length = copy_len; + return ERROR_SUCCESS; +} + +/* + * Write the raw encrypted data to the already-created file (or directory) + * corresponding to @dentry. + * + * The raw encrypted data is provided in ctx->data_buffer, and its size is + * ctx->encrypted_size. + * + * This function may close the target directory, in which case the caller needs + * to re-open it if needed. + */ static int -dentry_get_features(struct wim_dentry *dentry, void *_features_p) +extract_encrypted_file(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx) { - DWORD features = 0; - DWORD *features_p = _features_p; - struct wim_inode *inode = dentry->d_inode; + void *rawctx; + DWORD err; + ULONG flags; + bool retried; + + /* Temporarily build a Win32 path for OpenEncryptedFileRaw() */ + build_win32_extraction_path(dentry, ctx); + + flags = CREATE_FOR_IMPORT | OVERWRITE_HIDDEN; + if (dentry->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) + flags |= CREATE_FOR_DIR; + + retried = false; +retry: + err = OpenEncryptedFileRaw(ctx->pathbuf.Buffer, flags, &rawctx); + if (err == ERROR_SHARING_VIOLATION && !retried) { + /* This can be caused by the handle we have open to the target + * directory. Try closing it temporarily. */ + close_target_directory(ctx); + retried = true; + goto retry; + } - if (inode->i_visited) { - features |= FILE_SUPPORTS_HARD_LINKS; - } else { - inode->i_visited = 1; - if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) - features |= FILE_SUPPORTS_SPARSE_FILES; - if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) - features |= FILE_SUPPORTS_REPARSE_POINTS; - for (unsigned i = 0; i < inode->i_num_ads; i++) - if (inode->i_ads_entries[i].stream_name_nbytes) - features |= FILE_NAMED_STREAMS; - if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) - features |= FILE_SUPPORTS_ENCRYPTION; - if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) - features |= FILE_FILE_COMPRESSION; - if (inode->i_security_id != -1) - features |= FILE_PERSISTENT_ACLS; + /* Restore the NT namespace path */ + build_extraction_path(dentry, ctx); + + if (err != ERROR_SUCCESS) { + win32_error(err, L"Can't open \"%ls\" for encrypted import", + current_path(ctx)); + return WIMLIB_ERR_OPEN; + } + + ctx->encrypted_offset = 0; + + err = WriteEncryptedFileRaw(import_encrypted_data, ctx, rawctx); + + CloseEncryptedFileRaw(rawctx); + + if (err != ERROR_SUCCESS) { + win32_error(err, L"Can't import encrypted file \"%ls\"", + current_path(ctx)); + return WIMLIB_ERR_WRITE; } - *features_p |= features; + return 0; } -/* If not done already, load the supported feature flags for the volume onto - * which the image is being extracted, and warn the user about any missing - * features that could be important. */ +/* Called when starting to read a stream for extraction on Windows */ static int -win32_check_vol_flags(const wchar_t *output_path, - struct wim_dentry *root, struct apply_args *args) +begin_extract_stream(struct wim_lookup_table_entry *stream, void *_ctx) { - DWORD dentry_features = 0; - DWORD missing_features; - - if (args->have_vol_flags) - return 0; + struct win32_apply_ctx *ctx = _ctx; + const struct stream_owner *owners = stream_owners(stream); + int ret; - for_dentry_in_tree(root, dentry_clear_inode_visited, NULL); - for_dentry_in_tree(root, dentry_get_features, &dentry_features); + ctx->num_open_handles = 0; + ctx->data_buffer_ptr = NULL; + INIT_LIST_HEAD(&ctx->reparse_dentries); + INIT_LIST_HEAD(&ctx->encrypted_dentries); - win32_get_vol_flags(output_path, &args->vol_flags); - args->have_vol_flags = true; + for (u32 i = 0; i < stream->out_refcnt; i++) { + const struct wim_inode *inode = owners[i].inode; + const wchar_t *stream_name = owners[i].stream_name; + struct wim_dentry *dentry; - missing_features = dentry_features & ~args->vol_flags; + /* A copy of the stream needs to be extracted to @inode. */ - /* Warn the user about data that may not be extracted. */ - if (missing_features & FILE_SUPPORTS_SPARSE_FILES) - WARNING("Volume does not support sparse files!\n" - " Sparse files will be extracted as non-sparse."); - if (missing_features & FILE_SUPPORTS_REPARSE_POINTS) - WARNING("Volume does not support reparse points!\n" - " Reparse point data will not be extracted."); - if (missing_features & FILE_NAMED_STREAMS) { - WARNING("Volume does not support named data streams!\n" - " Named data streams will not be extracted."); - } - if (missing_features & FILE_SUPPORTS_ENCRYPTION) { - WARNING("Volume does not support encryption!\n" - " Encrypted files will be extracted as raw data."); - } - if (missing_features & FILE_FILE_COMPRESSION) { - WARNING("Volume does not support transparent compression!\n" - " Compressed files will be extracted as non-compressed."); - } - if (missing_features & FILE_PERSISTENT_ACLS) { - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) { - ERROR("Strict ACLs requested, but the volume does not " - "support ACLs!"); - return WIMLIB_ERR_VOLUME_LACKS_FEATURES; + if (ctx->common.supported_features.hard_links) { + dentry = inode_first_extraction_dentry(inode); + ret = begin_extract_stream_instance(stream, dentry, + stream_name, ctx); + ret = check_apply_error(dentry, ctx, ret); + if (ret) + goto fail; } else { - WARNING("Volume does not support persistent ACLS!\n" - " File permissions will not be extracted."); + /* Hard links not supported. Extract the stream + * separately to each alias of the inode. */ + struct list_head *next; + + next = inode->i_extraction_aliases.next; + do { + dentry = list_entry(next, struct wim_dentry, + d_extraction_alias_node); + ret = begin_extract_stream_instance(stream, + dentry, + stream_name, + ctx); + ret = check_apply_error(dentry, ctx, ret); + if (ret) + goto fail; + next = next->next; + } while (next != &inode->i_extraction_aliases); } } - if (running_on_windows_7_or_later() && - (missing_features & FILE_SUPPORTS_HARD_LINKS)) - { - WARNING("Volume does not support hard links!\n" - " Hard links will be extracted as duplicate files."); - } + return 0; + +fail: + close_handles(ctx); + return ret; } -/* - * Try extracting a hard link. - * - * @output_path: Path to link to be extracted. - * - * @inode: WIM inode that the link is to; inode->i_extracted_file - * the path to a name of the file that has already been - * extracted (we use this to create the hard link). - * - * @args: Additional apply context, used here to keep track of - * the number of times creating a hard link failed due to - * ERROR_INVALID_FUNCTION. This error should indicate that hard - * links are not supported by the volume, and we would like to - * warn the user a few times, but not too many times. - * - * Returns 0 if the hard link was successfully extracted. Returns - * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly - * being unsupported by the volume. Returns a negative value if creating the - * hard link failed due to ERROR_INVALID_FUNCTION. - */ +/* Called when the next chunk of a stream has been read for extraction on + * Windows */ static int -win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode, - struct apply_args *args) +extract_chunk(const void *chunk, size_t size, void *_ctx) { - DWORD err; + struct win32_apply_ctx *ctx = _ctx; + + /* Write the data chunk to each open handle */ + for (unsigned i = 0; i < ctx->num_open_handles; i++) { + u8 *bufptr = (u8 *)chunk; + size_t bytes_remaining = size; + NTSTATUS status; + while (bytes_remaining) { + ULONG count = min(0xFFFFFFFF, bytes_remaining); + + status = (*func_NtWriteFile)(ctx->open_handles[i], + NULL, NULL, NULL, + &ctx->iosb, bufptr, count, + NULL, NULL); + if (!NT_SUCCESS(status)) { + winnt_error(status, L"Error writing data to target volume"); + return WIMLIB_ERR_WRITE; + } + bufptr += ctx->iosb.Information; + bytes_remaining -= ctx->iosb.Information; + } + } - /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS), - * but it's only available on Windows 7 and later. - * - * Otherwise, CreateHardLinkW() will apparently return - * ERROR_INVALID_FUNCTION if the volume does not support hard links. */ + /* Copy the data chunk into the buffer (if needed) */ + if (ctx->data_buffer_ptr) + ctx->data_buffer_ptr = mempcpy(ctx->data_buffer_ptr, + chunk, size); + return 0; +} + +/* Called when a stream has been fully read for extraction on Windows */ +static int +end_extract_stream(struct wim_lookup_table_entry *stream, int status, void *_ctx) +{ + struct win32_apply_ctx *ctx = _ctx; + int ret; + const struct wim_dentry *dentry; - DEBUG("Creating hard link \"%ls => %ls\"", - output_path, inode->i_extracted_file); + close_handles(ctx); - if (running_on_windows_7_or_later() && - !(args->vol_flags & FILE_SUPPORTS_HARD_LINKS)) - goto hard_links_unsupported; + if (status) + return status; - if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL)) + if (likely(!ctx->data_buffer_ptr)) return 0; - err = GetLastError(); - if (err != ERROR_INVALID_FUNCTION) { - ERROR("Can't create hard link \"%ls => %ls\"", - output_path, inode->i_extracted_file); - win32_error(err); - return WIMLIB_ERR_LINK; + if (!list_empty(&ctx->reparse_dentries)) { + if (stream->size > REPARSE_DATA_MAX_SIZE) { + dentry = list_first_entry(&ctx->reparse_dentries, + struct wim_dentry, tmp_list); + build_extraction_path(dentry, ctx); + ERROR("Reparse data of \"%ls\" has size " + "%"PRIu64" bytes (exceeds %u bytes)", + current_path(ctx), stream->size, + REPARSE_DATA_MAX_SIZE); + ret = WIMLIB_ERR_INVALID_REPARSE_DATA; + return check_apply_error(dentry, ctx, ret); + } + /* In the WIM format, reparse streams are just the reparse data + * and omit the header. But we can reconstruct the header. */ + memcpy(ctx->rpbuf.rpdata, ctx->data_buffer, stream->size); + ctx->rpbuf.rpdatalen = stream->size; + ctx->rpbuf.rpreserved = 0; + list_for_each_entry(dentry, &ctx->reparse_dentries, tmp_list) { + ctx->rpbuf.rptag = dentry->d_inode->i_reparse_tag; + ret = set_reparse_data(dentry, &ctx->rpbuf, + stream->size + REPARSE_DATA_OFFSET, + ctx); + ret = check_apply_error(dentry, ctx, ret); + if (ret) + return ret; + } } -hard_links_unsupported: - args->num_hard_links_failed++; - if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) { - if (running_on_windows_7_or_later()) - { - WARNING("Extracting duplicate copy of \"%ls\" " - "rather than hard link", output_path); - } else { - WARNING("Can't create hard link \"%ls\" => \"%ls\":\n" - " Volume does not support hard links!\n" - " Falling back to extracting a copy of the file.", - output_path, inode->i_extracted_file); + + if (!list_empty(&ctx->encrypted_dentries)) { + ctx->encrypted_size = stream->size; + list_for_each_entry(dentry, &ctx->encrypted_dentries, tmp_list) { + ret = extract_encrypted_file(dentry, ctx); + ret = check_apply_error(dentry, ctx, ret); + if (ret) + return ret; + /* Re-open the target directory if needed. */ + ret = open_target_directory(ctx); + if (ret) + return ret; } } - if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) - WARNING("Suppressing further hard linking warnings..."); - return -1; + + return 0; } -/* Extract a file, directory, reparse point, or hard link to an - * already-extracted file using the Win32 API */ -int -win32_do_apply_dentry(const wchar_t *output_path, - size_t output_path_num_chars, - struct wim_dentry *dentry, - struct apply_args *args) +/* Attributes that can't be set directly */ +#define SPECIAL_ATTRIBUTES \ + (FILE_ATTRIBUTE_REPARSE_POINT | \ + FILE_ATTRIBUTE_DIRECTORY | \ + FILE_ATTRIBUTE_ENCRYPTED | \ + FILE_ATTRIBUTE_SPARSE_FILE | \ + FILE_ATTRIBUTE_COMPRESSED) + +/* Set the security descriptor @desc, of @desc_size bytes, on the file with open + * handle @h. */ +static NTSTATUS +set_security_descriptor(HANDLE h, const void *_desc, + size_t desc_size, struct win32_apply_ctx *ctx) { - int ret; - struct wim_inode *inode = dentry->d_inode; + SECURITY_INFORMATION info; + NTSTATUS status; + SECURITY_DESCRIPTOR_RELATIVE *desc; - ret = win32_check_vol_flags(output_path, dentry, args); - if (ret) - return ret; - if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) { - /* Linked file, with another name already extracted. Create a - * hard link. */ - ret = win32_try_hard_link(output_path, inode, args); - if (ret >= 0) - return ret; - /* Negative return value from win32_try_hard_link() indicates - * that hard links are probably not supported by the volume. - * Fall back to extracting a copy of the file. */ + /* + * Ideally, we would just pass in the security descriptor buffer as-is. + * But it turns out that Windows can mess up the security descriptor + * even when using the low-level NtSetSecurityObject() function: + * + * - Windows will clear SE_DACL_AUTO_INHERITED if it is set in the + * passed buffer. To actually get Windows to set + * SE_DACL_AUTO_INHERITED, the application must set the non-persistent + * flag SE_DACL_AUTO_INHERIT_REQ. As usual, Microsoft didn't bother + * to properly document either of these flags. It's unclear how + * important SE_DACL_AUTO_INHERITED actually is, but to be safe we use + * the SE_DACL_AUTO_INHERIT_REQ workaround to set it if needed. + * + * - The above also applies to the equivalent SACL flags, + * SE_SACL_AUTO_INHERITED and SE_SACL_AUTO_INHERIT_REQ. + * + * - If the application says that it's setting + * DACL_SECURITY_INFORMATION, then Windows sets SE_DACL_PRESENT in the + * resulting security descriptor, even if the security descriptor the + * application provided did not have a DACL. This seems to be + * unavoidable, since omitting DACL_SECURITY_INFORMATION would cause a + * default DACL to remain. Fortunately, this behavior seems harmless, + * since the resulting DACL will still be "null" --- but it will be + * "the other representation of null". + * + * - The above also applies to SACL_SECURITY_INFORMATION and + * SE_SACL_PRESENT. Again, it's seemingly unavoidable but "harmless" + * that Windows changes the representation of a "null SACL". + */ + if (likely(desc_size <= STACK_MAX)) { + desc = alloca(desc_size); + } else { + desc = MALLOC(desc_size); + if (!desc) + return STATUS_NO_MEMORY; } - /* If this is a reparse point and the volume does not support reparse - * points, just skip it completely. */ - if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT && - !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS)) - { - WARNING("Not extracting reparse point \"%ls\"", output_path); - } else { - /* Create the file, directory, or reparse point, and extract the - * data streams. */ - ret = win32_extract_streams(dentry, output_path, args); - if (ret) - return ret; + memcpy(desc, _desc, desc_size); + + if (likely(desc_size >= 4)) { + + if (desc->Control & SE_DACL_AUTO_INHERITED) + desc->Control |= SE_DACL_AUTO_INHERIT_REQ; + + if (desc->Control & SE_SACL_AUTO_INHERITED) + desc->Control |= SE_SACL_AUTO_INHERIT_REQ; } - if (inode->i_extracted_file == NULL) { - const struct wim_lookup_table_entry *lte; - - /* Tally bytes extracted, including all alternate data streams, - * unless we extracted a hard link (or, at least extracted a - * name that was supposed to be a hard link) */ - for (unsigned i = 0; i <= inode->i_num_ads; i++) { - lte = inode_stream_lte_resolved(inode, i); - if (lte) - args->progress.extract.completed_bytes += - wim_resource_size(lte); + + /* + * More API insanity. We want to set the entire security descriptor + * as-is. But all available APIs require specifying the specific parts + * of the security descriptor being set. Especially annoying is that + * mandatory integrity labels are part of the SACL, but they aren't set + * with SACL_SECURITY_INFORMATION. Instead, applications must also + * specify LABEL_SECURITY_INFORMATION (Windows Vista, Windows 7) or + * BACKUP_SECURITY_INFORMATION (Windows 8). But at least older versions + * of Windows don't error out if you provide these newer flags... + * + * Also, if the process isn't running as Administrator, then it probably + * doesn't have SE_RESTORE_PRIVILEGE. In this case, it will always get + * the STATUS_PRIVILEGE_NOT_HELD error by trying to set the SACL, even + * if the security descriptor it provided did not have a SACL. By + * default, in this case we try to recover and set as much of the + * security descriptor as possible --- potentially excluding the DACL, and + * even the owner, as well as the SACL. + */ + + info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | + LABEL_SECURITY_INFORMATION | BACKUP_SECURITY_INFORMATION; + + + /* + * It's also worth noting that SetFileSecurity() is unusable because it + * doesn't request "backup semantics" when it opens the file internally. + * NtSetSecurityObject() seems to be the best function to use in backup + * applications. (SetSecurityInfo() should also work, but it's harder + * to use and must call NtSetSecurityObject() internally anyway. + * BackupWrite() is theoretically usable as well, but it's inflexible + * and poorly documented.) + */ + +retry: + status = (*func_NtSetSecurityObject)(h, info, desc); + if (NT_SUCCESS(status)) + goto out_maybe_free_desc; + + /* Failed to set the requested parts of the security descriptor. If the + * error was permissions-related, try to set fewer parts of the security + * descriptor, unless WIMLIB_EXTRACT_FLAG_STRICT_ACLS is enabled. */ + if ((status == STATUS_PRIVILEGE_NOT_HELD || + status == STATUS_ACCESS_DENIED) && + !(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) + { + if (info & SACL_SECURITY_INFORMATION) { + info &= ~(SACL_SECURITY_INFORMATION | + LABEL_SECURITY_INFORMATION | + BACKUP_SECURITY_INFORMATION); + ctx->partial_security_descriptors++; + goto retry; + } + if (info & DACL_SECURITY_INFORMATION) { + info &= ~DACL_SECURITY_INFORMATION; + goto retry; } - if (inode->i_nlink > 1) { - /* Save extracted path for a later call to - * CreateHardLinkW() if this inode has multiple links. - * */ - inode->i_extracted_file = WSTRDUP(output_path); - if (!inode->i_extracted_file) - return WIMLIB_ERR_NOMEM; + if (info & OWNER_SECURITY_INFORMATION) { + info &= ~OWNER_SECURITY_INFORMATION; + goto retry; + } + /* Nothing left except GROUP, and if we removed it we + * wouldn't have anything at all. */ + } + + /* No part of the security descriptor could be set, or + * WIMLIB_EXTRACT_FLAG_STRICT_ACLS is enabled and the full security + * descriptor could not be set. */ + if (!(info & SACL_SECURITY_INFORMATION)) + ctx->partial_security_descriptors--; + ctx->no_security_descriptors++; + +out_maybe_free_desc: + if (unlikely(desc_size > STACK_MAX)) + FREE(desc); + return status; +} + +/* Set metadata on the open file @h from the WIM inode @inode. */ +static int +do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, + struct win32_apply_ctx *ctx) +{ + FILE_BASIC_INFORMATION info; + NTSTATUS status; + + /* Set security descriptor if present and not in NO_ACLS mode */ + if (inode->i_security_id >= 0 && + !(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)) + { + const struct wim_security_data *sd; + const void *desc; + size_t desc_size; + + sd = wim_get_current_security_data(ctx->common.wim); + desc = sd->descriptors[inode->i_security_id]; + desc_size = sd->sizes[inode->i_security_id]; + + status = set_security_descriptor(h, desc, desc_size, ctx); + if (!NT_SUCCESS(status) && + (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) + { + winnt_error(status, + L"Can't set security descriptor on \"%ls\"", + current_path(ctx)); + return WIMLIB_ERR_SET_SECURITY; } } + + /* Set attributes and timestamps */ + info.CreationTime.QuadPart = inode->i_creation_time; + info.LastAccessTime.QuadPart = inode->i_last_access_time; + info.LastWriteTime.QuadPart = inode->i_last_write_time; + info.ChangeTime.QuadPart = 0; + if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES) { + info.FileAttributes = FILE_ATTRIBUTE_NORMAL; + } else { + info.FileAttributes = inode->i_attributes & ~SPECIAL_ATTRIBUTES; + if (info.FileAttributes == 0) + info.FileAttributes = FILE_ATTRIBUTE_NORMAL; + } + + status = (*func_NtSetInformationFile)(h, &ctx->iosb, + &info, sizeof(info), + FileBasicInformation); + /* On FAT volumes we get STATUS_INVALID_PARAMETER if we try to set + * attributes on the root directory. (Apparently because FAT doesn't + * actually have a place to store those attributes!) */ + if (!NT_SUCCESS(status) + && !(status == STATUS_INVALID_PARAMETER && + dentry_is_root(inode_first_extraction_dentry(inode)))) + { + winnt_error(status, L"Can't set basic metadata on \"%ls\"", + current_path(ctx)); + return WIMLIB_ERR_SET_ATTRIBUTES; + } + return 0; } -/* Set timestamps on an extracted file using the Win32 API */ -int -win32_do_apply_dentry_timestamps(const wchar_t *path, - size_t path_num_chars, - struct wim_dentry *dentry, - struct apply_args *args) +static int +apply_metadata_to_file(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx) { - DWORD err; - HANDLE h; const struct wim_inode *inode = dentry->d_inode; + DWORD perms; + HANDLE h; + NTSTATUS status; + int ret; + + perms = FILE_WRITE_ATTRIBUTES | WRITE_DAC | + WRITE_OWNER | ACCESS_SYSTEM_SECURITY; + + build_extraction_path(dentry, ctx); - if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT && - !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS)) + /* Open a handle with as many relevant permissions as possible. */ + while (!NT_SUCCESS(status = do_create_file(&h, perms, NULL, + 0, FILE_OPEN, 0, ctx))) { - /* Skip reparse points not extracted */ - return 0; + if (status == STATUS_PRIVILEGE_NOT_HELD || + status == STATUS_ACCESS_DENIED) + { + if (perms & ACCESS_SYSTEM_SECURITY) { + perms &= ~ACCESS_SYSTEM_SECURITY; + continue; + } + if (perms & WRITE_DAC) { + perms &= ~WRITE_DAC; + continue; + } + if (perms & WRITE_OWNER) { + perms &= ~WRITE_OWNER; + continue; + } + } + winnt_error(status, L"Can't open \"%ls\" to set metadata", + current_path(ctx)); + return WIMLIB_ERR_OPEN; } - /* Windows doesn't let you change the timestamps of the root directory - * (at least on FAT, which is dumb but expected since FAT doesn't store - * any metadata about the root directory...) */ - if (path_is_root_of_drive(path)) - return 0; + ret = do_apply_metadata_to_file(h, inode, ctx); - DEBUG("Opening \"%ls\" to set timestamps", path); - h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES); - if (h == INVALID_HANDLE_VALUE) { - err = GetLastError(); - goto fail; + (*func_NtClose)(h); + + return ret; +} + +static int +apply_metadata(struct list_head *dentry_list, struct win32_apply_ctx *ctx) +{ + const struct wim_dentry *dentry; + int ret; + + /* We go in reverse so that metadata is set on all a directory's + * children before the directory itself. This avoids any potential + * problems with attributes, timestamps, or security descriptors. */ + list_for_each_entry_reverse(dentry, dentry_list, d_extraction_list_node) + { + ret = apply_metadata_to_file(dentry, ctx); + ret = check_apply_error(dentry, ctx, ret); + if (ret) + return ret; + ret = report_file_metadata_applied(&ctx->common); + if (ret) + return ret; } + return 0; +} - FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff, - .dwHighDateTime = inode->i_creation_time >> 32}; - FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff, - .dwHighDateTime = inode->i_last_access_time >> 32}; - FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff, - .dwHighDateTime = inode->i_last_write_time >> 32}; - - DEBUG("Calling SetFileTime() on \"%ls\"", path); - if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) { - err = GetLastError(); - CloseHandle(h); - goto fail; +/* Issue warnings about problems during the extraction for which warnings were + * not already issued (due to the high number of potential warnings if we issued + * them per-file). */ +static void +do_warnings(const struct win32_apply_ctx *ctx) +{ + if (ctx->partial_security_descriptors == 0 + && ctx->no_security_descriptors == 0 + && ctx->num_set_short_name_failures == 0 + #if 0 + && ctx->num_remove_short_name_failures == 0 + #endif + ) + return; + + WARNING("Extraction to \"%ls\" complete, but with one or more warnings:", + ctx->common.target); + if (ctx->num_set_short_name_failures) { + WARNING("- Could not set short names on %lu files or directories", + ctx->num_set_short_name_failures); } - DEBUG("Closing \"%ls\"", path); - if (!CloseHandle(h)) { - err = GetLastError(); - goto fail; +#if 0 + if (ctx->num_remove_short_name_failures) { + WARNING("- Could not remove short names on %lu files or directories" + " (This is expected on Vista and earlier)", + ctx->num_remove_short_name_failures); } - goto out; -fail: - /* Only warn if setting timestamps failed; still return 0. */ - WARNING("Can't set timestamps on \"%ls\"", path); - win32_error(err); +#endif + if (ctx->partial_security_descriptors) { + WARNING("- Could only partially set the security descriptor\n" + " on %lu files or directories.", + ctx->partial_security_descriptors); + } + if (ctx->no_security_descriptors) { + WARNING("- Could not set security descriptor at all\n" + " on %lu files or directories.", + ctx->no_security_descriptors); + } + if (ctx->partial_security_descriptors || ctx->no_security_descriptors) { + WARNING("To fully restore all security descriptors, run the program\n" + " with Administrator rights."); + } +} + +static uint64_t +count_dentries(const struct list_head *dentry_list) +{ + const struct list_head *cur; + uint64_t count = 0; + + list_for_each(cur, dentry_list) + count++; + + return count; +} + +/* Extract files from a WIM image to a directory on Windows */ +static int +win32_extract(struct list_head *dentry_list, struct apply_ctx *_ctx) +{ + int ret; + struct win32_apply_ctx *ctx = (struct win32_apply_ctx *)_ctx; + uint64_t dentry_count; + + ret = prepare_target(dentry_list, ctx); + if (ret) + goto out; + + if (unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) { + ret = start_wimboot_extraction(ctx); + if (ret) + goto out; + } + + dentry_count = count_dentries(dentry_list); + + ret = start_file_structure_phase(&ctx->common, dentry_count); + if (ret) + goto out; + + ret = create_directories(dentry_list, ctx); + if (ret) + goto out; + + ret = create_nondirectories(dentry_list, ctx); + if (ret) + goto out; + + ret = end_file_structure_phase(&ctx->common); + if (ret) + goto out; + + struct read_stream_list_callbacks cbs = { + .begin_stream = begin_extract_stream, + .begin_stream_ctx = ctx, + .consume_chunk = extract_chunk, + .consume_chunk_ctx = ctx, + .end_stream = end_extract_stream, + .end_stream_ctx = ctx, + }; + ret = extract_stream_list(&ctx->common, &cbs); + if (ret) + goto out; + + ret = start_file_metadata_phase(&ctx->common, dentry_count); + if (ret) + goto out; + + ret = apply_metadata(dentry_list, ctx); + if (ret) + goto out; + + ret = end_file_metadata_phase(&ctx->common); + if (ret) + goto out; + + if (unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) { + ret = end_wimboot_extraction(ctx); + if (ret) + goto out; + } + + do_warnings(ctx); out: - return 0; + close_target_directory(ctx); + if (ctx->target_ntpath.Buffer) + HeapFree(GetProcessHeap(), 0, ctx->target_ntpath.Buffer); + FREE(ctx->pathbuf.Buffer); + FREE(ctx->print_buffer); + if (ctx->wimboot.prepopulate_pats) { + FREE(ctx->wimboot.prepopulate_pats->strings); + FREE(ctx->wimboot.prepopulate_pats); + } + FREE(ctx->wimboot.mem_prepopulate_pats); + FREE(ctx->data_buffer); + return ret; } +const struct apply_operations win32_apply_ops = { + .name = "Windows", + .get_supported_features = win32_get_supported_features, + .extract = win32_extract, + .will_externally_back = win32_will_externally_back, + .context_size = sizeof(struct win32_apply_ctx), +}; + #endif /* __WIN32__ */