X-Git-Url: https://wimlib.net/git/?a=blobdiff_plain;f=src%2Fwin32_apply.c;h=a662cbf33ea94d0f306cd1bf84f8e4c8d883b313;hb=0b1278f508ef7606c822edadb3958c2c3648b419;hp=3e4856b08c9c7ebcdf425fe9e63a1ab141ce0b97;hpb=a10b5d147146cf9d6b0279aca63474b101df9e2f;p=wimlib diff --git a/src/win32_apply.c b/src/win32_apply.c index 3e4856b0..a662cbf3 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -3,7 +3,7 @@ */ /* - * Copyright (C) 2013, 2014 Eric Biggers + * Copyright (C) 2013, 2014, 2015 Eric Biggers * * 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 @@ -29,10 +29,11 @@ #include "wimlib/apply.h" #include "wimlib/assert.h" +#include "wimlib/blob_table.h" #include "wimlib/capture.h" /* for mangle_pat() and match_pattern_list() */ #include "wimlib/dentry.h" +#include "wimlib/encoding.h" #include "wimlib/error.h" -#include "wimlib/lookup_table.h" #include "wimlib/metadata.h" #include "wimlib/paths.h" #include "wimlib/reparse.h" @@ -52,7 +53,7 @@ struct win32_apply_ctx { u64 data_source_id; struct string_set *prepopulate_pats; void *mem_prepopulate_pats; - u8 wim_lookup_table_hash[SHA1_HASH_SIZE]; + u8 blob_table_hash[SHA1_HASH_SIZE]; bool wof_running; bool tried_to_load_prepopulate_list; } wimboot; @@ -78,7 +79,7 @@ struct win32_apply_ctx { * target-relative NT paths */ wchar_t *print_buffer; - /* Allocated buffer for reading stream data when it cannot be extracted + /* Allocated buffer for reading blob data when it cannot be extracted * directly */ u8 *data_buffer; @@ -103,20 +104,20 @@ struct win32_apply_ctx { /* Array of open handles to filesystem streams currently being written */ - HANDLE open_handles[MAX_OPEN_STREAMS]; + HANDLE open_handles[MAX_OPEN_FILES]; /* 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. */ + * extracted as soon as the whole blob 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. */ + * encrypted data extracted as soon as the whole blob 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 @@ -178,11 +179,9 @@ get_vol_flags(const wchar_t *target, DWORD *vol_flags_ret, vol_flags_ret, filesystem_name, ARRAY_LEN(filesystem_name))) { - DWORD err = GetLastError(); - set_errno_from_win32_error(err); - WARNING_WITH_ERRNO("Failed to get volume information for " - "\"%ls\" (err=%"PRIu32")", - target, (u32)err); + win32_warning(GetLastError(), + L"Failed to get volume information for \"%ls\"", + target); return; } @@ -278,7 +277,7 @@ load_prepopulate_pats(struct win32_apply_ctx *ctx) { const wchar_t *path = L"\\Windows\\System32\\WimBootCompress.ini"; struct wim_dentry *dentry; - struct wim_lookup_table_entry *lte; + struct blob_descriptor *blob; int ret; void *buf; struct string_set *s; @@ -292,13 +291,14 @@ load_prepopulate_pats(struct win32_apply_ctx *ctx) (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))) + !(blob = inode_get_blob_for_unnamed_data_stream(dentry->d_inode, + ctx->common.wim->blob_table))) { WARNING("%ls does not exist in WIM image!", path); return WIMLIB_ERR_PATH_DOES_NOT_EXIST; } - ret = read_full_stream_into_alloc_buf(lte, &buf); + ret = read_full_blob_into_alloc_buf(blob, &buf); if (ret) return ret; @@ -311,7 +311,7 @@ load_prepopulate_pats(struct win32_apply_ctx *ctx) sec.name = T("PrepopulateList"); sec.strings = s; - ret = do_load_text_file(path, buf, lte->size, &mem, &sec, 1, + ret = do_load_text_file(path, buf, blob->size, &mem, &sec, 1, LOAD_TEXT_FILE_REMOVE_QUOTES | LOAD_TEXT_FILE_NO_WARNINGS, mangle_pat); @@ -376,7 +376,7 @@ will_externally_back_inode(struct wim_inode *inode, struct win32_apply_ctx *ctx, { struct list_head *next; struct wim_dentry *dentry; - struct wim_lookup_table_entry *stream; + struct blob_descriptor *blob; int ret; if (inode->i_can_externally_back) @@ -392,12 +392,15 @@ will_externally_back_inode(struct wim_inode *inode, struct win32_apply_ctx *ctx, FILE_ATTRIBUTE_ENCRYPTED)) return WIM_BACKING_NOT_POSSIBLE; - stream = inode_unnamed_lte_resolved(inode); + blob = inode_get_blob_for_unnamed_data_stream_resolved(inode); - if (!stream || - stream->resource_location != RESOURCE_IN_WIM || - stream->rspec->wim != ctx->common.wim || - stream->size != stream->rspec->uncompressed_size) + /* Note: Microsoft's WoF driver errors out if it tries to satisfy a + * read, with ending offset >= 4 GiB, from an externally backed file. */ + if (!blob || + blob->blob_location != BLOB_IN_WIM || + blob->rdesc->wim != ctx->common.wim || + blob->size != blob->rdesc->uncompressed_size || + blob->size > 4200000000) return WIM_BACKING_NOT_POSSIBLE; /* @@ -474,29 +477,27 @@ set_external_backing(HANDLE h, struct wim_inode *inode, struct win32_apply_ctx * } else { /* Externally backing. */ if (unlikely(!wimboot_set_pointer(h, - inode_unnamed_lte_resolved(inode), + inode_get_blob_for_unnamed_data_stream_resolved(inode), ctx->wimboot.data_source_id, - ctx->wimboot.wim_lookup_table_hash, + ctx->wimboot.blob_table_hash, ctx->wimboot.wof_running))) { const DWORD err = GetLastError(); build_extraction_path(inode_first_extraction_dentry(inode), ctx); - set_errno_from_win32_error(err); - ERROR_WITH_ERRNO("\"%ls\": Couldn't set WIMBoot " - "pointer data (err=%"PRIu32")", - current_path(ctx), (u32)err); + win32_error(err, L"\"%ls\": Couldn't set WIMBoot pointer data", + current_path(ctx)); return WIMLIB_ERR_WIMBOOT; } return 0; } } -/* Calculates the SHA-1 message digest of the WIM's lookup table. */ +/* Calculates the SHA-1 message digest of the WIM's blob table. */ static int -hash_lookup_table(WIMStruct *wim, u8 hash[SHA1_HASH_SIZE]) +hash_blob_table(WIMStruct *wim, u8 hash[SHA1_HASH_SIZE]) { - return wim_reshdr_to_hash(&wim->hdr.lookup_table_reshdr, wim, hash); + return wim_reshdr_to_hash(&wim->hdr.blob_table_reshdr, wim, hash); } /* Prepare for doing a "WIMBoot" extraction by loading patterns from @@ -515,8 +516,7 @@ start_wimboot_extraction(struct win32_apply_ctx *ctx) if (!wim_info_get_wimboot(wim->wim_info, wim->current_image)) WARNING("Image is not marked as WIMBoot compatible!"); - ret = hash_lookup_table(ctx->common.wim, - ctx->wimboot.wim_lookup_table_hash); + ret = hash_blob_table(ctx->common.wim, ctx->wimboot.blob_table_hash); if (ret) return ret; @@ -593,10 +593,9 @@ out_unload_key: out_check_res: if (res) { /* Warning only. */ - set_errno_from_win32_error(res); - WARNING_WITH_ERRNO("Failed to set \\Setup: dword \"WimBoot\"=1 value " - "in registry hive \"%ls\" (err=%"PRIu32")", - ctx->pathbuf.Buffer, (u32)res); + win32_warning(res, L"Failed to set \\Setup: dword \"WimBoot\"=1 " + "value in registry hive \"%ls\"", + ctx->pathbuf.Buffer); } out: return 0; @@ -631,8 +630,11 @@ 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; + for (unsigned i = 0; i < inode->i_num_streams; i++) { + const struct wim_inode_stream *strm = &inode->i_streams[i]; + if (!stream_is_named_data_stream(strm)) + continue; + size_t len = utf16le_len_bytes(strm->stream_name); if (len > max) max = len; } @@ -790,10 +792,8 @@ open_target_directory(struct win32_apply_ctx *ctx) NULL, 0); if (!NT_SUCCESS(status)) { - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't open or create directory \"%ls\" " - "(status=0x%08"PRIx32")", - ctx->common.target, (u32)status); + winnt_error(status, L"Can't open or create directory \"%ls\"", + ctx->common.target); return WIMLIB_ERR_OPENDIR; } ctx->attr.RootDirectory = ctx->h_target; @@ -923,81 +923,11 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry, if (NT_SUCCESS(status)) return 0; - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't %s compression attribute on \"%ls\" " - "(status=0x%08"PRIx32")", - (compressed ? "set" : "clear"), - current_path(ctx), status); + winnt_error(status, L"Can't %s compression attribute on \"%ls\"", + (compressed ? "set" : "clear"), current_path(ctx)); return WIMLIB_ERR_SET_ATTRIBUTES; } -/* - * Clear FILE_ATTRIBUTE_ENCRYPTED if the file or directory is not supposed to be - * encrypted. - * - * You can provide FILE_ATTRIBUTE_ENCRYPTED to NtCreateFile() to set it on the - * created file. However, the file or directory will otherwise default to the - * encryption state of the parent directory. This function works around this - * limitation by using DecryptFile() to remove FILE_ATTRIBUTE_ENCRYPTED on files - * (and directories) that are not supposed to have it set. - * - * Regardless of whether it succeeds or fails, this function may close the - * handle to the file. If it does, it sets it to NULL. - */ -static int -maybe_clear_encryption_attribute(HANDLE *h_ptr, const struct wim_dentry *dentry, - struct win32_apply_ctx *ctx) -{ - if (dentry->d_inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) - return 0; - - if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES) - return 0; - - if (!ctx->common.supported_features.encrypted_files) - return 0; - - FILE_BASIC_INFORMATION info; - NTSTATUS status; - BOOL bret; - - /* Get current attributes */ - status = (*func_NtQueryInformationFile)(*h_ptr, &ctx->iosb, - &info, sizeof(info), - FileBasicInformation); - if (NT_SUCCESS(status) && - !(info.FileAttributes & FILE_ATTRIBUTE_ENCRYPTED)) - { - /* Nothing needs to be done. */ - return 0; - } - - /* Set the new encryption state */ - - /* Due to Windows' crappy file encryption APIs, we need to close the - * handle to the file so we don't get ERROR_SHARING_VIOLATION. We also - * hack together a Win32 path, although we will use the \\?\ prefix so - * it will actually be a NT path in disguise... */ - (*func_NtClose)(*h_ptr); - *h_ptr = NULL; - - build_win32_extraction_path(dentry, ctx); - - bret = DecryptFile(ctx->pathbuf.Buffer, 0); - - /* Restore the NT namespace path */ - build_extraction_path(dentry, ctx); - - if (!bret) { - DWORD err = GetLastError(); - set_errno_from_win32_error(err); - ERROR_WITH_ERRNO("Can't decrypt file \"%ls\" (err=%"PRIu32")", - current_path(ctx), (u32)err); - return WIMLIB_ERR_SET_ATTRIBUTES; - } - return 0; -} - /* Try to enable short name support on the target volume. If successful, return * true. If unsuccessful, issue a warning and return false. */ static bool @@ -1030,8 +960,9 @@ try_to_enable_short_names(const wchar_t *volume) return true; fail: - WARNING("Failed to enable short name support on %ls " - "(err=%"PRIu32")", volume + 4, (u32)GetLastError()); + win32_warning(GetLastError(), + L"Failed to enable short name support on %ls", + volume + 4); return false; } @@ -1063,8 +994,7 @@ remove_conflicting_short_name(const struct wim_dentry *dentry, struct win32_appl FILE_SHARE_VALID_FLAGS, FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT); if (!NT_SUCCESS(status)) { - WARNING("Can't open \"%ls\" (status=0x%08"PRIx32")", - current_path(ctx), (u32)status); + winnt_warning(status, L"Can't open \"%ls\"", current_path(ctx)); goto out; } @@ -1203,13 +1133,7 @@ retry: return 0; } - if (status == STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME) { - ERROR("Can't set short name when short " - "names are not enabled on the volume!"); - } else { - ERROR("Can't set short name on \"%ls\" (status=0x%08"PRIx32")", - current_path(ctx), (u32)status); - } + winnt_error(status, L"Can't set short name on \"%ls\"", current_path(ctx)); return WIMLIB_ERR_SET_SHORT_NAME; } @@ -1266,73 +1190,148 @@ create_file(PHANDLE FileHandle, ctx); } -/* Create empty named data streams. +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 for the specified file, if there are any. * - * 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. + * Since these won't have blob descriptors, they won't show up in the call to + * extract_blob_list(). Hence the need for the special case. */ static int -create_any_empty_ads(const struct wim_dentry *dentry, - struct win32_apply_ctx *ctx) +create_empty_named_data_streams(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx) { const struct wim_inode *inode = dentry->d_inode; - LARGE_INTEGER allocation_size; bool path_modified = false; int ret = 0; if (!ctx->common.supported_features.named_data_streams) return 0; - for (u16 i = 0; i < inode->i_num_ads; i++) { - const struct wim_ads_entry *entry; - NTSTATUS status; + for (unsigned i = 0; i < inode->i_num_streams; i++) { + const struct wim_inode_stream *strm = &inode->i_streams[i]; HANDLE h; - bool retried; - DWORD disposition; - - entry = &inode->i_ads_entries[i]; - /* Not named? */ - if (!entry->stream_name_nbytes) + if (!stream_is_named_data_stream(strm) || + stream_blob_resolved(strm) != NULL) continue; - /* Not empty? */ - if (entry->lte) - continue; - - /* Probably setting the allocation size to 0 has no effect, but - * we might as well try. */ - allocation_size.QuadPart = 0; - build_extraction_path_with_ads(dentry, ctx, - entry->stream_name, - entry->stream_name_nbytes / - sizeof(wchar_t)); + strm->stream_name, + utf16le_len_chars(strm->stream_name)); path_modified = true; - - retried = false; - disposition = FILE_SUPERSEDE; - retry: - status = do_create_file(&h, FILE_WRITE_DATA, &allocation_size, - 0, disposition, 0, ctx); - if (unlikely(!NT_SUCCESS(status))) { - if (status == STATUS_OBJECT_NAME_NOT_FOUND && !retried) { - /* Workaround for defect in the Windows PE - * in-memory filesystem implementation: - * FILE_SUPERSEDE does not create the file, as - * expected and documented, when the named file - * does not exist. */ - retried = true; - disposition = FILE_CREATE; - goto retry; - } - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't create \"%ls\" " - "(status=0x%08"PRIx32")", - current_path(ctx), (u32)status); - ret = WIMLIB_ERR_OPEN; + ret = supersede_file_or_stream(ctx, &h); + if (ret) break; - } (*func_NtClose)(h); } /* Restore the path to the dentry itself */ @@ -1349,38 +1348,52 @@ create_any_empty_ads(const struct wim_dentry *dentry, * 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) +create_directory(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) { - HANDLE h; + DWORD perms; NTSTATUS status; + HANDLE h; int ret; - /* DELETE is needed for set_short_name(). - * GENERIC_READ and GENERIC_WRITE are needed for - * adjust_compression_attribute(). */ - status = create_file(&h, GENERIC_READ | GENERIC_WRITE | DELETE, NULL, - 0, FILE_OPEN_IF, FILE_DIRECTORY_FILE, - dentry, ctx); + /* DELETE is needed for set_short_name(); GENERIC_READ and GENERIC_WRITE + * are needed for adjust_compression_attribute(). */ + perms = GENERIC_READ | GENERIC_WRITE; + if (!dentry_is_root(dentry)) + perms |= DELETE; + + /* 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, perms, NULL, FILE_ATTRIBUTE_SYSTEM, + FILE_OPEN_IF, FILE_DIRECTORY_FILE, dentry, ctx); if (!NT_SUCCESS(status)) { - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't create directory \"%ls\" " - "(status=0x%08"PRIx32")", - current_path(ctx), (u32)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); + if (ctx->iosb.Information == FILE_OPENED) { + /* If we opened an existing directory, try to clear its file + * attributes. As far as I know, this only actually makes a + * difference in the case where a FILE_ATTRIBUTE_READONLY + * directory has a named data stream which needs to be + * extracted. You cannot create a named data stream of such a + * directory, even though this contradicts Microsoft's + * documentation for FILE_ATTRIBUTE_READONLY which states it is + * not honored for directories! */ + FILE_BASIC_INFORMATION basic_info = { .FileAttributes = FILE_ATTRIBUTE_NORMAL }; + (*func_NtSetInformationFile)(h, &ctx->iosb, &basic_info, + sizeof(basic_info), FileBasicInformation); + } - if (!ret) - ret = maybe_clear_encryption_attribute(&h, dentry, ctx); - /* May close the handle!!! */ + if (!dentry_is_root(dentry)) { + ret = set_short_name(h, dentry, ctx); + if (ret) + goto out; + } - if (h) - (*func_NtClose)(h); + ret = adjust_compression_attribute(h, dentry, ctx); +out: + (*func_NtClose)(h); return ret; } @@ -1407,19 +1420,14 @@ create_directories(struct list_head *dentry_list, * 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_directory(dentry, ctx); - ret = create_any_empty_ads(dentry, ctx); - ret = check_apply_error(dentry, ctx, ret); - if (ret) - return ret; - } + if (!ret) + ret = create_empty_named_data_streams(dentry, ctx); + + ret = check_apply_error(dentry, ctx, ret); + if (ret) + return ret; ret = report_file_created(&ctx->common); if (ret) @@ -1433,128 +1441,36 @@ create_directories(struct list_head *dentry_list, * * 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 WIMLIB_ERR_OPEN. + * 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) { - const struct wim_inode *inode; - ULONG attrib; - NTSTATUS status; - bool retried = false; - DWORD disposition; - - inode = dentry->d_inode; + int ret; + HANDLE h; - /* If the file already exists and has FILE_ATTRIBUTE_SYSTEM and/or - * FILE_ATTRIBUTE_HIDDEN, these must be specified in order to supersede - * the file. - * - * Normally the user shouldn't be trying to overwrite such files anyway, - * but we at least provide FILE_ATTRIBUTE_SYSTEM and - * FILE_ATTRIBUTE_HIDDEN if the WIM inode has those attributes so that - * we catch the case where the user extracts the same files to the same - * location more than one time. - * - * Also specify FILE_ATTRIBUTE_ENCRYPTED if the file needs to be - * encrypted. - * - * In NO_ATTRIBUTES mode just don't specify any attributes at all. - */ - if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES) { - attrib = 0; - } else { - attrib = (inode->i_attributes & (FILE_ATTRIBUTE_SYSTEM | - FILE_ATTRIBUTE_HIDDEN | - FILE_ATTRIBUTE_ENCRYPTED)); - } build_extraction_path(dentry, ctx); - disposition = FILE_SUPERSEDE; -retry: - status = do_create_file(h_ret, GENERIC_READ | GENERIC_WRITE | DELETE, - NULL, attrib, disposition, - FILE_NON_DIRECTORY_FILE, ctx); - if (likely(NT_SUCCESS(status))) { - int ret; - - ret = adjust_compression_attribute(*h_ret, dentry, ctx); - if (ret) { - (*func_NtClose)(*h_ret); - return ret; - } - - ret = maybe_clear_encryption_attribute(h_ret, dentry, ctx); - /* May close the handle!!! */ - - if (ret) { - if (*h_ret) - (*func_NtClose)(*h_ret); - return ret; - } - - if (!*h_ret) { - /* Re-open the handle so that we can return it on - * success. */ - status = do_create_file(h_ret, - GENERIC_READ | - GENERIC_WRITE | DELETE, - NULL, 0, FILE_OPEN, - FILE_NON_DIRECTORY_FILE, ctx); - if (!NT_SUCCESS(status)) - goto fail; - } - ret = create_any_empty_ads(dentry, ctx); - if (ret) { - (*func_NtClose)(*h_ret); - return ret; - } - return 0; - } - - if (status == STATUS_OBJECT_NAME_NOT_FOUND && !retried) { - /* Workaround for defect in the Windows PE in-memory filesystem - * implementation: FILE_SUPERSEDE does not create the file, as - * expected and documented, when the named file does not exist. - */ - retried = true; - disposition = FILE_CREATE; - goto retry; - } - - if (status == STATUS_ACCESS_DENIED && !retried) { - /* We also can't supersede an existing file that has - * FILE_ATTRIBUTE_READONLY set; doing so causes NtCreateFile() - * to return STATUS_ACCESS_DENIED . The only workaround seems - * to be to explicitly remove FILE_ATTRIBUTE_READONLY on the - * existing file, then try again. */ + ret = supersede_file_or_stream(ctx, &h); + if (ret) + goto out; - FILE_BASIC_INFORMATION info; - HANDLE h; + ret = adjust_compression_attribute(h, dentry, ctx); + if (ret) + goto out_close; - status = do_create_file(&h, FILE_WRITE_ATTRIBUTES, NULL, 0, - FILE_OPEN, FILE_NON_DIRECTORY_FILE, ctx); - if (!NT_SUCCESS(status)) - goto fail; + ret = create_empty_named_data_streams(dentry, ctx); + if (ret) + goto out_close; - memset(&info, 0, sizeof(info)); - info.FileAttributes = FILE_ATTRIBUTE_NORMAL; + *h_ret = h; + return 0; - status = (*func_NtSetInformationFile)(h, &ctx->iosb, - &info, sizeof(info), - FileBasicInformation); - (*func_NtClose)(h); - if (!NT_SUCCESS(status)) - goto fail; - retried = true; - goto retry; - } -fail: - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't create file \"%ls\" (status=0x%08"PRIx32")", - current_path(ctx), (u32)status); - return WIMLIB_ERR_OPEN; +out_close: + (*func_NtClose)(h); +out: + return ret; } /* Creates a hard link at the location named by @dentry to the file represented @@ -1590,8 +1506,8 @@ create_link(HANDLE h, const struct wim_dentry *dentry, FileLinkInformation); if (NT_SUCCESS(status)) return 0; - ERROR("Failed to create link \"%ls\" (status=0x%08"PRIx32")", - current_path(ctx), (u32)status); + winnt_error(status, L"Failed to create link \"%ls\"", + current_path(ctx)); return WIMLIB_ERR_LINK; } else { HANDLE h2; @@ -1702,21 +1618,21 @@ close_handles(struct win32_apply_ctx *ctx) (*func_NtClose)(ctx->open_handles[i]); } -/* Prepare to read the next stream, which has size @stream_size, into an - * in-memory buffer. */ +/* Prepare to read the next blob, which has size @blob_size, into an in-memory + * buffer. */ static bool -prepare_data_buffer(struct win32_apply_ctx *ctx, u64 stream_size) +prepare_data_buffer(struct win32_apply_ctx *ctx, u64 blob_size) { - if (stream_size > ctx->data_buffer_size) { + if (blob_size > ctx->data_buffer_size) { /* Larger buffer needed. */ void *new_buffer; - if ((size_t)stream_size != stream_size) + if ((size_t)blob_size != blob_size) return false; - new_buffer = REALLOC(ctx->data_buffer, stream_size); + new_buffer = REALLOC(ctx->data_buffer, blob_size); if (!new_buffer) return false; ctx->data_buffer = new_buffer; - ctx->data_buffer_size = stream_size; + ctx->data_buffer_size = blob_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 @@ -1726,82 +1642,64 @@ prepare_data_buffer(struct win32_apply_ctx *ctx, u64 stream_size) } 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) +begin_extract_blob_instance(const struct blob_descriptor *blob, + struct wim_dentry *dentry, + const struct wim_inode_stream *strm, + 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); + if (unlikely(strm->stream_type == STREAM_TYPE_REPARSE_POINT)) { + /* We can't write the reparse point 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, blob->size)) + return WIMLIB_ERR_NOMEM; + list_add_tail(&dentry->tmp_list, &ctx->reparse_dentries); + return 0; } - - /* 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 + if (unlikely(strm->stream_type == STREAM_TYPE_EFSRPC_RAW_DATA)) { + /* We can't write encrypted files 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. + * The current workaround is to simply read the blob 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)) + if (!prepare_data_buffer(ctx, blob->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; - } + /* It's a data stream (may be unnamed or named). */ + wimlib_assert(strm->stream_type == STREAM_TYPE_DATA); - if (ctx->num_open_handles == MAX_OPEN_STREAMS) { + if (ctx->num_open_handles == MAX_OPEN_FILES) { /* 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. */ + * extract_blob_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; } + + if (unlikely(stream_is_named(strm))) { + build_extraction_path_with_ads(dentry, ctx, + strm->stream_name, + utf16le_len_chars(strm->stream_name)); + } else { + build_extraction_path(dentry, ctx); + } + + /* Open a new handle */ status = do_create_file(&h, FILE_WRITE_DATA | SYNCHRONIZE, @@ -1810,17 +1708,15 @@ begin_extract_stream_instance(const struct wim_lookup_table_entry *stream, FILE_SYNCHRONOUS_IO_NONALERT, ctx); if (!NT_SUCCESS(status)) { - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't open \"%ls\" for writing " - "(status=0x%08"PRIx32")", - current_path(ctx), (u32)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; + alloc_info.AllocationSize.QuadPart = blob->size; (*func_NtSetInformationFile)(h, &ctx->iosb, &alloc_info, sizeof(alloc_info), FileAllocationInformation); @@ -1870,10 +1766,8 @@ do_set_reparse_data(const struct wim_dentry *dentry, } fail: - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't set reparse data on \"%ls\" " - "(status=0x%08"PRIx32")", - current_path(ctx), (u32)status); + winnt_error(status, L"Can't set reparse data on \"%ls\"", + current_path(ctx)); return WIMLIB_ERR_SET_REPARSE_DATA; } @@ -2084,9 +1978,8 @@ retry: build_extraction_path(dentry, ctx); if (err != ERROR_SUCCESS) { - set_errno_from_win32_error(err); - ERROR_WITH_ERRNO("Can't open \"%ls\" for encrypted import " - "(err=%"PRIu32")", current_path(ctx), (u32)err); + win32_error(err, L"Can't open \"%ls\" for encrypted import", + current_path(ctx)); return WIMLIB_ERR_OPEN; } @@ -2097,21 +1990,20 @@ retry: CloseEncryptedFileRaw(rawctx); if (err != ERROR_SUCCESS) { - set_errno_from_win32_error(err); - ERROR_WITH_ERRNO("Can't import encrypted file \"%ls\" " - "(err=%"PRIu32")", current_path(ctx), (u32)err); + win32_error(err, L"Can't import encrypted file \"%ls\"", + current_path(ctx)); return WIMLIB_ERR_WRITE; } return 0; } -/* Called when starting to read a stream for extraction on Windows */ +/* Called when starting to read a blob for extraction on Windows */ static int -begin_extract_stream(struct wim_lookup_table_entry *stream, void *_ctx) +begin_extract_blob(struct blob_descriptor *blob, void *_ctx) { struct win32_apply_ctx *ctx = _ctx; - const struct stream_owner *owners = stream_owners(stream); + const struct blob_extraction_target *targets = blob_extraction_targets(blob); int ret; ctx->num_open_handles = 0; @@ -2119,22 +2011,21 @@ begin_extract_stream(struct wim_lookup_table_entry *stream, void *_ctx) INIT_LIST_HEAD(&ctx->reparse_dentries); INIT_LIST_HEAD(&ctx->encrypted_dentries); - 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; + for (u32 i = 0; i < blob->out_refcnt; i++) { + const struct wim_inode *inode = targets[i].inode; + const struct wim_inode_stream *strm = targets[i].stream; struct wim_dentry *dentry; - /* A copy of the stream needs to be extracted to @inode. */ + /* A copy of the blob needs to be extracted to @inode. */ if (ctx->common.supported_features.hard_links) { dentry = inode_first_extraction_dentry(inode); - ret = begin_extract_stream_instance(stream, dentry, - stream_name, ctx); + ret = begin_extract_blob_instance(blob, dentry, strm, ctx); ret = check_apply_error(dentry, ctx, ret); if (ret) goto fail; } else { - /* Hard links not supported. Extract the stream + /* Hard links not supported. Extract the blob * separately to each alias of the inode. */ struct list_head *next; @@ -2142,10 +2033,7 @@ begin_extract_stream(struct wim_lookup_table_entry *stream, void *_ctx) do { dentry = list_entry(next, struct wim_dentry, d_extraction_alias_node); - ret = begin_extract_stream_instance(stream, - dentry, - stream_name, - ctx); + ret = begin_extract_blob_instance(blob, dentry, strm, ctx); ret = check_apply_error(dentry, ctx, ret); if (ret) goto fail; @@ -2161,8 +2049,8 @@ fail: return ret; } -/* Called when the next chunk of a stream has been read for extraction on - * Windows */ +/* Called when the next chunk of a blob has been read for extraction on Windows + */ static int extract_chunk(const void *chunk, size_t size, void *_ctx) { @@ -2181,10 +2069,7 @@ extract_chunk(const void *chunk, size_t size, void *_ctx) &ctx->iosb, bufptr, count, NULL, NULL); if (!NT_SUCCESS(status)) { - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Error writing data to target " - "volume (status=0x%08"PRIx32")", - (u32)status); + winnt_error(status, L"Error writing data to target volume"); return WIMLIB_ERR_WRITE; } bufptr += ctx->iosb.Information; @@ -2199,9 +2084,9 @@ extract_chunk(const void *chunk, size_t size, void *_ctx) return 0; } -/* Called when a stream has been fully read for extraction on Windows */ +/* Called when a blob has been fully read for extraction on Windows */ static int -end_extract_stream(struct wim_lookup_table_entry *stream, int status, void *_ctx) +end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx) { struct win32_apply_ctx *ctx = _ctx; int ret; @@ -2216,26 +2101,27 @@ end_extract_stream(struct wim_lookup_table_entry *stream, int status, void *_ctx return 0; if (!list_empty(&ctx->reparse_dentries)) { - if (stream->size > REPARSE_DATA_MAX_SIZE) { + if (blob->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, + current_path(ctx), blob->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; + /* In the WIM format, reparse point streams are just the reparse + * data and omit the header. But we can reconstruct the header. + */ + memcpy(ctx->rpbuf.rpdata, ctx->data_buffer, blob->size); + ctx->rpbuf.rpdatalen = blob->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, + blob->size + REPARSE_DATA_OFFSET, ctx); ret = check_apply_error(dentry, ctx, ret); if (ret) @@ -2244,7 +2130,7 @@ end_extract_stream(struct wim_lookup_table_entry *stream, int status, void *_ctx } if (!list_empty(&ctx->encrypted_dentries)) { - ctx->encrypted_size = stream->size; + ctx->encrypted_size = blob->size; list_for_each_entry(dentry, &ctx->encrypted_dentries, tmp_list) { ret = extract_encrypted_file(dentry, ctx); ret = check_apply_error(dentry, ctx, ret); @@ -2428,10 +2314,9 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, if (!NT_SUCCESS(status) && (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) { - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't set security descriptor " - "on \"%ls\" (status=0x%08"PRIx32")", - current_path(ctx), (u32)status); + winnt_error(status, + L"Can't set security descriptor on \"%ls\"", + current_path(ctx)); return WIMLIB_ERR_SET_SECURITY; } } @@ -2441,10 +2326,13 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, 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 = 0; - else + 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), @@ -2456,10 +2344,8 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, && !(status == STATUS_INVALID_PARAMETER && dentry_is_root(inode_first_extraction_dentry(inode)))) { - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't set basic metadata on \"%ls\" " - "(status=0x%08"PRIx32")", - current_path(ctx), (u32)status); + winnt_error(status, L"Can't set basic metadata on \"%ls\"", + current_path(ctx)); return WIMLIB_ERR_SET_ATTRIBUTES; } @@ -2501,10 +2387,8 @@ apply_metadata_to_file(const struct wim_dentry *dentry, continue; } } - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't open \"%ls\" to set metadata " - "(status=0x%08"PRIx32")", - current_path(ctx), (u32)status); + winnt_error(status, L"Can't open \"%ls\" to set metadata", + current_path(ctx)); return WIMLIB_ERR_OPEN; } @@ -2629,15 +2513,15 @@ win32_extract(struct list_head *dentry_list, struct apply_ctx *_ctx) if (ret) goto out; - struct read_stream_list_callbacks cbs = { - .begin_stream = begin_extract_stream, - .begin_stream_ctx = ctx, + struct read_blob_list_callbacks cbs = { + .begin_blob = begin_extract_blob, + .begin_blob_ctx = ctx, .consume_chunk = extract_chunk, .consume_chunk_ctx = ctx, - .end_stream = end_extract_stream, - .end_stream_ctx = ctx, + .end_blob = end_extract_blob, + .end_blob_ctx = ctx, }; - ret = extract_stream_list(&ctx->common, &cbs); + ret = extract_blob_list(&ctx->common, &cbs); if (ret) goto out;