X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fwin32_apply.c;h=3f4a4562567ba223183636f2836fde07fb0bf660;hp=471669e1d9eb3ac7ce24be7671f82920e7d739da;hb=036c7da59a54e9a24b1afb917b4f9f10eee176ee;hpb=1746624a7e5d14dcc73f4f959b1dfa1e6f3c210a diff --git a/src/win32_apply.c b/src/win32_apply.c index 471669e1..3f4a4562 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -3,7 +3,7 @@ */ /* - * Copyright (C) 2013 Eric Biggers + * Copyright (C) 2013, 2014 Eric Biggers * * This file is part of wimlib, a library for working with WIM files. * @@ -27,1295 +27,1035 @@ # include "config.h" #endif -#include /* for SetSecurityInfo() */ - #include "wimlib/win32_common.h" #include "wimlib/apply.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/reparse.h" -#include "wimlib/security.h" - -#define MAX_CREATE_HARD_LINK_WARNINGS 5 -#define MAX_CREATE_SOFT_LINK_WARNINGS 5 +#include "wimlib/resource.h" +#include "wimlib/textfile.h" +#include "wimlib/xml.h" +#include "wimlib/wim.h" +#include "wimlib/wimboot.h" + +struct win32_apply_private_data { + u64 data_source_id; + struct string_set *prepopulate_pats; + void *mem_prepopulate_pats; + u8 wim_lookup_table_hash[SHA1_HASH_SIZE]; + bool wof_running; +}; -#define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1 -#define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1 +static struct win32_apply_private_data * +get_private_data(struct apply_ctx *ctx) +{ + BUILD_BUG_ON(sizeof(ctx->private) < sizeof(struct win32_apply_private_data)); + return (struct win32_apply_private_data *)(ctx->private); +} -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" - ; +static void +free_prepopulate_pats(struct win32_apply_private_data *dat) +{ + if (dat->prepopulate_pats) { + FREE(dat->prepopulate_pats->strings); + FREE(dat->prepopulate_pats); + dat->prepopulate_pats = NULL; + } + if (dat->mem_prepopulate_pats) { + FREE(dat->mem_prepopulate_pats); + dat->mem_prepopulate_pats = NULL; + } +} static int -win32_extract_try_rpfix(u8 *rpbuf, - u16 *rpbuflen_p, - const wchar_t *extract_root_realpath, - unsigned extract_root_realpath_nchars) +load_prepopulate_pats(struct apply_ctx *ctx) { - 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; - - utf16lechar *new_target; - utf16lechar *new_print_name; - size_t new_target_nchars; - size_t new_print_name_nchars; - utf16lechar *p; - - ret = parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata); - if (ret) - return ret; - - if (extract_root_realpath[0] == L'\0' || - extract_root_realpath[1] != L':' || - extract_root_realpath[2] != L'\\') + struct wim_dentry *dentry; + struct wim_lookup_table_entry *lte; + struct string_set *s; + const tchar *path = WIMLIB_WIM_PATH_SEPARATOR_STRING T("Windows") + WIMLIB_WIM_PATH_SEPARATOR_STRING T("System32") + WIMLIB_WIM_PATH_SEPARATOR_STRING T("WimBootCompress.ini"); + void *buf; + void *mem; + struct text_file_section sec; + struct win32_apply_private_data *dat = get_private_data(ctx); + + dentry = get_dentry(ctx->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->wim->lookup_table))) { - ERROR("Can't understand full path format \"%ls\". " - "Try turning reparse point fixups off...", - extract_root_realpath); - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; + WARNING("%"TS" does not exist in WIM image!", path); + return WIMLIB_ERR_PATH_DOES_NOT_EXIST; } - 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 + stripped_nchars; - 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 */ - p = wmempcpy(p, L"\\??\\", 4); - } + ret = read_full_stream_into_alloc_buf(lte, &buf); + if (ret) + return ret; - /* 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]; + s = CALLOC(1, sizeof(struct string_set)); + if (!s) { + FREE(buf); + return WIMLIB_ERR_NOMEM; } - /* Copy the rest of the extract root */ - p = wmempcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2); - /* Append the stripped target */ - p = wmempcpy(p, stripped_target, stripped_target_nchars); - new_target_nchars = p - new_target; - new_print_name_nchars = p - new_print_name; + sec.name = T("PrepopulateList"); + sec.strings = s; - 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; + 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; } - 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, rpbuflen_p); + dat->prepopulate_pats = s; + dat->mem_prepopulate_pats = mem; + return 0; } -/* 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) +static bool +in_prepopulate_list(struct wim_dentry *dentry, struct apply_ctx *ctx) { - int ret; - u8 rpbuf[REPARSE_POINT_MAX_SIZE] _aligned_attribute(8); - DWORD bytesReturned; - u16 rpbuflen; - - DEBUG("Setting reparse data on \"%ls\"", path); + struct string_set *pats; + const tchar *path; - ret = wim_inode_get_reparse_data(inode, rpbuf, &rpbuflen); - if (ret) - return ret; + pats = get_private_data(ctx)->prepopulate_pats; + if (!pats || !pats->num_strings) + return false; - 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, - &rpbuflen, - args->target_realpath, - args->target_realpath_len); - if (ret) - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; - } + path = dentry_full_path(dentry); + if (!path) + return false; - /* 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, - rpbuflen, - NULL, 0, - &bytesReturned /* lpBytesReturned */, - NULL /* lpOverlapped */)) - { - 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; - } - } - return 0; + return match_pattern_list(path, tstrlen(path), pats); } -/* Wrapper around the FSCTL_SET_COMPRESSION ioctl to change the - * FILE_ATTRIBUTE_COMPRESSED flag of a file or directory. */ static int -win32_set_compression_state(HANDLE hFile, USHORT format, const wchar_t *path) +hash_lookup_table(WIMStruct *wim, u8 hash[SHA1_HASH_SIZE]) { - 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 wim_reshdr_to_hash(&wim->hdr.lookup_table_reshdr, wim, hash); +} + +/* Given a Windows-style path, return the number of characters of the prefix + * that specify the path to the root directory of a drive, or return 0 if the + * drive is relative (or at least on the current drive, in the case of + * absolute-but-not-really-absolute paths like \Windows\System32) */ +static size_t +win32_path_drive_spec_len(const wchar_t *path) +{ + size_t n = 0; + + if (!wcsncmp(path, L"\\\\?\\", 4)) { + /* \\?\-prefixed path. Check for following drive letter and + * path separator. */ + if (path[4] != L'\0' && path[5] == L':' && + is_any_path_separator(path[6])) + n = 7; + } else { + /* Not a \\?\-prefixed path. Check for an initial drive letter + * and path separator. */ + if (path[0] != L'\0' && path[1] == L':' && + is_any_path_separator(path[2])) + n = 3; } - return 0; + /* Include any additional path separators.*/ + if (n > 0) + while (is_any_path_separator(path[n])) + n++; + return n; +} + +static bool +win32_path_is_root_of_drive(const wchar_t *path) +{ + size_t drive_spec_len; + wchar_t full_path[32768]; + DWORD ret; + + ret = GetFullPathName(path, ARRAY_LEN(full_path), full_path, NULL); + if (ret > 0 && ret < ARRAY_LEN(full_path)) + path = full_path; + + /* Explicit drive letter and path separator? */ + drive_spec_len = win32_path_drive_spec_len(path); + if (drive_spec_len > 0 && path[drive_spec_len] == L'\0') + return true; + + /* All path separators? */ + for (const wchar_t *p = path; *p != L'\0'; p++) + if (!is_any_path_separator(*p)) + return false; + return true; } -/* Wrapper around FSCTL_SET_SPARSE ioctl to set a file as sparse. */ +/* Given a path, which may not yet exist, get a set of flags that describe the + * features of the volume the path is on. */ static int -win32_set_sparse(HANDLE hFile, const wchar_t *path) +win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret, + bool *supports_SetFileShortName_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; + wchar_t *volume; + BOOL bret; + DWORD vol_flags; + size_t drive_spec_len; + wchar_t filesystem_name[MAX_PATH + 1]; + + if (supports_SetFileShortName_ret) + *supports_SetFileShortName_ret = false; + + drive_spec_len = win32_path_drive_spec_len(path); + + if (drive_spec_len == 0) + if (path[0] != L'\0' && path[1] == L':') /* Drive-relative path? */ + drive_spec_len = 2; + + if (drive_spec_len == 0) { + /* Path does not start with a drive letter; use the volume of + * the current working directory. */ + volume = NULL; + } else { + /* Path starts with a drive letter (or \\?\ followed by a drive + * letter); use it. */ + volume = alloca((drive_spec_len + 2) * sizeof(wchar_t)); + wmemcpy(volume, path, drive_spec_len); + /* Add trailing backslash in case this was a drive-relative + * path. */ + volume[drive_spec_len] = L'\\'; + volume[drive_spec_len + 1] = L'\0'; + } + bret = GetVolumeInformation( + volume, /* lpRootPathName */ + NULL, /* lpVolumeNameBuffer */ + 0, /* nVolumeNameSize */ + NULL, /* lpVolumeSerialNumber */ + NULL, /* lpMaximumComponentLength */ + &vol_flags, /* lpFileSystemFlags */ + filesystem_name, /* lpFileSystemNameBuffer */ + ARRAY_LEN(filesystem_name)); /* nFileSystemNameSize */ + if (!bret) { + set_errno_from_GetLastError(); + WARNING_WITH_ERRNO("Failed to get volume information for " + "path \"%ls\"", path); + vol_flags = 0xffffffff; + goto out; + } + + 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 |= FILE_SUPPORTS_HARD_LINKS; + + if (supports_SetFileShortName_ret) + *supports_SetFileShortName_ret = true; } + +out: + DEBUG("using vol_flags = %x", vol_flags); + *vol_flags_ret = vol_flags; return 0; } -/* - * 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_start_extract(const wchar_t *path, struct apply_ctx *ctx) { - PSECURITY_DESCRIPTOR descriptor; - unsigned long n; - DWORD err; - const struct wim_security_data *sd; + int ret; + unsigned vol_flags; + bool supports_SetFileShortName; + struct win32_apply_private_data *dat = get_private_data(ctx); - SECURITY_INFORMATION securityInformation = 0; + ret = win32_get_vol_flags(path, &vol_flags, &supports_SetFileShortName); + if (ret) + goto err; - void *owner = NULL; - void *group = NULL; - ACL *dacl = NULL; - ACL *sacl = NULL; + ctx->supported_features.archive_files = 1; + ctx->supported_features.hidden_files = 1; + ctx->supported_features.system_files = 1; - BOOL owner_defaulted; - BOOL group_defaulted; - BOOL dacl_present; - BOOL dacl_defaulted; - BOOL sacl_present; - BOOL sacl_defaulted; + if (vol_flags & FILE_FILE_COMPRESSION) + ctx->supported_features.compressed_files = 1; - sd = wim_const_security_data(args->wim); - descriptor = sd->descriptors[inode->i_security_id]; + if (vol_flags & FILE_SUPPORTS_ENCRYPTION) { + ctx->supported_features.encrypted_files = 1; + ctx->supported_features.encrypted_directories = 1; + } - GetSecurityDescriptorOwner(descriptor, &owner, &owner_defaulted); - if (owner) - securityInformation |= OWNER_SECURITY_INFORMATION; + ctx->supported_features.not_context_indexed_files = 1; - GetSecurityDescriptorGroup(descriptor, &group, &group_defaulted); - if (group) - securityInformation |= GROUP_SECURITY_INFORMATION; +#if 0 + if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) + ctx->supported_features.sparse_files = 1; +#endif - GetSecurityDescriptorDacl(descriptor, &dacl_present, - &dacl, &dacl_defaulted); - if (dacl) - securityInformation |= DACL_SECURITY_INFORMATION; + if (vol_flags & FILE_NAMED_STREAMS) + ctx->supported_features.named_data_streams = 1; - GetSecurityDescriptorSacl(descriptor, &sacl_present, - &sacl, &sacl_defaulted); - if (sacl) - securityInformation |= SACL_SECURITY_INFORMATION; + if (vol_flags & FILE_SUPPORTS_HARD_LINKS) + ctx->supported_features.hard_links = 1; -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_REPARSE_POINTS) { + ctx->supported_features.reparse_points = 1; + if (func_CreateSymbolicLinkW) + ctx->supported_features.symlink_reparse_points = 1; } -} + if (vol_flags & FILE_PERSISTENT_ACLS) + ctx->supported_features.security_descriptors = 1; -static int -win32_extract_chunk(const void *buf, size_t len, void *arg) -{ - HANDLE hStream = arg; + if (supports_SetFileShortName) + ctx->supported_features.short_names = 1; - DWORD nbytes_written; - wimlib_assert(len <= 0xffffffff); + if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT) { - 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; -} + ret = load_prepopulate_pats(ctx); + if (ret == WIMLIB_ERR_NOMEM) + goto err; -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 (!wim_info_get_wimboot(ctx->wim->wim_info, + ctx->wim->current_image)) + WARNING("Image is not marked as WIMBoot compatible!"); -struct win32_encrypted_extract_ctx { - const struct wim_lookup_table_entry *lte; - u64 offset; -}; - -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; - len = min(len, wim_resource_size(lte) - ctx->offset); + ret = hash_lookup_table(ctx->wim, dat->wim_lookup_table_hash); + if (ret) + goto err; + + ret = wimboot_alloc_data_source_id(ctx->wim->filename, + ctx->wim->hdr.guid, + ctx->wim->current_image, + path, + &dat->data_source_id, + &dat->wof_running); + if (ret) + goto err; + } - if (read_partial_wim_resource_into_buf(lte, len, ctx->offset, data)) - return ERROR_READ_FAULT; + return 0; - ctx->offset += len; - *len_p = len; - return ERROR_SUCCESS; +err: + free_prepopulate_pats(dat); + return ret; } -/* 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. - */ static int -do_win32_extract_encrypted_stream(const wchar_t *path, - const struct wim_lookup_table_entry *lte) +win32_finish_extract(struct apply_ctx *ctx) { - void *file_ctx; - int ret; - - DEBUG("Opening file \"%ls\" to extract raw encrypted data", path); + free_prepopulate_pats(get_private_data(ctx)); + return 0; +} - 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; - } +/* Delete a non-directory file, working around Windows quirks. */ +static BOOL +win32_delete_file_wrapper(const wchar_t *path) +{ + DWORD err; + DWORD attrib; - if (lte) { - struct win32_encrypted_extract_ctx ctx; + if (DeleteFile(path)) + return TRUE; - 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); + err = GetLastError(); + attrib = GetFileAttributes(path); + if ((attrib != INVALID_FILE_ATTRIBUTES) && + (attrib & FILE_ATTRIBUTE_READONLY)) + { + /* Try again with FILE_ATTRIBUTE_READONLY cleared. */ + attrib &= ~FILE_ATTRIBUTE_READONLY; + if (SetFileAttributes(path, attrib)) { + if (DeleteFile(path)) + return TRUE; + else + err = GetLastError(); } } - CloseEncryptedFileRaw(file_ctx); - return ret; -} -static inline DWORD -win32_mask_attributes(DWORD i_attributes) -{ - 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); + SetLastError(err); + return FALSE; } -static inline DWORD -win32_get_create_flags_and_attributes(DWORD i_attributes) + +/* Create a normal file, overwriting one already present. */ +static int +win32_create_file(const wchar_t *path, struct apply_ctx *ctx, u64 *cookie_ret) { - /* - * 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(). + HANDLE h; + + /* Notes: * - * FILE_ATTRIBUTE_COMPRESSED: - * Not clear from the documentation, but apparently this needs an - * ioctl as well. - * See: win32_set_compressed(). + * WRITE_OWNER and WRITE_DAC privileges are required for some reason, + * even through we're creating a new file. * - * FILE_ATTRIBUTE_REPARSE_POINT: - * Needs an ioctl, with the reparse data specified. - * See: win32_set_reparse_data(). + * FILE_FLAG_OPEN_REPARSE_POINT is required to prevent an existing + * reparse point from redirecting the creation of the new file + * (potentially to an arbitrary location). * - * 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. + * CREATE_ALWAYS could be used instead of CREATE_NEW. However, there + * are quirks that would need to be handled (e.g. having to set + * FILE_ATTRIBUTE_HIDDEN and/or FILE_ATTRIBUTE_SYSTEM if the existing + * file had them specified, and/or having to clear + * FILE_ATTRIBUTE_READONLY on the existing file). It's simpler to just + * call win32_delete_file_wrapper() to delete the existing file in such + * a way that already handles the FILE_ATTRIBUTE_READONLY quirk. */ - return win32_mask_attributes(i_attributes) | - FILE_FLAG_OPEN_REPARSE_POINT | - FILE_FLAG_BACKUP_SEMANTICS; -} - -/* Set compression and/or sparse attributes on a stream, if supported by the - * volume. */ -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) -{ - 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); - } - } +retry: + h = CreateFile(path, WRITE_OWNER | WRITE_DAC, 0, NULL, CREATE_NEW, + FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if (h == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); - 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); - } + if (err == ERROR_FILE_EXISTS && win32_delete_file_wrapper(path)) + goto retry; + set_errno_from_win32_error(err); + return WIMLIB_ERR_OPEN; } + CloseHandle(h); return 0; } -/* Pre-create directories; extract encrypted streams */ 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_create_directory(const wchar_t *path, struct apply_ctx *ctx, + u64 *cookie_ret) { - DWORD err; - int ret; + if (!CreateDirectory(path, NULL)) + if (GetLastError() != ERROR_ALREADY_EXISTS) + goto error; + return 0; - /* 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) { - if (!win32_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; - } +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_MKDIR; +} - /* 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 && - !win32_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; - } +static int +win32_create_hardlink(const wchar_t *oldpath, const wchar_t *newpath, + struct apply_ctx *ctx) +{ + if (!CreateHardLink(newpath, oldpath, NULL)) { + if (GetLastError() != ERROR_ALREADY_EXISTS) + goto error; + if (!win32_delete_file_wrapper(newpath)) + goto error; + if (!CreateHardLink(newpath, oldpath, NULL)) + goto error; } return 0; + +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_LINK; } -/* 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) +win32_create_symlink(const wchar_t *oldpath, const wchar_t *newpath, + struct 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; - } - - /* 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; - } - - if (dentry_has_short_name(dentry) && !dentry->dos_name_invalid) - SetFileShortNameW(h, dentry->short_name); - else if (running_on_windows_7_or_later()) - SetFileShortNameW(h, L""); - } 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); - } + if (!(*func_CreateSymbolicLinkW)(newpath, oldpath, 0)) { + if (GetLastError() != ERROR_ALREADY_EXISTS) + goto error; + if (!win32_delete_file_wrapper(newpath)) + goto error; + if (!(*func_CreateSymbolicLinkW)(newpath, oldpath, 0)) + goto error; } - return ret; + return 0; + +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_LINK; } static int -win32_decrypt_file(HANDLE open_handle, const wchar_t *path) +win32_extract_wim_chunk(const void *buf, size_t len, void *arg) { - 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; - } + HANDLE h = (HANDLE)arg; + DWORD nbytes_written; + + if (unlikely(!WriteFile(h, buf, len, &nbytes_written, NULL))) + goto error; + if (unlikely(nbytes_written != len)) + goto error; return 0; + +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_WRITE; } -/* - * 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. - */ 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) +win32_extract_stream(const wchar_t *path, const wchar_t *stream_name, + size_t stream_name_nchars, + struct wim_lookup_table_entry *lte, struct apply_ctx *ctx) { - wchar_t *stream_path; + DWORD creationDisposition = OPEN_EXISTING; + wchar_t *stream_path = (wchar_t*)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; - - 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 a 1-character long relative path - * 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_nchars == 1 && !is_any_path_separator(path[0])) { - static const wchar_t _prefix[] = - {L'.', OS_PREFERRED_PATH_SEPARATOR, L'\0'}; - prefix = _prefix; - 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; - ret = win32_begin_extract_unnamed_stream(inode, lte, path, - &creationDisposition, - args->vol_flags); - if (ret) - goto fail; + if (stream_name_nchars) { + creationDisposition = CREATE_ALWAYS; + stream_path = alloca(sizeof(wchar_t) * + (wcslen(path) + 1 + + wcslen(stream_name) + 1)); + tsprintf(stream_path, L"%ls:%ls", path, stream_name); } - DEBUG("Opening \"%ls\"", stream_path); - requestedAccess = GENERIC_READ | GENERIC_WRITE | - ACCESS_SYSTEM_SECURITY; - /* DELETE access is needed for SetFileShortNameW(), for some reason. - * But don't request it for the extraction root, for the following - * reasons: - * - * - Requesting DELETE access on the extraction root will cause a - * sharing violation if the extraction root is the current working - * directory ("."). - * - The extraction root may be extracted to a different name than given - * in the WIM file, in which case the DOS name, if given, would not be - * meaningful. - * - For full-image extractions, the root dentry is supposed to be - * unnamed anyway. - * - Microsoft's ImageX does not extract the root directory. - */ - if (dentry != args->extract_root) - requestedAccess |= DELETE; -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); + h = CreateFile(stream_path, FILE_WRITE_DATA, 0, NULL, + creationDisposition, FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OPEN_REPARSE_POINT, + NULL); if (h == INVALID_HANDLE_VALUE) { - err = GetLastError(); - if (err == ERROR_ACCESS_DENIED && - win32_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 && - (inode->i_attributes & (FILE_ATTRIBUTE_ENCRYPTED | - FILE_ATTRIBUTE_DIRECTORY)) == - (FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_DIRECTORY)) - { - 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..."); - } - } - if (creationDisposition == OPEN_EXISTING) - ERROR("Failed to open \"%ls\"", stream_path); - else - ERROR("Failed to create \"%ls\"", stream_path); - win32_error(err); + set_errno_from_GetLastError(); ret = WIMLIB_ERR_OPEN; - goto fail; + goto out; } - /* 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; + if (!lte) { + ret = 0; + goto out_close_handle; } - /* 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; - } + if (!SetFilePointerEx(h, + (LARGE_INTEGER) { .QuadPart = lte->size}, + NULL, + FILE_BEGIN)) + goto write_error; - /* 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; - } + if (!SetEndOfFile(h)) + goto write_error; - /* Set compression and/or sparse attributes if needed */ - ret = win32_set_special_stream_attributes(h, inode, lte, path, - args->vol_flags); + if (!SetFilePointerEx(h, + (LARGE_INTEGER) { .QuadPart = 0}, + NULL, + FILE_BEGIN)) + goto write_error; - 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; + ret = extract_stream(lte, lte->size, win32_extract_wim_chunk, h); + goto out_close_handle; - /* Done extracting the stream. Close the handle and return. */ - DEBUG("Closing \"%ls\"", stream_path); +write_error: + set_errno_from_GetLastError(); + ret = WIMLIB_ERR_WRITE; + +out_close_handle: if (!CloseHandle(h)) { - err = GetLastError(); - ERROR("Failed to close \"%ls\"", stream_path); - win32_error(err); - ret = WIMLIB_ERR_WRITE; - goto fail; + if (!ret) { + set_errno_from_GetLastError(); + ret = WIMLIB_ERR_WRITE; + } } - ret = 0; - goto out; -fail_close_handle: - CloseHandle(h); -fail: - ERROR("Error extracting \"%ls\"", stream_path); out: return ret; } -/* - * 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. - * - * @dentry: WIM dentry for this file or directory. - * @path: UTF-16LE external path to extract the inode to. - * @args: Additional extraction context. - * - * Returns 0 on success; nonzero on failure. - */ static int -win32_extract_streams(const struct wim_dentry *dentry, - const wchar_t *path, struct apply_args *args) +win32_extract_unnamed_stream(file_spec_t file, + struct wim_lookup_table_entry *lte, + struct apply_ctx *ctx, + struct wim_dentry *dentry) { - struct wim_lookup_table_entry *unnamed_lte; - int ret; - const struct wim_inode *inode = dentry->d_inode; + if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT + && lte + && lte->resource_location == RESOURCE_IN_WIM + && lte->rspec->wim == ctx->wim + && lte->size == lte->rspec->uncompressed_size) + { + if (in_prepopulate_list(dentry, ctx)) { + if (ctx->progress_func) { + union wimlib_progress_info info; - /* First extract the unnamed stream. */ + info.wimboot_exclude.path_in_wim = dentry->_full_path; + info.wimboot_exclude.extraction_path = file.path; - unnamed_lte = inode_unnamed_lte_resolved(inode); - ret = win32_extract_stream(dentry, path, NULL, unnamed_lte, args); - if (ret) - goto out; + ctx->progress_func(WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE, + &info); + } + } else { + const struct win32_apply_private_data *dat; - /* Extract any named streams, if supported by the volume. */ + dat = get_private_data(ctx); + return wimboot_set_pointer(file.path, lte, + dat->data_source_id, + dat->wim_lookup_table_hash, + dat->wof_running); + } + } - if (!(args->vol_flags & FILE_NAMED_STREAMS)) + return win32_extract_stream(file.path, NULL, 0, lte, ctx); +} + +static int +win32_extract_named_stream(file_spec_t file, const wchar_t *stream_name, + size_t stream_name_nchars, + struct wim_lookup_table_entry *lte, struct apply_ctx *ctx) +{ + return win32_extract_stream(file.path, stream_name, + stream_name_nchars, lte, ctx); +} + +struct win32_encrypted_extract_ctx { + const struct wim_lookup_table_entry *lte; + u64 offset; +}; + +static DWORD WINAPI +win32_encrypted_import_cb(unsigned char *data, void *_import_ctx, + unsigned long *len_p) +{ + struct win32_encrypted_extract_ctx *import_ctx = _import_ctx; + unsigned long len = *len_p; + const struct wim_lookup_table_entry *lte = import_ctx->lte; + + len = min(len, lte->size - import_ctx->offset); + + if (read_partial_wim_stream_into_buf(lte, len, import_ctx->offset, data)) + return ERROR_READ_FAULT; + + import_ctx->offset += len; + *len_p = len; + return ERROR_SUCCESS; +} + +static int +win32_extract_encrypted_stream(const wchar_t *path, + struct wim_lookup_table_entry *lte, + struct apply_ctx *ctx) +{ + void *file_ctx; + DWORD err; + int ret; + struct win32_encrypted_extract_ctx extract_ctx; + + err = OpenEncryptedFileRaw(path, CREATE_FOR_IMPORT, &file_ctx); + if (err != ERROR_SUCCESS) { + set_errno_from_win32_error(err); + ret = WIMLIB_ERR_OPEN; goto out; - for (u16 i = 0; i < inode->i_num_ads; i++) { - const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i]; - - /* Skip the unnamed stream if it's in the ADS entries (we - * already extracted it...) */ - if (ads_entry->stream_name_nbytes == 0) - continue; - - /* 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; - - /* Extract the named stream */ - ret = win32_extract_stream(dentry, - path, - ads_entry->stream_name, - ads_entry->lte, - args); - if (ret) - break; } + + extract_ctx.lte = lte; + extract_ctx.offset = 0; + err = WriteEncryptedFileRaw(win32_encrypted_import_cb, &extract_ctx, + file_ctx); + if (err != ERROR_SUCCESS) { + set_errno_from_win32_error(err); + ret = WIMLIB_ERR_WRITE; + goto out_close; + } + + ret = 0; +out_close: + CloseEncryptedFileRaw(file_ctx); out: return ret; } -static int -dentry_clear_inode_visited(struct wim_dentry *dentry, void *_ignore) +static BOOL +win32_set_special_file_attributes(const wchar_t *path, u32 attributes) { - dentry->d_inode->i_visited = 0; - return 0; + HANDLE h; + DWORD err; + USHORT compression_format = COMPRESSION_FORMAT_DEFAULT; + DWORD bytes_returned; + + h = win32_open_existing_file(path, GENERIC_READ | GENERIC_WRITE); + if (h == INVALID_HANDLE_VALUE) + goto error; + + /* Don't make extracted files sparse. It is pointless without also + * skipping over runs of zeroes when writing the file, and in fact + * increases disk usage --- apparently, allocation sizes in sparse files + * are rounded up to multiples of 131072 bytes rather than 4096 bytes. + * And in some Windows 7 images, *all* files are set as sparse for some + * reason, which causes 1 GB+ of disk space to be wasted on the target + * drive of a full extraction. + * + * WIMGAPI seemingly does not make extracted files sparse either. + * + * XXX: We really ought to do a proper sparse extraction anyway if the + * file meets some heuristic that indicates this would be beneficial. + */ +#if 0 + if (attributes & FILE_ATTRIBUTE_SPARSE_FILE) + if (!DeviceIoControl(h, FSCTL_SET_SPARSE, + NULL, 0, + NULL, 0, + &bytes_returned, NULL)) + goto error_close_handle; +#endif + + if (attributes & FILE_ATTRIBUTE_COMPRESSED) + if (!DeviceIoControl(h, FSCTL_SET_COMPRESSION, + &compression_format, sizeof(USHORT), + NULL, 0, + &bytes_returned, NULL)) + goto error_close_handle; + + if (!CloseHandle(h)) + goto error; + + if (attributes & FILE_ATTRIBUTE_ENCRYPTED) + if (!EncryptFile(path)) + goto error; + + return TRUE; + +error_close_handle: + err = GetLastError(); + CloseHandle(h); + SetLastError(err); +error: + return FALSE; } static int -dentry_get_features(struct wim_dentry *dentry, void *_features_p) +win32_set_file_attributes(const wchar_t *path, u32 attributes, + struct apply_ctx *ctx, unsigned pass) { - DWORD features = 0; - DWORD *features_p = _features_p; - struct wim_inode *inode = dentry->d_inode; + u32 special_attributes = + FILE_ATTRIBUTE_REPARSE_POINT | + FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_SPARSE_FILE | + FILE_ATTRIBUTE_COMPRESSED | + FILE_ATTRIBUTE_ENCRYPTED; + u32 actual_attributes; + + /* Delay setting FILE_ATTRIBUTE_READONLY on the initial pass (when files + * are created, but data not extracted); otherwise the system will + * refuse access to the file even if the process has SeRestorePrivilege. + */ + if (pass == 0) + attributes &= ~FILE_ATTRIBUTE_READONLY; - 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; + if (!SetFileAttributes(path, attributes & ~special_attributes)) + goto error; + + if (pass != 0) + return 0; + + if (attributes & (FILE_ATTRIBUTE_SPARSE_FILE | + FILE_ATTRIBUTE_ENCRYPTED | + FILE_ATTRIBUTE_COMPRESSED)) + if (!win32_set_special_file_attributes(path, attributes)) + goto error; + + /* If file is not supposed to be encrypted or compressed, remove + * defaulted encrypted or compressed attributes (from creating file in + * encrypted or compressed directory). */ + actual_attributes = GetFileAttributes(path); + if (actual_attributes == INVALID_FILE_ATTRIBUTES) + goto error; + + if ((actual_attributes & FILE_ATTRIBUTE_ENCRYPTED) && + !(attributes & FILE_ATTRIBUTE_ENCRYPTED)) + if (!DecryptFile(path, 0)) + goto error; + if ((actual_attributes & FILE_ATTRIBUTE_COMPRESSED) && + !(attributes & FILE_ATTRIBUTE_COMPRESSED)) + { + HANDLE h; + DWORD bytes_returned; + USHORT compression_format = COMPRESSION_FORMAT_NONE; + + h = win32_open_existing_file(path, GENERIC_READ | GENERIC_WRITE); + if (h == INVALID_HANDLE_VALUE) + goto error; + + if (!DeviceIoControl(h, FSCTL_SET_COMPRESSION, + &compression_format, sizeof(USHORT), + NULL, 0, + &bytes_returned, NULL)) + { + DWORD err = GetLastError(); + CloseHandle(h); + SetLastError(err); + goto error; + } + + if (!CloseHandle(h)) + goto error; } - *features_p |= features; + return 0; + +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_SET_ATTRIBUTES; } -/* 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. */ static int -win32_check_vol_flags(const wchar_t *output_path, - struct wim_dentry *root, struct apply_args *args) +win32_set_reparse_data(const wchar_t *path, const u8 *rpbuf, u16 rpbuflen, + struct apply_ctx *ctx) { - DWORD dentry_features = 0; - DWORD missing_features; - - if (args->have_vol_flags) - return 0; + HANDLE h; + DWORD err; + DWORD bytes_returned; - for_dentry_in_tree(root, dentry_clear_inode_visited, NULL); - for_dentry_in_tree(root, dentry_get_features, &dentry_features); + h = win32_open_existing_file(path, GENERIC_WRITE); + if (h == INVALID_HANDLE_VALUE) + goto error; - win32_get_vol_flags(output_path, &args->vol_flags); - args->have_vol_flags = true; + if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, + (void*)rpbuf, rpbuflen, + NULL, 0, &bytes_returned, NULL)) + goto error_close_handle; - missing_features = dentry_features & ~args->vol_flags; + if (!CloseHandle(h)) + goto error; - /* 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; - } else { - WARNING("Volume does not support persistent ACLS!\n" - " File permissions will not be extracted."); - } - } - 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; + +error_close_handle: + err = GetLastError(); + CloseHandle(h); + SetLastError(err); +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_WRITE; /* XXX: need better error code */ } -/* - * 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. - */ static int -win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode, - struct apply_args *args) +win32_set_short_name(const wchar_t *path, const wchar_t *short_name, + size_t short_name_nchars, struct apply_ctx *ctx) { + HANDLE h; DWORD err; - /* 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. */ + h = win32_open_existing_file(path, GENERIC_WRITE | DELETE); + if (h == INVALID_HANDLE_VALUE) + goto error; - DEBUG("Creating hard link \"%ls => %ls\"", - output_path, inode->i_extracted_file); + if (short_name_nchars) { + if (!SetFileShortName(h, short_name)) + goto error_close_handle; + } else if (running_on_windows_7_or_later()) { + if (!SetFileShortName(h, L"")) + goto error_close_handle; + } - if (running_on_windows_7_or_later() && - !(args->vol_flags & FILE_SUPPORTS_HARD_LINKS)) - goto hard_links_unsupported; + if (!CloseHandle(h)) + goto error; - if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL)) - return 0; + return 0; +error_close_handle: 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; - } -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 (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) - WARNING("Suppressing further hard linking warnings..."); - return -1; + CloseHandle(h); + SetLastError(err); +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_WRITE; /* XXX: need better error code */ } -/* 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) +/* + * Set an arbitrary security descriptor on an arbitrary file (or directory), + * working around bugs and design flaws in the Windows operating system. + * + * On success, return 0. On failure, return WIMLIB_ERR_SET_SECURITY and set + * errno. Note: if WIMLIB_EXTRACT_FLAG_STRICT_ACLS is not set in + * ctx->extract_flags, this function succeeds iff any part of the security + * descriptor was successfully set. + */ +static int +win32_set_security_descriptor(const wchar_t *path, const u8 *desc, + size_t desc_size, struct apply_ctx *ctx) { + SECURITY_INFORMATION info; + DWORD dwDesiredAccess; + HANDLE h; + DWORD status; int ret; - struct wim_inode *inode = dentry->d_inode; - 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. */ + /* We really just want to set entire the security descriptor as-is, but + * all available APIs require specifying the specific parts of the + * descriptor being set. Start out by requesting all parts be set. If + * permissions problems are encountered, fall back to omitting some + * parts (first the SACL, then the DACL, then the owner), unless the + * WIMLIB_EXTRACT_FLAG_STRICT_ACLS flag has been enabled. */ + info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION; + + /* Prefer NtSetSecurityObject() to SetFileSecurity(). SetFileSecurity() + * itself necessarily uses NtSetSecurityObject() as the latter is the + * underlying system call for setting security information, but + * SetFileSecurity() opens the handle with NtCreateFile() without + * FILE_OPEN_FILE_BACKUP_INTENT. Hence, access checks are done and due + * to the Windows security model, even a process running as the + * Administrator can have access denied. (Of course, this not mentioned + * in the MS "documentation".) */ + + /* Open a handle for NtSetSecurityObject() with as many relevant + * access rights as possible. + * + * We don't know which rights will be actually granted. It + * could be less than what is needed to actually assign the full + * security descriptor, especially if the process is running as + * a non-Administrator. However, by default we just do the best + * we can, unless WIMLIB_EXTRACT_FLAG_STRICT_ACLS has been + * enabled. The MAXIMUM_ALLOWED access right is seemingly + * designed for this use case; however, it does not work + * properly in all cases: it can cause CreateFile() to fail with + * ERROR_ACCESS_DENIED, even though by definition + * MAXIMUM_ALLOWED access only requests access rights that are + * *not* denied. (Needless to say, MS does not document this + * bug.) */ + + dwDesiredAccess = WRITE_DAC | WRITE_OWNER | ACCESS_SYSTEM_SECURITY; + while ((h = win32_open_existing_file(path, + dwDesiredAccess)) == INVALID_HANDLE_VALUE) + { + DWORD err; + + err = GetLastError(); + if (err == ERROR_ACCESS_DENIED || + err == ERROR_PRIVILEGE_NOT_HELD) + { + /* Don't increment partial_security_descriptors + * here or check WIMLIB_EXTRACT_FLAG_STRICT_ACLS + * here. It will be done later if needed; here + * we are just trying to get as many relevant + * access rights as possible. */ + if (dwDesiredAccess & ACCESS_SYSTEM_SECURITY) { + dwDesiredAccess &= ~ACCESS_SYSTEM_SECURITY; + continue; + } + if (dwDesiredAccess & WRITE_DAC) { + dwDesiredAccess &= ~WRITE_DAC; + continue; + } + if (dwDesiredAccess & WRITE_OWNER) { + dwDesiredAccess &= ~WRITE_OWNER; + continue; + } + } + /* Other error, or couldn't open the file even with no + * access rights specified. Something else must be + * wrong. */ + set_errno_from_win32_error(err); + return WIMLIB_ERR_SET_SECURITY; } - /* 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)) + /* Try setting the security descriptor. */ + ret = 0; + while (!(NT_SUCCESS(status = (*func_NtSetSecurityObject)(h, + info, + (PSECURITY_DESCRIPTOR)desc)))) { - WARNING("Not extracting reparse point \"%ls\"", output_path); - dentry->not_extracted = 1; - } 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; - } - 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); - } - if (inode->i_nlink > 1) { - /* Save extracted path for a later call to - * CreateHardLinkW() if this inode has multiple links. - * */ - inode->i_extracted_file = WCSDUP(output_path); - if (!inode->i_extracted_file) - return WIMLIB_ERR_NOMEM; + /* 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->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) + { + if (info & SACL_SECURITY_INFORMATION) { + info &= ~SACL_SECURITY_INFORMATION; + ctx->partial_security_descriptors++; + continue; + } + if (info & DACL_SECURITY_INFORMATION) { + info &= ~DACL_SECURITY_INFORMATION; + continue; + } + if (info & OWNER_SECURITY_INFORMATION) { + info &= ~OWNER_SECURITY_INFORMATION; + continue; + } + /* 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--; + set_errno_from_nt_status(status); + ret = WIMLIB_ERR_SET_SECURITY; + break; } - return 0; + + /* Close handle opened for NtSetSecurityObject(). */ + CloseHandle(h); + return ret; } -/* 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 +win32_set_timestamps(const wchar_t *path, u64 creation_time, + u64 last_write_time, u64 last_access_time, + struct apply_ctx *ctx) { - DWORD err; HANDLE h; - const struct wim_inode *inode = dentry->d_inode; - - /* 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 (win32_path_is_root_of_drive(path)) - return 0; + DWORD err; + FILETIME creationTime = {.dwLowDateTime = creation_time & 0xffffffff, + .dwHighDateTime = creation_time >> 32}; + FILETIME lastAccessTime = {.dwLowDateTime = last_access_time & 0xffffffff, + .dwHighDateTime = last_access_time >> 32}; + FILETIME lastWriteTime = {.dwLowDateTime = last_write_time & 0xffffffff, + .dwHighDateTime = last_write_time >> 32}; - 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; - } + if (h == INVALID_HANDLE_VALUE) + goto error; - 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}; + if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) + goto error_close_handle; + + if (!CloseHandle(h)) + goto error; - DEBUG("Calling SetFileTime() on \"%ls\"", path); - if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) { - err = GetLastError(); - CloseHandle(h); - goto fail; - } - DEBUG("Closing \"%ls\"", path); - if (!CloseHandle(h)) { - err = GetLastError(); - goto fail; - } - goto out; -fail: - /* Only warn if setting timestamps failed; still return 0. */ - WARNING("Can't set timestamps on \"%ls\"", path); - win32_error(err); -out: return 0; + +error_close_handle: + err = GetLastError(); + CloseHandle(h); + SetLastError(err); +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_SET_TIMESTAMPS; } +const struct apply_operations win32_apply_ops = { + .name = L"Win32", + + .target_is_root = win32_path_is_root_of_drive, + .start_extract = win32_start_extract, + .finish_extract = win32_finish_extract, + .abort_extract = win32_finish_extract, + .create_file = win32_create_file, + .create_directory = win32_create_directory, + .create_hardlink = win32_create_hardlink, + .create_symlink = win32_create_symlink, + .extract_unnamed_stream = win32_extract_unnamed_stream, + .extract_named_stream = win32_extract_named_stream, + .extract_encrypted_stream = win32_extract_encrypted_stream, + .set_file_attributes = win32_set_file_attributes, + .set_reparse_data = win32_set_reparse_data, + .set_short_name = win32_set_short_name, + .set_security_descriptor = win32_set_security_descriptor, + .set_timestamps = win32_set_timestamps, + + .path_prefix = L"\\\\?\\", + .path_prefix_nchars = 4, + .path_separator = L'\\', + .path_max = 32768, + + .requires_realtarget_in_paths = 1, + .realpath_works_on_nonexisting_files = 1, + .root_directory_is_special = 1, + .requires_final_set_attributes_pass = 1, + .extract_encrypted_stream_creates_file = 1, + .requires_short_name_reordering = 1, /* TODO: check if this is really needed */ +}; + #endif /* __WIN32__ */