#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/metadata.h"
+#include "wimlib/object_id.h"
#include "wimlib/paths.h"
#include "wimlib/pattern.h"
#include "wimlib/reparse.h"
+#include "wimlib/scan.h" /* for mangle_pat() and match_pattern_list() */
#include "wimlib/textfile.h"
#include "wimlib/xml.h"
#include "wimlib/wimboot.h"
} wimboot;
/* External backing information */
- struct string_set *prepopulate_pats;
+ struct string_list *prepopulate_pats;
void *mem_prepopulate_pats;
bool tried_to_load_prepopulate_list;
* beginning of the array) */
unsigned num_open_handles;
+ /* For each currently open stream, whether we're writing to it in
+ * "sparse" mode or not. */
+ bool is_sparse_stream[MAX_OPEN_FILES];
+
+ /* Whether is_sparse_stream[] is true for any currently open stream */
+ bool any_sparse_streams;
+
/* List of dentries, joined by @d_tmp_list, that need to have reparse
* data extracted as soon as the whole blob has been read into
* @data_buffer. */
* [PrepopulateList]. */
unsigned long num_system_compression_exclusions;
+ /* Number of files for which we couldn't set the object ID. */
+ unsigned long num_object_id_failures;
+
/* The Windows build number of the image being applied, or 0 if unknown.
*/
u64 windows_build_number;
supported_features->not_context_indexed_files = 1;
- /* Don't do anything with FILE_SUPPORTS_SPARSE_FILES. */
+ if (vol_flags & FILE_SUPPORTS_SPARSE_FILES)
+ supported_features->sparse_files = 1;
if (vol_flags & FILE_NAMED_STREAMS)
supported_features->named_data_streams = 1;
if (short_names_supported)
supported_features->short_names = 1;
+ if (vol_flags & FILE_SUPPORTS_OBJECT_IDS)
+ supported_features->object_ids = 1;
+
supported_features->timestamps = 1;
- /* Note: Windows does not support case sensitive filenames! At least
- * not without changing the registry and rebooting... */
+ if (vol_flags & FILE_CASE_SENSITIVE_SEARCH) {
+ /*
+ * The filesystem supports case-sensitive filenames. But does
+ * the operating system as well? This normally requires the
+ * registry setting ObCaseInsensitive=0. We can test it
+ * indirectly by attempting to open the "\SystemRoot" symbolic
+ * link using a name with the wrong case. If we get
+ * STATUS_OBJECT_NAME_NOT_FOUND instead of STATUS_ACCESS_DENIED,
+ * then case-sensitive names must be enabled.
+ */
+ UNICODE_STRING path;
+ OBJECT_ATTRIBUTES attr;
+ HANDLE h;
+ NTSTATUS status;
+
+ RtlInitUnicodeString(&path, L"\\systemroot");
+ InitializeObjectAttributes(&attr, &path, 0, NULL, NULL);
+
+ status = NtOpenSymbolicLinkObject(&h, 0, &attr);
+ if (status == STATUS_OBJECT_NAME_NOT_FOUND)
+ supported_features->case_sensitive_filenames = 1;
+ }
return 0;
}
const struct blob_descriptor *blob;
int ret;
void *buf;
- struct string_set *s;
+ struct string_list *strings;
void *mem;
struct text_file_section sec;
if (ret)
return ret;
- s = CALLOC(1, sizeof(struct string_set));
- if (!s) {
+ strings = CALLOC(1, sizeof(struct string_list));
+ if (!strings) {
FREE(buf);
return WIMLIB_ERR_NOMEM;
}
sec.name = T("PrepopulateList");
- sec.strings = s;
+ sec.strings = strings;
ret = do_load_text_file(path, buf, blob->size, &mem, &sec, 1,
LOAD_TEXT_FILE_REMOVE_QUOTES |
STATIC_ASSERT(OS_PREFERRED_PATH_SEPARATOR == WIM_PATH_SEPARATOR);
FREE(buf);
if (ret) {
- FREE(s);
+ FREE(strings);
return ret;
}
- ctx->prepopulate_pats = s;
+ ctx->prepopulate_pats = strings;
ctx->mem_prepopulate_pats = mem;
return 0;
}
build_win32_extraction_path(dentry, ctx);
- randomize_char_array_with_alnum(subkeyname, 20);
+ get_random_alnum_chars(subkeyname, 20);
subkeyname[20] = L'\0';
res = RegLoadKey(HKEY_LOCAL_MACHINE, subkeyname, ctx->pathbuf.Buffer);
0,
FILE_SHARE_VALID_FLAGS,
FILE_OPEN_IF,
- FILE_DIRECTORY_FILE |
- FILE_OPEN_FOR_BACKUP_INTENT,
+ FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT,
NULL,
0);
if (!NT_SUCCESS(status)) {
status = winnt_fsctl(h, FSCTL_SET_COMPRESSION,
&compression_state, sizeof(USHORT), NULL, 0, NULL);
if (NT_SUCCESS(status))
- return status;
+ return 0;
winnt_error(status, L"Can't %s compression attribute on \"%ls\"",
(compressed ? "set" : "clear"), current_path(ctx));
return WIMLIB_ERR_SET_ATTRIBUTES;
}
+static bool
+need_sparse_flag(const struct wim_inode *inode,
+ const struct win32_apply_ctx *ctx)
+{
+ return (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) &&
+ ctx->common.supported_features.sparse_files;
+}
+
+static int
+set_sparse_flag(HANDLE h, struct win32_apply_ctx *ctx)
+{
+ NTSTATUS status;
+
+ status = winnt_fsctl(h, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, NULL);
+ if (NT_SUCCESS(status))
+ return 0;
+
+ winnt_error(status, L"Can't set sparse flag on \"%ls\"",
+ current_path(ctx));
+ return WIMLIB_ERR_SET_ATTRIBUTES;
+}
+
/* Try to enable short name support on the target volume. If successful, return
* true. If unsuccessful, issue a warning and return false. */
static bool
FileShortNameInformation);
if (status == STATUS_INVALID_PARAMETER && !retried) {
-
/* Microsoft forgot to make it possible to remove short names
* until Windows 7. Oops. Use a random short name instead. */
-
+ get_random_alnum_chars(info->FileName, 8);
+ wcscpy(&info->FileName[8], L".WLB");
info->FileNameLength = 12 * sizeof(wchar_t);
- for (int i = 0; i < 8; i++)
- info->FileName[i] = 'A' + (rand() % 26);
- info->FileName[8] = L'.';
- info->FileName[9] = L'W';
- info->FileName[10] = L'L';
- info->FileName[11] = L'B';
- info->FileName[12] = L'\0';
retried = true;
goto retry;
}
/*
* 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.
+ * open handle to the file or named data stream with the requested permissions.
*/
static int
-supersede_file_or_stream(struct win32_apply_ctx *ctx, HANDLE *h_ret)
+supersede_file_or_stream(struct win32_apply_ctx *ctx, DWORD perms,
+ HANDLE *h_ret)
{
NTSTATUS status;
bool retried = false;
* 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,
+ perms,
NULL,
FILE_ATTRIBUTE_SYSTEM,
FILE_CREATE,
build_extraction_path_with_ads(dentry, ctx,
strm->stream_name,
utf16le_len_chars(strm->stream_name));
- ret = supersede_file_or_stream(ctx, &h);
+ /*
+ * Note: do not request any permissions on the handle.
+ * Otherwise, we may encounter a Windows bug where the
+ * parent directory DACL denies read access to the new
+ * named data stream, even when using backup semantics!
+ */
+ ret = supersede_file_or_stream(ctx, 0, &h);
build_extraction_path(dentry, ctx);
int ret;
/* DELETE is needed for set_short_name(); GENERIC_READ and GENERIC_WRITE
- * are needed for adjust_compression_attribute(); WRITE_DAC is needed to
- * remove the directory's DACL if the directory already existed */
- perms = GENERIC_READ | GENERIC_WRITE | WRITE_DAC;
+ * 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. */
-retry:
status = create_file(&h, perms, NULL, FILE_ATTRIBUTE_SYSTEM,
FILE_OPEN_IF, FILE_DIRECTORY_FILE, dentry, ctx);
if (unlikely(!NT_SUCCESS(status))) {
- if (status == STATUS_ACCESS_DENIED) {
- if (perms & WRITE_DAC) {
- perms &= ~WRITE_DAC;
- goto retry;
- }
- if (perms & DELETE) {
- perms &= ~DELETE;
- goto retry;
- }
- }
const wchar_t *path = current_path(ctx);
winnt_error(status, L"Can't create directory \"%ls\"", path);
sizeof(basic_info),
FileBasicInformation);
}
-
- /* Also try to remove the directory's DACL. This isn't supposed
- * to be necessary because we *always* use backup semantics.
- * However, there is a case where NtCreateFile() fails with
- * STATUS_ACCESS_DENIED when creating a named data stream that
- * was just deleted, using a directory-relative open. I have no
- * idea why Windows is broken in this case. */
- if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)) {
- static const SECURITY_DESCRIPTOR_RELATIVE desc = {
- .Revision = SECURITY_DESCRIPTOR_REVISION1,
- .Control = SE_SELF_RELATIVE | SE_DACL_PRESENT,
- .Owner = 0,
- .Group = 0,
- .Sacl = 0,
- .Dacl = 0,
- };
- NtSetSecurityObject(h, DACL_SECURITY_INFORMATION,
- (void *)&desc);
- }
}
if (!dentry_is_root(dentry)) {
build_extraction_path(dentry, ctx);
- ret = supersede_file_or_stream(ctx, &h);
+ ret = supersede_file_or_stream(ctx,
+ GENERIC_READ | GENERIC_WRITE | DELETE,
+ &h);
if (ret)
goto out;
if (ret)
goto out_close;
+ if (need_sparse_flag(dentry->d_inode, ctx)) {
+ ret = set_sparse_flag(h, ctx);
+ if (ret)
+ goto out_close;
+ }
+
ret = create_empty_streams(dentry, ctx);
if (ret)
goto out_close;
const struct wim_inode_stream *strm,
struct win32_apply_ctx *ctx)
{
- FILE_ALLOCATION_INFORMATION alloc_info;
HANDLE h;
NTSTATUS status;
return WIMLIB_ERR_OPEN;
}
+ ctx->is_sparse_stream[ctx->num_open_handles] = false;
+ if (need_sparse_flag(dentry->d_inode, ctx)) {
+ /* If the stream is unnamed, then the sparse flag was already
+ * set when the file was created. But if the stream is named,
+ * then we need to set the sparse flag here. */
+ if (unlikely(stream_is_named(strm))) {
+ int ret = set_sparse_flag(h, ctx);
+ if (ret) {
+ NtClose(h);
+ return ret;
+ }
+ }
+ ctx->is_sparse_stream[ctx->num_open_handles] = true;
+ ctx->any_sparse_streams = true;
+ } else {
+ /* Allocate space for the data. */
+ FILE_ALLOCATION_INFORMATION info =
+ { .AllocationSize = { .QuadPart = blob->size }};
+ NtSetInformationFile(h, &ctx->iosb, &info, sizeof(info),
+ FileAllocationInformation);
+ }
ctx->open_handles[ctx->num_open_handles++] = h;
-
- /* Allocate space for the data. */
- alloc_info.AllocationSize.QuadPart = blob->size;
- NtSetInformationFile(h, &ctx->iosb, &alloc_info, sizeof(alloc_info),
- FileAllocationInformation);
return 0;
}
return 0;
}
-/* Called when starting to read a blob for extraction on Windows */
+/* Called when starting to read a blob for extraction */
static int
-begin_extract_blob(struct blob_descriptor *blob, void *_ctx)
+win32_begin_extract_blob(struct blob_descriptor *blob, void *_ctx)
{
struct win32_apply_ctx *ctx = _ctx;
const struct blob_extraction_target *targets = blob_extraction_targets(blob);
ctx->num_open_handles = 0;
ctx->data_buffer_ptr = NULL;
+ ctx->any_sparse_streams = false;
INIT_LIST_HEAD(&ctx->reparse_dentries);
INIT_LIST_HEAD(&ctx->encrypted_dentries);
return ret;
}
-/* 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)
+pwrite_to_handle(HANDLE h, const void *data, size_t size, u64 offset)
+{
+ const void * const end = data + size;
+ const void *p;
+ IO_STATUS_BLOCK iosb;
+ NTSTATUS status;
+
+ for (p = data; p != end; p += iosb.Information,
+ offset += iosb.Information)
+ {
+ LARGE_INTEGER offs = { .QuadPart = offset };
+
+ status = NtWriteFile(h, NULL, NULL, NULL, &iosb,
+ (void *)p, min(INT32_MAX, end - p),
+ &offs, NULL);
+ if (!NT_SUCCESS(status)) {
+ winnt_error(status,
+ L"Error writing data to target volume");
+ return WIMLIB_ERR_WRITE;
+ }
+ }
+ return 0;
+}
+
+/* Called when the next chunk of a blob has been read for extraction */
+static int
+win32_extract_chunk(const struct blob_descriptor *blob, u64 offset,
+ const void *chunk, size_t size, void *_ctx)
{
struct win32_apply_ctx *ctx = _ctx;
+ const void * const end = chunk + size;
+ const void *p;
+ bool zeroes;
+ size_t len;
+ unsigned i;
+ int ret;
- /* Write the data chunk to each open handle */
- for (unsigned i = 0; i < ctx->num_open_handles; i++) {
- u8 *bufptr = (u8 *)chunk;
- size_t bytes_remaining = size;
- NTSTATUS status;
- while (bytes_remaining) {
- ULONG count = min(0xFFFFFFFF, bytes_remaining);
-
- status = NtWriteFile(ctx->open_handles[i],
- NULL, NULL, NULL,
- &ctx->iosb, bufptr, count,
- NULL, NULL);
- if (!NT_SUCCESS(status)) {
- winnt_error(status, L"Error writing data to target volume");
- return WIMLIB_ERR_WRITE;
+ /*
+ * For sparse streams, only write nonzero regions. This lets the
+ * filesystem use holes to represent zero regions.
+ */
+ for (p = chunk; p != end; p += len, offset += len) {
+ zeroes = maybe_detect_sparse_region(p, end - p, &len,
+ ctx->any_sparse_streams);
+ for (i = 0; i < ctx->num_open_handles; i++) {
+ if (!zeroes || !ctx->is_sparse_stream[i]) {
+ ret = pwrite_to_handle(ctx->open_handles[i],
+ p, len, offset);
+ if (ret)
+ return ret;
}
- bufptr += ctx->iosb.Information;
- bytes_remaining -= ctx->iosb.Information;
}
}
/* Hard-coded list of files which the Windows bootloader may need to access
* before the WOF driver has been loaded. */
-static wchar_t *bootloader_pattern_strings[] = {
+static const wchar_t * const bootloader_pattern_strings[] = {
L"*winload.*",
L"*winresume.*",
L"\\Windows\\AppPatch\\drvmain.sdb",
L"\\Windows\\System32\\CodeIntegrity\\driver.stl",
};
-static const struct string_set bootloader_patterns = {
- .strings = bootloader_pattern_strings,
+static const struct string_list bootloader_patterns = {
+ .strings = (wchar_t **)bootloader_pattern_strings,
.num_strings = ARRAY_LEN(bootloader_pattern_strings),
};
}
}
-/* Called when a blob has been fully read for extraction on Windows */
+/* Called when a blob has been fully read for extraction */
static int
-end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx)
+win32_end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx)
{
struct win32_apply_ctx *ctx = _ctx;
int ret;
const struct wim_dentry *dentry;
+ /* Extend sparse streams to their final size. */
+ if (ctx->any_sparse_streams && !status) {
+ for (unsigned i = 0; i < ctx->num_open_handles; i++) {
+ FILE_END_OF_FILE_INFORMATION info =
+ { .EndOfFile = { .QuadPart = blob->size } };
+ NTSTATUS ntstatus;
+
+ if (!ctx->is_sparse_stream[i])
+ continue;
+
+ ntstatus = NtSetInformationFile(ctx->open_handles[i],
+ &ctx->iosb,
+ &info, sizeof(info),
+ FileEndOfFileInformation);
+ if (!NT_SUCCESS(ntstatus)) {
+ winnt_error(ntstatus, L"Error writing data to "
+ "target volume (while extending)");
+ status = WIMLIB_ERR_WRITE;
+ break;
+ }
+ }
+ }
+
close_handles(ctx);
if (status)
FILE_ATTRIBUTE_SPARSE_FILE | \
FILE_ATTRIBUTE_COMPRESSED)
+static void
+set_object_id(HANDLE h, const struct wim_inode *inode,
+ struct win32_apply_ctx *ctx)
+{
+ const void *object_id;
+ u32 len;
+ NTSTATUS status;
+
+ if (!ctx->common.supported_features.object_ids)
+ return;
+
+ object_id = inode_get_object_id(inode, &len);
+ if (likely(object_id == NULL)) /* No object ID? */
+ return;
+
+ status = winnt_fsctl(h, FSCTL_SET_OBJECT_ID,
+ object_id, len, NULL, 0, NULL);
+ if (NT_SUCCESS(status))
+ return;
+
+ /* Object IDs must be unique within the filesystem. A duplicate might
+ * occur if an image containing object IDs is applied twice to the same
+ * filesystem. Arguably, the user should be warned in this case; but
+ * the reality seems to be that nothing important cares about object IDs
+ * except the Distributed Link Tracking Service... so for now these
+ * failures are just ignored. */
+ if (status == STATUS_DUPLICATE_NAME ||
+ status == STATUS_OBJECT_NAME_COLLISION)
+ return;
+
+ ctx->num_object_id_failures++;
+ if (ctx->num_object_id_failures < 10) {
+ winnt_warning(status, L"Can't set object ID on \"%ls\"",
+ current_path(ctx));
+ } else if (ctx->num_object_id_failures == 10) {
+ WARNING("Suppressing further warnings about failure to set "
+ "object IDs.");
+ }
+}
+
/* Set the security descriptor @desc, of @desc_size bytes, on the file with open
* handle @h. */
static NTSTATUS
FILE_BASIC_INFORMATION info;
NTSTATUS status;
- /* Set security descriptor if present and not in NO_ACLS mode */
+ /* Set the file's object ID if present and object IDs are supported by
+ * the filesystem. */
+ set_object_id(h, inode, ctx);
+
+ /* Set the file's security descriptor if present and we're not in
+ * NO_ACLS mode */
if (inode_has_security_descriptor(inode) &&
!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS))
{
goto out;
struct read_blob_callbacks cbs = {
- .begin_blob = begin_extract_blob,
- .consume_chunk = extract_chunk,
- .end_blob = end_extract_blob,
+ .begin_blob = win32_begin_extract_blob,
+ .continue_blob = win32_extract_chunk,
+ .end_blob = win32_end_extract_blob,
.ctx = ctx,
};
ret = extract_blob_list(&ctx->common, &cbs);